<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import configparser

import requests
from requests.auth import HTTPBasicAuth
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

from .const import CONFIG_FILE, CLOUD_URLS, DEVICE_URLS
from .models import AppModel

# disable InsecureRequestWarning: Unverified HTTPS request is being made.
requests.packages.urllib3.disable_warnings()

class LaMetricManager(object):
    """
    simple python class that allows the sending of notification
    messages to the LaMetric (https://www.lametric.com)
    """
    def __init__(self, config_file=CONFIG_FILE):
        # set current device to none
        self.dev = None

        # store the result of the last call
        self.result = None

        self.client_id = ""
        self.client_secret = ""
        self.token = ""

        # list of installed apps
        self.available_apps = []

        # load the config i.e. the LaMetric API details
        self.load_config(os.path.expanduser(config_file))

        # create oauth2 session that is required to access the cloud
        self.oauth = OAuth2Session(
            client=BackendApplicationClient(client_id=self.client_id)
        )

        # get a current token
        self.get_token()

    def _exec(self, cmd, url, json_data=None):
        """
        execute a command at the device
        """
        assert(cmd in ("GET", "POST", "PUT", "DELETE"))
        assert(self.dev is not None)

        if json_data is None:
            json_data = {}

        # add device address to the URL
        url = url % self.dev["ipv4_internal"]

        # set basic authentication
        auth = HTTPBasicAuth("dev", self.dev["api_key"])

        r = None
        if cmd == "GET":
            r = self.oauth.get(url, auth=auth, verify=False)

        elif cmd == "POST":
            r = self.oauth.post(url, auth=auth, json=json_data, verify=False)

        elif cmd == "PUT":
            r = self.oauth.put(url, auth=auth, json=json_data, verify=False)

        elif cmd == "DELETE":
            r = self.oauth.delete(url, auth=auth, verify=False)

        return r.json()

    def load_config(self, config_file):
        """
        load the config from the config file or create a template
        if it is not existing yet
        """
        config = configparser.ConfigParser()
        if not os.path.exists(config_file):
            # config file does not exist => create template
            config['lametric'] = {'client_id': '', 'client_secret': ''}
            with open(config_file, "w") as configfile:
                config.write(configfile)

            sys.exit(
                "The config file '%s' does not exist. An empty "
                "template has been created. Please fill in your "
                "credentials. Abort!" % config_file
            )

        # read config file
        config.read(config_file)

        # put config details to internal variables
        self.client_id = config.get("lametric", "client_id")
        self.client_secret = config.get("lametric", "client_secret")

    def set_device(self, dev):
        """
        set the current device (that will be used for API calls)
        """
        self.dev = dev
        self.set_apps_list()

    def _get_widget_id(self, package_name):
        """
        returns widget_id for given package_name
        does not care about multiple widget ids at the moment -

        :param package_name: package to check for
        :type package_name: str
        :return: id of first widget which belongs to the given package_name
        :rtype: str
        """
        widget_id = ""
        for app in self.get_apps_list():
            if app.package == package_name:
                widget_id = list(app.widgets.keys())[0]

        return widget_id

    # ----- rest api calls on cloud ------
    def get_token(self):
        """
        get current oauth token
        """
        self.token = self.oauth.fetch_token(
            token_url=CLOUD_URLS["get_token"][1],
            client_id=self.client_id,
            client_secret=self.client_secret
        )

    def get_user(self):
        """
        get the user details
        """
        r = self.oauth.get(CLOUD_URLS["get_user"][1])
        return r.json()

    def get_devices(self):
        """
        get all devices that are linked to the user
        """
        r = self.oauth.get(CLOUD_URLS["get_devices"][1])
        return r.json()

    # ----- rest api calls on device ------
    def get_endpoint_map(self):
        """
        returns API version and endpoint map
        """
        cmd, url = DEVICE_URLS["get_endpoint_map"]
        return self._exec(cmd, url)

    def get_device_state(self):
        """
        returns full device state
        """
        cmd, url = DEVICE_URLS["get_device_state"]
        return self._exec(cmd, url)

    def send_notification(
        self, model, priority="warning", icon_type=None, lifetime=None
    ):
        """
        sends new notification to device
        """
        assert(priority in ("info", "warning", "critical"))
        assert(icon_type in (None, "none", "info", "alert"))
        assert(lifetime is None or (lifetime > 0))

        cmd, url = DEVICE_URLS["send_notification"]

        json_data = {"model": model.json(), "priority": priority}

        if icon_type is not None:
            json_data["icon_type"] = icon_type
        if lifetime is not None:
            json_data["lifetime"] = lifetime

        return self._exec(cmd, url, json_data=json_data)

    def get_notifications(self):
        """
        returns the list of notifications in queue
        """
        cmd, url = DEVICE_URLS["get_notifications_queue"]
        return self._exec(cmd, url)

    def get_current_notification(self):
        """
        returns current notification (notification that is visible)
        """
        cmd, url = DEVICE_URLS["get_current_notification"]
        return self._exec(cmd, url)

    def get_notification(self, notification_id):
        """
        returns specific notification
        """
        cmd, url = DEVICE_URLS["get_notification"]
        return self._exec(cmd, url.replace(":id", notification_id))

    def remove_notification(self, notification_id):
        """
        removes notification from queue or dismisses if it is visible
        """
        cmd, url = DEVICE_URLS["remove_notification"]
        return self._exec(cmd, url.replace(":id", notification_id))

    def get_display(self):
        """
        returns information about display, like brightness
        """
        cmd, url = DEVICE_URLS["get_display"]
        return self._exec(cmd, url)

    def set_display(self, brightness=100, brightness_mode="auto"):
        """
        allows to modify display state (change brightness)
        """
        assert(brightness_mode in ("auto", "manual"))
        assert(brightness in range(101))

        cmd, url = DEVICE_URLS["set_display"]
        json_data = {
            "brightness_mode": brightness_mode,
            "brightness": brightness
        }

        return self._exec(cmd, url, json_data=json_data)

    def get_volume(self):
        """
        returns current volume
        """
        cmd, url = DEVICE_URLS["get_volume"]
        return self._exec(cmd, url)

    def set_volume(self, volume=50):
        """
        allows to change volume
        """
        assert(volume in range(101))

        cmd, url = DEVICE_URLS["set_volume"]
        json_data = {
            "volume": volume,
        }

        return self._exec(cmd, url, json_data=json_data)

    def get_bluetooth_state(self):
        """
        returns bluetooth state
        """
        cmd, url = DEVICE_URLS["get_bluetooth_state"]
        return self._exec(cmd, url)

    def set_bluetooth(self, active=None, name=None):
        """
        allows to activate/deactivate bluetooth and change name
        """
        assert(active is not None or name is not None)

        cmd, url = DEVICE_URLS["set_bluetooth"]
        json_data = {}
        if name is not None:
            json_data["name"] = name
        if active is not None:
            json_data["active"] = active

        return self._exec(cmd, url, json_data=json_data)

    def get_wifi_state(self):
        """
        returns wi-fi state
        """
        cmd, url = DEVICE_URLS["get_wifi_state"]
        return self._exec(cmd, url)

    # ----- rest api calls for app control on device ------
    def set_apps_list(self):
        """
        gets installed apps and puts them into the available_apps list
        """
        cmd, url = DEVICE_URLS["get_apps_list"]
        result = self._exec(cmd, url)

        self.available_apps = [
            AppModel(result[app])
            for app in result
        ]

    def get_apps_list(self):
        """
        returns list of available apps
        """
        return self.available_apps

    def switch_to_app(self, package):
        """
        activates an app that is specified by package. Selects the first
        app it finds in the app list

        :param package: name of package/app
        :type package: str
        :return: None
        :rtype: None
        """
        cmd, url = DEVICE_URLS["switch_to_app"]
        widget_id = self._get_widget_id(package)

        url %= '%s', package, widget_id

        self.result = self._exec(cmd, url)

    def switch_to_next_app(self):
        """
        switches to next app
        """
        cmd, url = DEVICE_URLS["switch_to_next_app"]
        self.result = self._exec(cmd, url)

    def switch_to_prev_app(self):
        """
        switches to previous app
        """
        cmd, url = DEVICE_URLS["switch_to_prev_app"]
        self.result = self._exec(cmd, url)

    def _app_exec(self, package, action, params=None):
        """
        meta method for all interactions with apps

        :param package: name of package/app
        :type package: str
        :param action: the action to be executed
        :type action: str
        :param params: optional parameters for this action
        :type params: dict
        :return: None
        :rtype: None
        """

        # get list of possible commands from app.actions
        allowed_commands = []
        for app in self.get_apps_list():
            if app.package == package:
                allowed_commands = list(app.actions.keys())
                break

        # check if action is in this list
        assert(action in allowed_commands)

        cmd, url = DEVICE_URLS["do_action"]

        # get widget id for the package
        widget_id = self._get_widget_id(package)
        url %= '%s', package, widget_id

        json_data = {"id": action}
        if params is not None:
            json_data["params"] = params
        self.result = self._exec(cmd, url, json_data=json_data)

    def radio_play(self):
        self._app_exec("com.lametric.radio", "radio.play")

    def radio_stop(self):
        self._app_exec("com.lametric.radio", "radio.stop")

    def radio_prev(self):
        self._app_exec("com.lametric.radio", "radio.prev")

    def radio_next(self):
        self._app_exec("com.lametric.radio", "radio.next")

    def alarm_set(self, time, wake_with_radio=False):
        # TODO: check for correct time format
        params = {
            "enabled": True,
            "time": time,
            "wake_with_radio": wake_with_radio
        }
        self._app_exec("com.lametric.clock", "clock.alarm", params=params)

    def alarm_disable(self):
        params = {"enabled": False}
        self._app_exec("com.lametric.clock", "clock.alarm", params=params)

    def countdown_start(self):
        self._app_exec("com.lametric.countdown", "countdown.start")

    def countdown_pause(self):
        self._app_exec("com.lametric.countdown", "countdown.pause")

    def countdown_reset(self):
        self._app_exec("com.lametric.countdown", "countdown.reset")

    def countdown_set(self, duration, start_now):
        params = {'duration': duration, 'start_now': start_now}
        self._app_exec(
            "com.lametric.countdown", "countdown.configure", params
        )

    def stopwatch_start(self):
        self._app_exec("com.lametric.stopwatch", "stopwatch.start")

    def stopwatch_pause(self):
        self._app_exec("com.lametric.stopwatch", "stopwatch.pause")

    def stopwatch_reset(self):
        self._app_exec("com.lametric.stopwatch", "stopwatch.reset")
<commit_msg>modified constructor signature<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import configparser

import requests
from requests.auth import HTTPBasicAuth
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

from .const import CONFIG_FILE, CLOUD_URLS, DEVICE_URLS
from .models import AppModel

# disable InsecureRequestWarning: Unverified HTTPS request is being made.
requests.packages.urllib3.disable_warnings()

class LaMetricManager(object):
    """
    simple python class that allows the sending of notification
    messages to the LaMetric (https://www.lametric.com)
    """
    def __init__(self, config_file=CONFIG_FILE, client_id=None, client_secret=None):
        # set current device to none
        self.dev = None

        # store the result of the last call
        self.result = None

        self.client_id = ""
        self.client_secret = ""
        self.token = ""

        # list of installed apps
        self.available_apps = []

        # if client_id or client_secret are not passed into the constructor
        if client_id is None or client_secret is None:
            # load the config i.e. the LaMetric API details
            self.load_config(os.path.expanduser(config_file))
        else:
            self.client_id = client_id
            self.client_secret = client_secret

        # create oauth2 session that is required to access the cloud
        self.oauth = OAuth2Session(
            client=BackendApplicationClient(client_id=self.client_id)
        )

        # get a current token
        self.get_token()

    def _exec(self, cmd, url, json_data=None):
        """
        execute a command at the device
        """
        assert(cmd in ("GET", "POST", "PUT", "DELETE"))
        assert(self.dev is not None)

        if json_data is None:
            json_data = {}

        # add device address to the URL
        url = url % self.dev["ipv4_internal"]

        # set basic authentication
        auth = HTTPBasicAuth("dev", self.dev["api_key"])

        r = None
        if cmd == "GET":
            r = self.oauth.get(url, auth=auth, verify=False)

        elif cmd == "POST":
            r = self.oauth.post(url, auth=auth, json=json_data, verify=False)

        elif cmd == "PUT":
            r = self.oauth.put(url, auth=auth, json=json_data, verify=False)

        elif cmd == "DELETE":
            r = self.oauth.delete(url, auth=auth, verify=False)

        return r.json()

    def load_config(self, config_file):
        """
        load the config from the config file or create a template
        if it is not existing yet
        """
        config = configparser.ConfigParser()
        if not os.path.exists(config_file):
            # config file does not exist => create template
            config['lametric'] = {'client_id': '', 'client_secret': ''}
            with open(config_file, "w") as configfile:
                config.write(configfile)

            sys.exit(
                "The config file '%s' does not exist. An empty "
                "template has been created. Please fill in your "
                "credentials. Abort!" % config_file
            )

        # read config file
        config.read(config_file)

        # put config details to internal variables
        self.client_id = config.get("lametric", "client_id")
        self.client_secret = config.get("lametric", "client_secret")

    def set_device(self, dev):
        """
        set the current device (that will be used for API calls)
        """
        self.dev = dev
        self.set_apps_list()

    def _get_widget_id(self, package_name):
        """
        returns widget_id for given package_name
        does not care about multiple widget ids at the moment -

        :param package_name: package to check for
        :type package_name: str
        :return: id of first widget which belongs to the given package_name
        :rtype: str
        """
        widget_id = ""
        for app in self.get_apps_list():
            if app.package == package_name:
                widget_id = list(app.widgets.keys())[0]

        return widget_id

    # ----- rest api calls on cloud ------
    def get_token(self):
        """
        get current oauth token
        """
        self.token = self.oauth.fetch_token(
            token_url=CLOUD_URLS["get_token"][1],
            client_id=self.client_id,
            client_secret=self.client_secret
        )

    def get_user(self):
        """
        get the user details
        """
        r = self.oauth.get(CLOUD_URLS["get_user"][1])
        return r.json()

    def get_devices(self):
        """
        get all devices that are linked to the user
        """
        r = self.oauth.get(CLOUD_URLS["get_devices"][1])
        return r.json()

    # ----- rest api calls on device ------
    def get_endpoint_map(self):
        """
        returns API version and endpoint map
        """
        cmd, url = DEVICE_URLS["get_endpoint_map"]
        return self._exec(cmd, url)

    def get_device_state(self):
        """
        returns full device state
        """
        cmd, url = DEVICE_URLS["get_device_state"]
        return self._exec(cmd, url)

    def send_notification(
        self, model, priority="warning", icon_type=None, lifetime=None
    ):
        """
        sends new notification to device
        """
        assert(priority in ("info", "warning", "critical"))
        assert(icon_type in (None, "none", "info", "alert"))
        assert(lifetime is None or (lifetime > 0))

        cmd, url = DEVICE_URLS["send_notification"]

        json_data = {"model": model.json(), "priority": priority}

        if icon_type is not None:
            json_data["icon_type"] = icon_type
        if lifetime is not None:
            json_data["lifetime"] = lifetime

        return self._exec(cmd, url, json_data=json_data)

    def get_notifications(self):
        """
        returns the list of notifications in queue
        """
        cmd, url = DEVICE_URLS["get_notifications_queue"]
        return self._exec(cmd, url)

    def get_current_notification(self):
        """
        returns current notification (notification that is visible)
        """
        cmd, url = DEVICE_URLS["get_current_notification"]
        return self._exec(cmd, url)

    def get_notification(self, notification_id):
        """
        returns specific notification
        """
        cmd, url = DEVICE_URLS["get_notification"]
        return self._exec(cmd, url.replace(":id", notification_id))

    def remove_notification(self, notification_id):
        """
        removes notification from queue or dismisses if it is visible
        """
        cmd, url = DEVICE_URLS["remove_notification"]
        return self._exec(cmd, url.replace(":id", notification_id))

    def get_display(self):
        """
        returns information about display, like brightness
        """
        cmd, url = DEVICE_URLS["get_display"]
        return self._exec(cmd, url)

    def set_display(self, brightness=100, brightness_mode="auto"):
        """
        allows to modify display state (change brightness)
        """
        assert(brightness_mode in ("auto", "manual"))
        assert(brightness in range(101))

        cmd, url = DEVICE_URLS["set_display"]
        json_data = {
            "brightness_mode": brightness_mode,
            "brightness": brightness
        }

        return self._exec(cmd, url, json_data=json_data)

    def get_volume(self):
        """
        returns current volume
        """
        cmd, url = DEVICE_URLS["get_volume"]
        return self._exec(cmd, url)

    def set_volume(self, volume=50):
        """
        allows to change volume
        """
        assert(volume in range(101))

        cmd, url = DEVICE_URLS["set_volume"]
        json_data = {
            "volume": volume,
        }

        return self._exec(cmd, url, json_data=json_data)

    def get_bluetooth_state(self):
        """
        returns bluetooth state
        """
        cmd, url = DEVICE_URLS["get_bluetooth_state"]
        return self._exec(cmd, url)

    def set_bluetooth(self, active=None, name=None):
        """
        allows to activate/deactivate bluetooth and change name
        """
        assert(active is not None or name is not None)

        cmd, url = DEVICE_URLS["set_bluetooth"]
        json_data = {}
        if name is not None:
            json_data["name"] = name
        if active is not None:
            json_data["active"] = active

        return self._exec(cmd, url, json_data=json_data)

    def get_wifi_state(self):
        """
        returns wi-fi state
        """
        cmd, url = DEVICE_URLS["get_wifi_state"]
        return self._exec(cmd, url)

    # ----- rest api calls for app control on device ------
    def set_apps_list(self):
        """
        gets installed apps and puts them into the available_apps list
        """
        cmd, url = DEVICE_URLS["get_apps_list"]
        result = self._exec(cmd, url)

        self.available_apps = [
            AppModel(result[app])
            for app in result
        ]

    def get_apps_list(self):
        """
        returns list of available apps
        """
        return self.available_apps

    def switch_to_app(self, package):
        """
        activates an app that is specified by package. Selects the first
        app it finds in the app list

        :param package: name of package/app
        :type package: str
        :return: None
        :rtype: None
        """
        cmd, url = DEVICE_URLS["switch_to_app"]
        widget_id = self._get_widget_id(package)

        url %= '%s', package, widget_id

        self.result = self._exec(cmd, url)

    def switch_to_next_app(self):
        """
        switches to next app
        """
        cmd, url = DEVICE_URLS["switch_to_next_app"]
        self.result = self._exec(cmd, url)

    def switch_to_prev_app(self):
        """
        switches to previous app
        """
        cmd, url = DEVICE_URLS["switch_to_prev_app"]
        self.result = self._exec(cmd, url)

    def _app_exec(self, package, action, params=None):
        """
        meta method for all interactions with apps

        :param package: name of package/app
        :type package: str
        :param action: the action to be executed
        :type action: str
        :param params: optional parameters for this action
        :type params: dict
        :return: None
        :rtype: None
        """

        # get list of possible commands from app.actions
        allowed_commands = []
        for app in self.get_apps_list():
            if app.package == package:
                allowed_commands = list(app.actions.keys())
                break

        # check if action is in this list
        assert(action in allowed_commands)

        cmd, url = DEVICE_URLS["do_action"]

        # get widget id for the package
        widget_id = self._get_widget_id(package)
        url %= '%s', package, widget_id

        json_data = {"id": action}
        if params is not None:
            json_data["params"] = params
        self.result = self._exec(cmd, url, json_data=json_data)

    def radio_play(self):
        self._app_exec("com.lametric.radio", "radio.play")

    def radio_stop(self):
        self._app_exec("com.lametric.radio", "radio.stop")

    def radio_prev(self):
        self._app_exec("com.lametric.radio", "radio.prev")

    def radio_next(self):
        self._app_exec("com.lametric.radio", "radio.next")

    def alarm_set(self, time, wake_with_radio=False):
        # TODO: check for correct time format
        params = {
            "enabled": True,
            "time": time,
            "wake_with_radio": wake_with_radio
        }
        self._app_exec("com.lametric.clock", "clock.alarm", params=params)

    def alarm_disable(self):
        params = {"enabled": False}
        self._app_exec("com.lametric.clock", "clock.alarm", params=params)

    def countdown_start(self):
        self._app_exec("com.lametric.countdown", "countdown.start")

    def countdown_pause(self):
        self._app_exec("com.lametric.countdown", "countdown.pause")

    def countdown_reset(self):
        self._app_exec("com.lametric.countdown", "countdown.reset")

    def countdown_set(self, duration, start_now):
        params = {'duration': duration, 'start_now': start_now}
        self._app_exec(
            "com.lametric.countdown", "countdown.configure", params
        )

    def stopwatch_start(self):
        self._app_exec("com.lametric.stopwatch", "stopwatch.start")

    def stopwatch_pause(self):
        self._app_exec("com.lametric.stopwatch", "stopwatch.pause")

    def stopwatch_reset(self):
        self._app_exec("com.lametric.stopwatch", "stopwatch.reset")
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
    longboxed.helpers
    ~~~~~~~~~~~~~~~~

    longboxed helpers module
"""

import pkgutil
import importlib

from datetime import datetime, timedelta
from HTMLParser import HTMLParser

from flask import Blueprint
from json import JSONEncoder as BaseJSONEncoder

def register_blueprints(app, package_name, package_path):
    """Register all Blueprint instances on the specified Flask application found
    in all modules for the specified package.

    :param app: the Flask application
    :param package_name: the package name
    :param package_path: the package path
    """
    rv = []
    for _, name, _ in pkgutil.iter_modules(package_path):
        m = importlib.import_module('%s.%s' % (package_name, name))
        for item in dir(m):
            item = getattr(m, item)
            if isinstance(item, Blueprint):
                app.register_blueprint(item)
            rv.append(item)
    return rv

class JSONEncoder(BaseJSONEncoder):
    """Custom :class:`JSONEncoder` which respects objects that include the
    :class:`JsonSerializer` mixin.
    """
    def default(self, obj):
        if isinstance(obj, JsonSerializer):
            return obj.to_json()
        return super(JSONEncoder, self).default(obj)

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

def wednesday(date, multiplier=0):
    """Given a date, returns a Date() object containng the week's wednesday.
    A week is defined as Sunday thru Saturday. The 'multiplier' argument
    provides the ability navigate multiple weeks into the future and past"""
    sunday, saturday = get_week(date, multiplier)
    return sunday + timedelta(days=3)

def current_wednesday():
    return wednesday(datetime.today().date())

def get_week(date, multiplier=0):
    """Returns Sunday and Saturday of the week the 'date' argument is currently in.
    The 'multiplier' argument provides the ability to navigate multiple weeks into
    the future and past"""
    day_idx = (date.weekday() + 1) % 7 # Turn sunday into 0, monday into 1, etc.
    sunday = (date - timedelta(days=day_idx)) + (multiplier * timedelta(7))
    saturday = (sunday + timedelta(days=6))
    return (sunday, saturday)

class JsonSerializer(object):
    """A mixin that can be used to mark a SQLAlchemy model class which
    implements a :func:`to_json` method. The :func:`to_json` method is used
    in conjuction with the custom :class:`JSONEncoder` class. By default this
    mixin will assume all properties of the SQLAlchemy model are to be visible
    in the JSON output. Extend this class to customize which properties are
    public, hidden or modified before being being passed to the JSON serializer.
    """

    __json_public__ = None
    __json_hidden__ = None
    __json_modifiers__ = None

    def get_field_names(self):
        for p in self.__mapper__.iterate_properties:
            yield p.key

    def to_json(self):
        field_names = self.get_field_names()

        public = self.__json_public__ or field_names
        hidden = self.__json_hidden__ or []
        modifiers = self.__json_modifiers__ or dict()

        rv = dict()
        for key in public:
            rv[key] = getattr(self, key)
        for key, modifier in modifiers.items():
            value = getattr(self, key)
            rv[key] = modifier(value, self)
        for key in hidden:
            rv.pop(key, None)
        return rv<commit_msg>added next_wednesday() function<commit_after># -*- coding: utf-8 -*-
"""
    longboxed.helpers
    ~~~~~~~~~~~~~~~~

    longboxed helpers module
"""

import pkgutil
import importlib

from datetime import datetime, timedelta
from HTMLParser import HTMLParser

from flask import Blueprint
from json import JSONEncoder as BaseJSONEncoder

def register_blueprints(app, package_name, package_path):
    """Register all Blueprint instances on the specified Flask application found
    in all modules for the specified package.

    :param app: the Flask application
    :param package_name: the package name
    :param package_path: the package path
    """
    rv = []
    for _, name, _ in pkgutil.iter_modules(package_path):
        m = importlib.import_module('%s.%s' % (package_name, name))
        for item in dir(m):
            item = getattr(m, item)
            if isinstance(item, Blueprint):
                app.register_blueprint(item)
            rv.append(item)
    return rv

class JSONEncoder(BaseJSONEncoder):
    """Custom :class:`JSONEncoder` which respects objects that include the
    :class:`JsonSerializer` mixin.
    """
    def default(self, obj):
        if isinstance(obj, JsonSerializer):
            return obj.to_json()
        return super(JSONEncoder, self).default(obj)

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

def wednesday(date, multiplier=0):
    """Given a date, returns a Date() object containng the week's wednesday.
    A week is defined as Sunday thru Saturday. The 'multiplier' argument
    provides the ability navigate multiple weeks into the future and past"""
    sunday, saturday = get_week(date, multiplier)
    return sunday + timedelta(days=3)

def current_wednesday():
    return wednesday(datetime.today().date())

def next_wednesday():
    return wednesday(datetime.today().date(), 1)

def get_week(date, multiplier=0):
    """Returns Sunday and Saturday of the week the 'date' argument is currently in.
    The 'multiplier' argument provides the ability to navigate multiple weeks into
    the future and past"""
    day_idx = (date.weekday() + 1) % 7 # Turn sunday into 0, monday into 1, etc.
    sunday = (date - timedelta(days=day_idx)) + (multiplier * timedelta(7))
    saturday = (sunday + timedelta(days=6))
    return (sunday, saturday)

class JsonSerializer(object):
    """A mixin that can be used to mark a SQLAlchemy model class which
    implements a :func:`to_json` method. The :func:`to_json` method is used
    in conjuction with the custom :class:`JSONEncoder` class. By default this
    mixin will assume all properties of the SQLAlchemy model are to be visible
    in the JSON output. Extend this class to customize which properties are
    public, hidden or modified before being being passed to the JSON serializer.
    """

    __json_public__ = None
    __json_hidden__ = None
    __json_modifiers__ = None

    def get_field_names(self):
        for p in self.__mapper__.iterate_properties:
            yield p.key

    def to_json(self):
        field_names = self.get_field_names()

        public = self.__json_public__ or field_names
        hidden = self.__json_hidden__ or []
        modifiers = self.__json_modifiers__ or dict()

        rv = dict()
        for key in public:
            rv[key] = getattr(self, key)
        for key, modifier in modifiers.items():
            value = getattr(self, key)
            rv[key] = modifier(value, self)
        for key in hidden:
            rv.pop(key, None)
        return rv<|endoftext|>


<commit_before>#!/usr/bin/env python
"""
Loslassa
========

Workflow
--------

Start
'''''
**not implemented yet**

::

    loslassa start */path/to/project*

Creates a new project with a basic structure and configuration
similar to sphinx-quickstart only simpler and tailored to only HTML output.

Play
''''
Playing with the source and create your page. Add content and see the
changes right away thanks to local server with automatic
rebuild of the web pages::

    cd */path/to/project*
    loslassa play

Starts a local development server reachable on http://localhost:8080.
All files in project folder are being watched and if something changes
the project is rebuild.

Publish
'''''''
**not implemented yet**

This part is a bit vague still but basically it should simply push the
generated pages to the server, by maintaining them in a git repository

First time publishing would clone the repository bare to the web space and
set it to be origin from then on
... or summin like that, didn't think that through yet::

    cd */path/to/project*
    loslassa loslassa

Customize
'''''''''
This is not thought out yet, but I imagine that additional customization
can be done easily by expanding the settings in the sphinx conf.py and
do more involved stuff via sphinx extensions.
"""
from __future__ import print_function

import logging
import sys

from plumbum import cli, cmd, local, utils as plum_utils
from plumbum.local_machine import LocalPath

from devserver import serve_with_reloader
from utils import *

LOSLASSA = "loslassa"
__version__ = '0.3.2-dev'

log = logging.getLogger()

class LoslassaProject(object):
    SOURCE_DIR_NAME = "source"
    CONF_FILE_NAME = "conf.py"
    BUILDS_DIR_NAME = "build"
    HTML_BUILD_DIR_NAME = "html"
    DOCTREES_DIR_NAME = "doctrees"
    EXAMPLE_PROJECT_NAME = "example_project"
    EXAMPLE_PROJECT_PATH = LocalPath(__file__).dirname/EXAMPLE_PROJECT_NAME

    def __init__(self, projectPath):
        assert projectPath, "No project path set"
        self.projectPath = LocalPath(projectPath)
        assert self.projectPath.exists
        self.projectName = self.projectPath.basename
        self.sourcePath = self.projectPath/self.SOURCE_DIR_NAME
        self.sphinxConfFilePath = self.sourcePath/self.CONF_FILE_NAME
        self.buildPath = self.projectPath/self.BUILDS_DIR_NAME
        self.doctreesPath = self.buildPath/self.DOCTREES_DIR_NAME
        self.outputPath = self.buildPath/self.HTML_BUILD_DIR_NAME

    def __str__(self):
        return simple_dbg(self, excludeNames=["allPaths"])

    def __repr__(self):
        return "<%s at %s>" % (self.__class__.__name__, self.projectPath)

    def create_empty_project(self):
        for thisPath in self.neededDirPaths:
            thisPath.mkdir()
        confSkelPAth = (
            self.EXAMPLE_PROJECT_PATH/self.SOURCE_DIR_NAME/self.CONF_FILE_NAME)
        plum_utils.copy(confSkelPAth, self.sphinxConfFilePath)

    @property
    def buildCommand(self):
        return cmd.sphinx_build[
            "-b", "dirhtml", "-d", self.doctreesPath._path,
            self.sourcePath._path, self.outputPath._path]

    def check_sanity(self):
        """Make sure we have a valid seeming sphinx project to work with"""
        badPaths = [path for path in self.neededPaths if not path.exists()]
        if badPaths:
            raise LoslassaError("missing paths: %s" % (badPaths))

    @cached_property
    def neededPaths(self):
        return self.neededDirPaths + [self.sphinxConfFilePath]

    @cached_property
    def neededDirPaths(self):
        return [self.projectPath, self.sourcePath]

    @cached_property
    def allPaths(self):
        return [getattr(self, p) for p in self.__dict__ if p.endswith("Path")]

class LoslassaCliApplication(cli.Application):
    PROGNAME = LOSLASSA
    VERSION = __version__
    USAGE = LOSLASSA + " [start|play|loslassa] [OPTIONS]"
    projectPath = None
    logLevel = logging.DEBUG
    logFilePath = None

    def __str__(self):
        return simple_dbg(
            self, excludeNames=["parent", "nested_command"])

    @cli.autoswitch(str)
    def project_name(self, projectName):
        """Set name (can be a relative or absolute path as well"""
        self.projectPath = LocalPath(projectName)

    @cli.autoswitch(str)
    def verbosity(self, level):
        """Adjust the talkativeness of loslassing activities.

        :param str level: log level (one of the accepted logging values)
        Levels from very chatty to almost silent: debug, info, warning, error
        """
        try:
            self.logLevel = logging.getLevelName(int(level))
        except ValueError:
            self.logLevel = level.upper()

    @cli.autoswitch(str)
    def log_to_file(self, filePath):
        """Log to a file instead of the console"""
        self.logFilePath = filePath

    def _init(self, create=False):
        if not self.projectPath:
            confPath = find_file(local.cwd, LoslassaProject.CONF_FILE_NAME)
            self.projectPath = confPath.dirname.up()
        self.project = LoslassaProject(self.projectPath)
        if create:
            self.project.create_empty_project()
        self.project.check_sanity()
        adjust_log(level=self.logLevel, filePath=self.logFilePath)
        log.info("working with project '%s'" % (self.project.projectName))
        if log.getEffectiveLevel() > logging.DEBUG:
            sys.excepthook = friendly_exception_handler

class Loslassa(LoslassaCliApplication):
    def main(self, *args):
        log.debug("executing command %s" % str(self.nested_command))
        if args:
            log.error("unknown command %r" % (args[0]))
            return 1

        if not self.nested_command:
            log.error("Which kind of loslassing? "
                      "Try %s --help" % (Loslassa.PROGNAME))
            return 1

@Loslassa.subcommand("start")
class LoslassaStart(LoslassaCliApplication):
    """Starts a new project by creating the initial project structure"""
    def main(self):
        log.info("start loslassing ...")
        if not self.projectPath:
            raise LoslassaError("please provide a name for the project")

        if self.projectPath.exists():
            # fixme Workdir.dirname as in local.cwd.dirname crashes
            # using._path in the meantime
            # see https://github.com/tomerfiliba/plumbum/issues/78
            raise LoslassaError(
                "'%s' exists already in %s, try a different name." %
                (self.projectPath.basename, self.projectPath._path))

        self._init(create=True)

@Loslassa.subcommand("play")
class LoslassaPlay(LoslassaCliApplication):
    """Start playing with the source and create your page"""
    serverPort = 8080

    @cli.autoswitch(int)
    def serve_on_port(self, serverPort):
        """Set port manually"""
        self.serverPort = serverPort

    def main(self):
        """Create the project representation and start serving"""
        self._init()
        log.info("play loslassing...")
        log.debug("work with %s" % (self.project))
        serve_with_reloader(
            serveFromPath=str(self.project.outputPath),
            port=self.serverPort,
            changedCallback=self.project.buildCommand,
            pathToWatch=self.project.sourcePath)

@Loslassa.subcommand(LOSLASSA)
class LoslassaLoslassa(LoslassaCliApplication):
    """Practice loslassing by pushing your page into the interwebs"""
    def main(self):
        self._init(create=True)
        # todo make a progress bar consisting of loslassa :)
        log.info("loslassa loslassa loslassa ...")
        #raise LoslassaError("coming soon...")

def main():
    logging.basicConfig(level=logging.INFO, format='%(message)s')
    Loslassa.run()

if __name__ == "__main__":
    sys.exit(main())
<commit_msg>fixme and some test settings<commit_after>#!/usr/bin/env python
"""
Loslassa
========

Workflow
--------

Start
'''''
**not implemented yet**

::

    loslassa start */path/to/project*

Creates a new project with a basic structure and configuration
similar to sphinx-quickstart only simpler and tailored to only HTML output.

Play
''''
Playing with the source and create your page. Add content and see the
changes right away thanks to local server with automatic
rebuild of the web pages::

::

    cd */path/to/project*
    loslassa play

Starts a local development server reachable on http://localhost:8080.
All files in project folder are being watched and if something changes
the project is rebuild.

Publish
'''''''
**not implemented yet**

This part is a bit vague still but basically it should simply push the
generated pages to the server, by maintaining them in a git repository

First time publishing would clone the repository bare to the web space and
set it to be origin from then on
... or summin like that, didn't think that through yet::

    cd */path/to/project*
    loslassa loslassa

Customize
'''''''''
This is not thought out yet, but I imagine that additional customization
can be done easily by expanding the settings in the sphinx conf.py and
do more involved stuff via sphinx extensions.
"""
from __future__ import print_function

import logging
import sys

from plumbum import cli, cmd, local, utils as plum_utils
from plumbum.local_machine import LocalPath

from devserver import serve_with_reloader
from utils import *

LOSLASSA = "loslassa"
__version__ = '0.3.2-dev'

log = logging.getLogger()

class LoslassaProject(object):
    SOURCE_DIR_NAME = "source"
    CONF_FILE_NAME = "conf.py"
    BUILDS_DIR_NAME = "build"
    HTML_BUILD_DIR_NAME = "html"
    DOCTREES_DIR_NAME = "doctrees"
    EXAMPLE_PROJECT_NAME = "example_project"
    EXAMPLE_PROJECT_PATH = LocalPath(__file__).dirname/EXAMPLE_PROJECT_NAME

    def __init__(self, projectPath):
        assert projectPath, "No project path set"
        self.projectPath = LocalPath(projectPath)
        assert self.projectPath.exists
        self.projectName = self.projectPath.basename
        self.sourcePath = self.projectPath/self.SOURCE_DIR_NAME
        self.sphinxConfFilePath = self.sourcePath/self.CONF_FILE_NAME
        self.buildPath = self.projectPath/self.BUILDS_DIR_NAME
        self.doctreesPath = self.buildPath/self.DOCTREES_DIR_NAME
        self.outputPath = self.buildPath/self.HTML_BUILD_DIR_NAME

    def __str__(self):
        return simple_dbg(self, excludeNames=["allPaths"])

    def __repr__(self):
        return "<%s at %s>" % (self.__class__.__name__, self.projectPath)

    def create_empty_project(self):
        for thisPath in self.neededDirPaths:
            thisPath.mkdir()
        confSkelPAth = (
            self.EXAMPLE_PROJECT_PATH/self.SOURCE_DIR_NAME/self.CONF_FILE_NAME)
        plum_utils.copy(confSkelPAth, self.sphinxConfFilePath)

    @property
    def buildCommand(self):
        return cmd.sphinx_build[
            "-b", "dirhtml", "-d", self.doctreesPath._path,
            self.sourcePath._path, self.outputPath._path]

    def check_sanity(self):
        """Make sure we have a valid seeming sphinx project to work with"""
        badPaths = [path for path in self.neededPaths if not path.exists()]
        if badPaths:
            raise LoslassaError("missing paths: %s" % (badPaths))

    @cached_property
    def neededPaths(self):
        return self.neededDirPaths + [self.sphinxConfFilePath]

    @cached_property
    def neededDirPaths(self):
        return [self.projectPath, self.sourcePath]

    @cached_property
    def allPaths(self):
        return [getattr(self, p) for p in self.__dict__ if p.endswith("Path")]

class LoslassaCliApplication(cli.Application):
    PROGNAME = LOSLASSA
    VERSION = __version__
    USAGE = LOSLASSA + " [start|play|loslassa] [OPTIONS]"
    projectPath = None
    logLevel = logging.DEBUG
    logFilePath = None

    def __str__(self):
        return simple_dbg(
            self, excludeNames=["parent", "nested_command"])

    @cli.autoswitch(str)
    def project_name(self, projectName):
        """Set name (can be a relative or absolute path as well"""
        self.projectPath = LocalPath(projectName)

    @cli.autoswitch(str)
    def verbosity(self, level):
        """Adjust the talkativeness of loslassing activities.

        :param str level: log level (one of the accepted logging values)
        Levels from very chatty to almost silent: debug, info, warning, error
        """
        try:
            self.logLevel = logging.getLevelName(int(level))
        except ValueError:
            self.logLevel = level.upper()

    @cli.autoswitch(str)
    def log_to_file(self, filePath):
        """Log to a file instead of the console"""
        self.logFilePath = filePath

    def _init(self, create=False):
        if not self.projectPath:
            confPath = find_file(local.cwd, LoslassaProject.CONF_FILE_NAME)
            self.projectPath = confPath.dirname.up()
        self.project = LoslassaProject(self.projectPath)
        if create:
            self.project.create_empty_project()
        self.project.check_sanity()
        adjust_log(level=self.logLevel, filePath=self.logFilePath)
        log.info("working with project '%s'" % (self.project.projectName))
        if log.getEffectiveLevel() > logging.DEBUG:
            sys.excepthook = friendly_exception_handler

class Loslassa(LoslassaCliApplication):
    def main(self, *args):
        log.debug("executing command %s" % str(self.nested_command))
        if args:
            log.error("unknown command %r" % (args[0]))
            return 1

        if not self.nested_command:
            log.error("Which kind of loslassing? "
                      "Try %s --help" % (Loslassa.PROGNAME))
            return 1

@Loslassa.subcommand("start")
class LoslassaStart(LoslassaCliApplication):
    """Starts a new project by creating the initial project structure"""
    def main(self):
        log.info("start loslassing ...")
        if not self.projectPath:
            raise LoslassaError("please provide a name for the project")

        if self.projectPath.exists():
            # fixme Workdir.dirname as in local.cwd.dirname crashes
            # using._path in the meantime
            # see https://github.com/tomerfiliba/plumbum/issues/78
            raise LoslassaError(
                "'%s' exists already in %s, try a different name." %
                (self.projectPath.basename, self.projectPath._path))

        self._init(create=True)

@Loslassa.subcommand("play")
class LoslassaPlay(LoslassaCliApplication):
    """Start playing with the source and create your page"""
    serverPort = 8080

    @cli.autoswitch(int)
    def serve_on_port(self, serverPort):
        """Set port manually"""
        self.serverPort = serverPort

    def main(self):
        """Create the project representation and start serving"""
        self._init()
        log.info("play loslassing...")
        # fixme reloader reloads main method instead just the server!?
        serve_with_reloader(
            serveFromPath=str(self.project.outputPath),
            port=self.serverPort,
            changedCallback=self.project.buildCommand,
            pathToWatch=self.project.sourcePath)

@Loslassa.subcommand(LOSLASSA)
class LoslassaLoslassa(LoslassaCliApplication):
    """Practice loslassing by pushing your page into the interwebs"""
    def main(self):
        self._init(create=True)
        # todo make a progress bar consisting of loslassa :)
        log.info("loslassa loslassa loslassa ...")
        #raise LoslassaError("coming soon...")

def main():
    logging.basicConfig(level=logging.INFO, format='%(message)s')
    if len(sys.argv) == 1:
        log.info("no args ... using test config instead")
        # startArgs = [
        #     "start", "--verbosity", "DEBUG",
        #     "--project-name", "new"]
        playArgs = [
            "play", "--verbosity", "DEBUG",
            "--project-name", str(LoslassaProject.EXAMPLE_PROJECT_PATH)]
        sys.argv.extend(playArgs)
    Loslassa.run()

if __name__ == "__main__":
    sys.exit(main())
<|endoftext|>


<commit_before>from datetime import date, timedelta
from typing import Tuple, Iterator

class Week:
    """A Week object represents a week in epidemiological week calendar
    using CDC or ISO calculation method.
    """

    def __new__(cls, year: int, week: int, method: str = "cdc") -> "Week":
        """Construct Week object after validation"""
        _check_year(year)
        _check_method(method)
        _check_week(year, week, method)
        return super().__new__(cls)

    def __init__(self, year: int, week: int, method: str = "cdc") -> None:
        """
        :param year: epidemiological year
        :type year: int
        :param week: epidemiological week
        :type week: int
        :param method: calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        self._year = year
        self._week = week
        self._method = method

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        return "{}({}, {}, {})".format(
            class_name, self._year, self._week, self._method
        )

    def __str__(self) -> str:
        return self.isoformat()

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() == other.weektuple()

    def __gt__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() > other.weektuple()

    def __ge__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() >= other.weektuple()

    def __lt__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() < other.weektuple()

    def __le__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() <= other.weektuple()

    def __add__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        new_date = self.startdate() + timedelta(weeks=other)
        year, month, day = new_date.timetuple()[:3]
        return Week.fromdate(year, month, day, self._method)

    def __sub__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        return self + (-other)

    def __contains__(self, other: date) -> bool:
        if not isinstance(other, date):
            raise TypeError("tested operand must be 'date' object")
        return other in self.iterdates()

    @classmethod
    def fromdate(
        cls, year: int, month: int, day: int, method: str = "cdc"
    ) -> "Week":
        """Construct Week object from a Gregorian date (year, month and day).

        :param year: Gregorian year
        :type year: int
        :param month: Gregorian month
        :type month: int
        :param day: Gregorian day
        :type day: int
        :param method: calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        _check_method(method)
        date_ordinal = date(year, month, day).toordinal()
        year_start_ordinal = _year_start(year, method)
        week = (date_ordinal - year_start_ordinal) // 7
        if week < 0:
            year -= 1
            year_start_ordinal = _year_start(year, method)
            week = (date_ordinal - year_start_ordinal) // 7
        elif week >= 52:
            year_start_ordinal = _year_start(year + 1, method)
            if date_ordinal >= year_start_ordinal:
                year += 1
                week = 0
        week += 1
        return _ValidatedWeek(year, week, method)

    @classmethod
    def thisweek(cls, method: str = "cdc") -> "Week":
        """Construct Week object from current Gregorian date.

        :param method: calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        year, month, day = date.today().timetuple()[:3]
        return cls.fromdate(year, month, day, method)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def week(self) -> int:
        """Return week number as an integer"""
        return self._week

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method

    def weektuple(self) -> Tuple[int, int]:
        """Return week as a tuple of (year, week)."""
        return self._year, self._week

    def isoformat(self) -> str:
        """Return a string representing the week in compact form of ISO format
        ‘YYYYWww’.
        """

        return "{:04}W{:02}".format(self._year, self._week)

    def startdate(self) -> date:
        """Return date for first day of week."""
        year_start_ordinal = _year_start(self._year, self._method)
        week_start_ordinal = year_start_ordinal + ((self._week - 1) * 7)
        startdate = date.fromordinal(week_start_ordinal)
        return startdate

    def enddate(self) -> date:
        """Return date for last day of week."""
        enddate = self.startdate() + timedelta(days=6)
        return enddate

    def iterdates(self) -> Iterator[date]:
        """Return an iterator that yield datetime.date objects for all days of
        week."""

        startdate = self.startdate()
        for day in range(0, 7):
            yield startdate + timedelta(days=day)

class Year:
    """A Year object represents a year in epidemiological week calendar
    using US CDC or ISO calculation method.
    """

    def __init__(self, year: int, method: str = "cdc") -> None:
        """
        :param year: epidemiological year
        :type year: int
        :param method: calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        _check_year(year)
        _check_method(method)
        self._year = year
        self._method = method

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        return "{}({}, {})".format(class_name, self._year, self._method)

    def __str__(self) -> str:
        return "{:04}".format(self._year)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method

    @property
    def totalweeks(self) -> int:
        """Return number of weeks in year as an integer"""
        return _year_total_weeks(self._year, self._method)

    def startdate(self) -> date:
        """Return date for first day of first week of year."""
        year_start_ordinal = _year_start(self._year, self._method)
        return date.fromordinal(year_start_ordinal)

    def enddate(self) -> date:
        """Return date for last day of last week of year."""
        year_end_ordinal = _year_start(self._year + 1, self._method) - 1
        return date.fromordinal(year_end_ordinal)

    def iterweeks(self) -> Iterator[Week]:
        """Return an iterator that yield Week objects for all weeks of year."""
        for week in range(1, self.totalweeks + 1):
            yield _ValidatedWeek(self._year, week, self._method)

class _ValidatedWeek(Week):
    """A Week object. An implementation to avoid unnecessary validation.
    """

    def __new__(cls, *args, **kwargs) -> "Week":
        return super(Week, cls).__new__(cls)

def _check_year(year: int) -> None:
    """Check type and value of year."""
    if not isinstance(year, int):
        raise TypeError("year must be an integer")
    if not 1 <= year <= 9999:
        raise ValueError("year must be in 1..9999")

def _check_week(year: int, week: int, method: str) -> None:
    """Check type and value of week."""
    if not isinstance(week, int):
        raise TypeError("week must be an integer")
    weeks = _year_total_weeks(year, method)
    if not 1 <= week <= weeks:
        raise ValueError("week must be in 1..{} for year".format(weeks))

def _check_method(method: str) -> None:
    """Check type and value of calculation method."""
    if not isinstance(method, str):
        raise TypeError("method must be a string")
    methods = ["cdc", "iso"]
    if method not in methods:
        raise ValueError("method must be '{}' or '{}'".format(*methods))

def _method_adjustment(method: str) -> int:
    """Return needed adjustment based on first day of week using given
    calculation method.
    """

    first_day = ("Mon", "Sun")
    if method == "cdc":
        return first_day.index("Sun")
    return first_day.index("Mon")

def _year_start(year: int, method: str) -> int:
    """Return proleptic Gregorian ordinal for first day of first week for
    given year using given calculation method.
    """

    adjustment = _method_adjustment(method)
    mid_weekday = 3 - adjustment  # Sun is 6 .. Mon is 0
    jan1 = date(year, 1, 1)
    jan1_ordinal = jan1.toordinal()
    jan1_weekday = jan1.weekday()
    week1_start_ordinal = jan1_ordinal - jan1_weekday - adjustment
    if jan1_weekday > mid_weekday:
        week1_start_ordinal += 7
    return week1_start_ordinal

def _year_total_weeks(year: int, method: str) -> int:
    """Return number of weeks in year for given year using given calculation
    method.
    """

    year_start_ordinal = _year_start(year, method)
    next_year_start_ordinal = _year_start(year + 1, method)
    weeks = (next_year_start_ordinal - year_start_ordinal) // 7
    return weeks
<commit_msg>improve docstrings<commit_after>from datetime import date, timedelta
from typing import Tuple, Iterator

class Week:
    """A Week object represents a week in epidemiological week calendar."""

    def __new__(cls, year: int, week: int, method: str = "cdc") -> "Week":
        """Construct Week object after validation"""
        _check_year(year)
        _check_method(method)
        _check_week(year, week, method)
        return super().__new__(cls)

    def __init__(self, year: int, week: int, method: str = "cdc") -> None:
        """
        :param year: Epidemiological year
        :type year: int
        :param week: Epidemiological week
        :type week: int
        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        self._year = year
        self._week = week
        self._method = method

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        return "{}({}, {}, {})".format(
            class_name, self._year, self._week, self._method
        )

    def __str__(self) -> str:
        return self.isoformat()

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() == other.weektuple()

    def __gt__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() > other.weektuple()

    def __ge__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() >= other.weektuple()

    def __lt__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() < other.weektuple()

    def __le__(self, other: object) -> bool:
        if not isinstance(other, Week):
            raise TypeError("second operand must be 'Week' object")
        return self.weektuple() <= other.weektuple()

    def __add__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        new_date = self.startdate() + timedelta(weeks=other)
        year, month, day = new_date.timetuple()[:3]
        return Week.fromdate(year, month, day, self._method)

    def __sub__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        return self + (-other)

    def __contains__(self, other: date) -> bool:
        if not isinstance(other, date):
            raise TypeError("tested operand must be 'date' object")
        return other in self.iterdates()

    @classmethod
    def fromdate(
        cls, year: int, month: int, day: int, method: str = "cdc"
    ) -> "Week":
        """Construct Week object from a Gregorian date (year, month and day).

        :param year: Gregorian year
        :type year: int
        :param month: Gregorian month
        :type month: int
        :param day: Gregorian day
        :type day: int
        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        _check_method(method)
        date_ordinal = date(year, month, day).toordinal()
        year_start_ordinal = _year_start(year, method)
        week = (date_ordinal - year_start_ordinal) // 7
        if week < 0:
            year -= 1
            year_start_ordinal = _year_start(year, method)
            week = (date_ordinal - year_start_ordinal) // 7
        elif week >= 52:
            year_start_ordinal = _year_start(year + 1, method)
            if date_ordinal >= year_start_ordinal:
                year += 1
                week = 0
        week += 1
        return _ValidatedWeek(year, week, method)

    @classmethod
    def thisweek(cls, method: str = "cdc") -> "Week":
        """Construct Week object from current Gregorian date.

        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        year, month, day = date.today().timetuple()[:3]
        return cls.fromdate(year, month, day, method)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def week(self) -> int:
        """Return week number as an integer"""
        return self._week

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method

    def weektuple(self) -> Tuple[int, int]:
        """Return week as a tuple of (year, week)."""
        return self._year, self._week

    def isoformat(self) -> str:
        """Return a string representing the week in compact form of ISO format
        ‘YYYYWww’.
        """

        return "{:04}W{:02}".format(self._year, self._week)

    def startdate(self) -> date:
        """Return date for first day of week."""
        year_start_ordinal = _year_start(self._year, self._method)
        week_start_ordinal = year_start_ordinal + ((self._week - 1) * 7)
        startdate = date.fromordinal(week_start_ordinal)
        return startdate

    def enddate(self) -> date:
        """Return date for last day of week."""
        enddate = self.startdate() + timedelta(days=6)
        return enddate

    def iterdates(self) -> Iterator[date]:
        """Return an iterator that yield datetime.date objects for all days of
        week."""

        startdate = self.startdate()
        for day in range(0, 7):
            yield startdate + timedelta(days=day)

class Year:
    """A Year object represents a year in epidemiological week calendar."""

    def __init__(self, year: int, method: str = "cdc") -> None:
        """
        :param year: Epidemiological year
        :type year: int
        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        _check_year(year)
        _check_method(method)
        self._year = year
        self._method = method

    def __repr__(self) -> str:
        class_name = self.__class__.__name__
        return "{}({}, {})".format(class_name, self._year, self._method)

    def __str__(self) -> str:
        return "{:04}".format(self._year)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method

    @property
    def totalweeks(self) -> int:
        """Return number of weeks in year as an integer"""
        return _year_total_weeks(self._year, self._method)

    def startdate(self) -> date:
        """Return date for first day of first week of year."""
        year_start_ordinal = _year_start(self._year, self._method)
        return date.fromordinal(year_start_ordinal)

    def enddate(self) -> date:
        """Return date for last day of last week of year."""
        year_end_ordinal = _year_start(self._year + 1, self._method) - 1
        return date.fromordinal(year_end_ordinal)

    def iterweeks(self) -> Iterator[Week]:
        """Return an iterator that yield Week objects for all weeks of year."""
        for week in range(1, self.totalweeks + 1):
            yield _ValidatedWeek(self._year, week, self._method)

class _ValidatedWeek(Week):
    """A Week object. An implementation to avoid unnecessary validation."""

    def __new__(cls, *args, **kwargs) -> "Week":
        """Construct Week object without validation"""
        return super(Week, cls).__new__(cls)

def _check_year(year: int) -> None:
    """Check type and value of year."""
    if not isinstance(year, int):
        raise TypeError("year must be an integer")
    if not 1 <= year <= 9999:
        raise ValueError("year must be in 1..9999")

def _check_week(year: int, week: int, method: str) -> None:
    """Check type and value of week."""
    if not isinstance(week, int):
        raise TypeError("week must be an integer")
    weeks = _year_total_weeks(year, method)
    if not 1 <= week <= weeks:
        raise ValueError("week must be in 1..{} for year".format(weeks))

def _check_method(method: str) -> None:
    """Check type and value of calculation method."""
    if not isinstance(method, str):
        raise TypeError("method must be a string")
    methods = ["cdc", "iso"]
    if method not in methods:
        raise ValueError("method must be '{}' or '{}'".format(*methods))

def _method_adjustment(method: str) -> int:
    """Return needed adjustment based on first day of week."""
    first_day = ("Mon", "Sun")
    if method == "cdc":
        return first_day.index("Sun")
    return first_day.index("Mon")

def _year_start(year: int, method: str) -> int:
    """Return Gregorian ordinal for first day of first week for year."""
    adjustment = _method_adjustment(method)
    mid_weekday = 3 - adjustment  # Sun is 6 .. Mon is 0
    jan1 = date(year, 1, 1)
    jan1_ordinal = jan1.toordinal()
    jan1_weekday = jan1.weekday()
    week1_start_ordinal = jan1_ordinal - jan1_weekday - adjustment
    if jan1_weekday > mid_weekday:
        week1_start_ordinal += 7
    return week1_start_ordinal

def _year_total_weeks(year: int, method: str) -> int:
    """Return number of weeks in year."""
    year_start_ordinal = _year_start(year, method)
    next_year_start_ordinal = _year_start(year + 1, method)
    weeks = (next_year_start_ordinal - year_start_ordinal) // 7
    return weeks
<|endoftext|>


<commit_before>"""Retrieving data from Eurostat"""
from skeleton import Skeleton
import lxml.etree
import urllib
from pandas.tseries.offsets import *
import pandas
import datetime
from io import BytesIO, StringIO, TextIOWrapper
import urllib.request
import gzip
import uuid
import re
import pymongo
import logging

class Eurostat(Skeleton):
    """Eurostat statistical provider"""
    def __init__(self):
        super(Eurostat, self).__init__()
        self.lgr = logging.getLogger('Eurostat')
        self.lgr.setLevel(logging.DEBUG)
        self.fh = logging.FileHandler('Eurostat.log')
        self.fh.setLevel(logging.DEBUG)
        self.frmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        self.fh.setFormatter(self.frmt)
        self.lgr.addHandler(self.fh)
        self.db = self.client.eurostat
        webpage = urllib.request.urlopen(
            'http://www.epp.eurostat.ec.europa.eu/'
            'NavTree_prod/everybody/BulkDownloadListing'
            '?sort=1&file=table_of_contents.xml', timeout=7)
        table_of_contents = webpage.read()
        self.table_of_contents = lxml.etree.fromstring(table_of_contents)
        self.store_path = '/mnt/data/dlstats/'
    def update_categories_db(self):
        """Update the categories in MongoDB
        """

        id_journal = self.db.journal.insert({'name': 'categories'})
        def walktree(branch, parent_id):
            """Recursive function for parsing table_of_contents.xml

            :param branch: The current branch explored. The function
            is going top to bottom.
            :type branch ElementTree
            :param parent_id: The id of the previous branch
            :type branch MongoObject(Id)
            """
            for children in branch.iterchildren(
                '{urn:eu.europa.ec.eurostat.navtree}children'):
                for branch in children.iterchildren(
                    '{urn:eu.europa.ec.eurostat.navtree}branch'):
                    for title in branch.iterchildren(
                        '{urn:eu.europa.ec.eurostat.navtree}title'):
                        if title.attrib.values()[0] == 'en':
                            node = {'name': title.text,
                                    'id_journal': id_journal}
                            _id = self.db.categories.insert(node)
                            self.db.categories.update(
                                {'_id': parent_id},
                                {'$push': {'children': _id}})
                    walktree(branch, _id)
                for leaf in children.iterchildren(
                    '{urn:eu.europa.ec.eurostat.navtree}leaf'):
                    for title in leaf.iterchildren(
                        '{urn:eu.europa.ec.eurostat.navtree}title'):
                        if title.attrib.values()[0] == 'en':
                            node = {'name': title.text,
                                    'id_journal': id_journal}
                            _id = self.db.categories.insert(node)
                            self.db.categories.update(
                                {'_id': parent_id},
                                {'$push': {'children': _id}})
                    for download_link in leaf.iterchildren(
                        '{urn:eu.europa.ec.eurostat.navtree}downloadLink'):
                        if download_link.attrib.values()[0] == 'tsv':
                            _url = download_link.text
                            self.db.categories.update(
                                {'_id': _id},
                                {'$push': {'url': _url }})

        root = self.table_of_contents.iterfind(
            '{urn:eu.europa.ec.eurostat.navtree}branch')
        for branch in root:
            for title in branch.iterchildren(
                '{urn:eu.europa.ec.eurostat.navtree}title'):
                if title.attrib.values()[0] == 'en':
                    node = {'name': title.text, 'id_journal': id_journal}
                    _id = self.db.categories.insert(node)
            walktree(branch, _id)

    def leaf_to_pandas(self, url_leaf):
        """Download series contained in a leaf and returns a list of pandas
        DataFrame"""
        def _date_parser(date):
            regex_quarterly = re.compile('[0-9]{4}Q[1-4]')
            regex_monthly = re.compile('[0-9]{4}M[0-9]{2}')
            if re.search(regex_quarterly, date):
                date = date.split('Q')
                date = str(int(date[1])*3) + date[0]
                return datetime.datetime.strptime(date, '%m%Y')
            if re.search(regex_monthly, date):
                return datetime.datetime.strptime(date, '%YM%m')
        response = urllib.request.urlopen(url_leaf)
        memzip = BytesIO(response.read())
        archive = gzip.open(memzip,'rb')
        tsv = TextIOWrapper(archive, encoding='utf-8', newline='')
        split_tsv = [line.strip() for line in tsv]
        header = split_tsv[0].split()
        cols_labels = header[0].split(',')
        dates = [_date_parser(date_string)
                 for date_string in header[1:]]
        series_from_leaf = []
        for line in split_tsv[1:]:
            metadata = {}
            i = 0
            for label in cols_labels:
                value = line.split(',')[i].split('\t')[0]
                label = label.split('\\')
                metadata[label[0]] = value
                i += 1
            identifier = 'id'+str(uuid.uuid4()).replace('-','')
            values = line.split('\t')[1:]
            values = [re.sub(r'[a-z]','',value).strip() for value in values]
            series = pandas.DataFrame(
                {identifier: values}, index=dates).replace(
                    ':', pandas.np.nan).replace(
                        ': ', pandas.np.nan).astype('float')
            series_from_leaf.append([identifier, metadata, series])
        return series_from_leaf

    def update_series_db(self):
        """Update the series in MongoDB
        """
        id_journal = self.db.journal.insert({'name': 'series'})
        file_identifier = id_journal.__repr__().split('\'')[1]
        store = pandas.HDFStore(
            self.store_path + 'eurostat' + file_identifier + '.h5',
            complevel=9, complib='zlib')
        last_update_categories = list(self.db.journal.find(
            {'name': 'categories'}).sort([('_id',-1)]).limit(1))
        leaves = list(self.db.categories.find({
            'id_journal': last_update_categories[0]['_id'],
            'url': {'$exists': 'true'}}))
        series = []
        for leaf in leaves:
            series = self.leaf_to_pandas(leaf['url'][0])
            for a_series in series:
                a_series[1]['hdfpath'] = 'id'+str(uuid.uuid4()).replace('-','')
                self.db.series.insert({'uuid': a_series[0],'metadata': a_series[1]})
                store[a_series[1]['hdfpath']+'/'+a_series[0]] = a_series[2]
        store.close()

<commit_msg>MongoDB transition. Must switch to reST<commit_after>"""Retrieving data from Eurostat"""
from skeleton import Skeleton
import lxml.etree
import urllib
from pandas.tseries.offsets import *
import pandas
import datetime
from io import BytesIO, StringIO, TextIOWrapper
import urllib.request
import gzip
import re
import pymongo
import logging
from multiprocessing import Pool

def insert_series(leaf):
    try:
        client = pymongo.MongoClient()
        db = client.eurostat
        def _date_parser(date):
            regex_annually = re.compile('[0-9]{4}')
            regex_quarterly = re.compile('[0-9]{4}Q[1-4]')
            regex_monthly = re.compile('[0-9]{4}M[0-9]{2}')
            if re.search(regex_quarterly, date):
                date = date.split('Q')
                date = str(int(date[1])*3) + date[0]
                return datetime.datetime.strptime(date, '%m%Y')
            if re.search(regex_monthly, date):
                return datetime.datetime.strptime(date, '%YM%m')
            if re.search(regex_annually, date):
                return datetime.datetime.strptime(date, '%Y')
        response = urllib.request.urlopen(leaf['url'][0])
        memzip = BytesIO(response.read())
        archive = gzip.open(memzip,'rb')
        tsv = TextIOWrapper(archive, encoding='utf-8', newline='')
        split_tsv = [line.strip() for line in tsv]
        header = split_tsv[0].split()
        cols_labels = header[0].split(',')
        if cols_labels[-1] == 'time\\geo':
            return False
        dates = [_date_parser(date_string)
                 for date_string in header[1:]]
        if type(dates[0]) is not datetime.datetime:
            return False
        series_from_leaf = []
        for line in split_tsv[1:]:
            metadata = {}
            i = 0
            for label in cols_labels:
                value = line.split(',')[i].split('\t')[0]
                label = label.split('\\')
                metadata[label[0]] = value
                i += 1
            values = line.split('\t')[1:]
            values = [re.sub(r'[a-z]','',value).strip() for value in values]
            series = pandas.DataFrame(values, index=dates).replace(
                    ':', pandas.np.nan).replace(
                        ': ', pandas.np.nan).astype('float')
            series_from_leaf.append([metadata,
                                     [value[0] for value in series.values],
                                     [value for value in series.index.astype(object)]])
        for a_series in series_from_leaf:
            id_series = db.series.insert({'metadata': a_series[0],
                                  'data': {'value': a_series[1],
                                            'date': a_series[2]}})
            db.categories.update({'_id': leaf['_id']},
                                  {'$push': {'id_series': id_series}})

        return True
    except:
        return False

class Eurostat(Skeleton):
    """Eurostat statistical provider"""
    def __init__(self):
        self.client = pymongo.MongoClient()
        super(Eurostat, self).__init__()
        self.lgr = logging.getLogger('Eurostat')
        self.lgr.setLevel(logging.DEBUG)
        self.fh = logging.FileHandler('Eurostat.log')
        self.fh.setLevel(logging.DEBUG)
        self.frmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        self.fh.setFormatter(self.frmt)
        self.lgr.addHandler(self.fh)
        self.db = self.client.eurostat
        webpage = urllib.request.urlopen(
            'http://www.epp.eurostat.ec.europa.eu/'
            'NavTree_prod/everybody/BulkDownloadListing'
            '?sort=1&file=table_of_contents.xml', timeout=7)
        table_of_contents = webpage.read()
        self.table_of_contents = lxml.etree.fromstring(table_of_contents)
        self.store_path = '/mnt/data/dlstats/'
    def update_categories_db(self):
        """Update the categories in MongoDB
        """

        id_journal = self.db.journal.insert({'name': 'categories'})
        def walktree(branch, parent_id):
            """Recursive function for parsing table_of_contents.xml

            :param branch: The current branch explored. The function
            is going top to bottom.
            :type branch ElementTree
            :param parent_id: The id of the previous branch
            :type branch MongoObject(Id)
            """
            for children in branch.iterchildren(
                '{urn:eu.europa.ec.eurostat.navtree}children'):
                for branch in children.iterchildren(
                    '{urn:eu.europa.ec.eurostat.navtree}branch'):
                    for title in branch.iterchildren(
                        '{urn:eu.europa.ec.eurostat.navtree}title'):
                        if title.attrib.values()[0] == 'en':
                            node = {'name': title.text,
                                    'id_journal': id_journal}
                            _id = self.db.categories.insert(node)
                            self.db.categories.update(
                                {'_id': parent_id},
                                {'$push': {'children': _id}})
                    walktree(branch, _id)
                for leaf in children.iterchildren(
                    '{urn:eu.europa.ec.eurostat.navtree}leaf'):
                    for title in leaf.iterchildren(
                        '{urn:eu.europa.ec.eurostat.navtree}title'):
                        if title.attrib.values()[0] == 'en':
                            node = {'name': title.text,
                                    'id_journal': id_journal}
                            _id = self.db.categories.insert(node)
                            self.db.categories.update(
                                {'_id': parent_id},
                                {'$push': {'children': _id}})
                    for download_link in leaf.iterchildren(
                        '{urn:eu.europa.ec.eurostat.navtree}downloadLink'):
                        if download_link.attrib.values()[0] == 'tsv':
                            _url = download_link.text
                            self.db.categories.update(
                                {'_id': _id},
                                {'$push': {'url': _url }})

        root = self.table_of_contents.iterfind(
            '{urn:eu.europa.ec.eurostat.navtree}branch')
        for branch in root:
            for title in branch.iterchildren(
                '{urn:eu.europa.ec.eurostat.navtree}title'):
                if title.attrib.values()[0] == 'en':
                    node = {'name': title.text, 'id_journal': id_journal}
                    _id = self.db.categories.insert(node)
            walktree(branch, _id)

    def update_series_db(self):
        """Update the series in MongoDB
        """
        id_journal = self.db.journal.insert({'name': 'series'})
        last_update_categories = list(self.db.journal.find(
            {'name': 'categories'}).sort([('_id',-1)]).limit(1))
        leaves = list(self.db.categories.find({
            'id_journal': last_update_categories[0]['_id'],
            'url': {'$exists': 'true'}}))
        series = []
        pool = Pool(8)
        i=0
        validation = []
        for dummy in pool.map(insert_series,leaves):
            i+=1
            validation.append(dummy)
            self.lgr.info(str(i)+'/'+str(len(leaves)))
        print(str(sum(validation)) + ' groups of series retrieved over a total of ' + str(len(validation)))
        pool.close()
        pool.join()
<|endoftext|>


<commit_before>"""
A robot arm
"""
import numpy as np
import utils

class Arm:

    def __init__(self, links, q0, name, base=None, tool=None):
        """
        :param links: Vector of Link objects (1xN numpy vector)
        :param q0: The default (resting state) joint configuration
                   (1xN numpy vector)
        :param name: Name of the arm
        :param base: Base position of the arm
        """
        self.num_links = links.size
        self.links = links
        self.q0 = q0
        self.name = name

        if base is None:
            self.base = np.identity(4)
        else:
            self.base = base

        if tool is None:
            self.tool = np.identity(4)
        else:
            self.tool = tool

        # Set the arm to its default position
        self.reset()
        self.update_link_positions(q0)

    def update_link_angle(self, link, new_angle):
        """
        Updates the given link's angle with the given angle
        :param link: The link you want to update, given as a integer
        :param new_angle: The link's new angle
        """
        self.links[link].theta = new_angle
        self.update_link_positions(new_angle)

    def get_current_joint_config(self):
        """
        Gets the current joint configuration from the links
        """
        q = np.zeros(self.num_links)
        for i, link in enumerate(self.links):
            q[i] = link.theta
        return q

    def reset(self):
        """
        Resets the arm back to its resting state, i.e. q0
        """
        for link, q in zip(self.links, self.q0):
            link.set_theta(q)

    def fkine(self, q=None, links=None):
        """
        Computes the forward kinematics of the arm using the current joint
        configuration or a given joint configuration
        :param q: (Optional) joint configuration to compute the FK on
                  (1xN numpy vector)
        :param links: (Optional) Specify which links to run fkine on
        """
        if links is None:
            links = range(self.num_links)

        t = self.base
        for i, link in zip(links, self.links):
            if np.any(q):
                t = t * link.compute_transformation_matrix(q[i])
            else:
                t = t * link.transform_matrix
        t = t * self.tool
        return t

    def ikine(self, p, num_iterations=1000, alpha=0.1):
        """
        Computes the inverse kinematics to find the correct joint
        configuration to reach a given point
        :param p: The point we want to solve the inverse kinematics for
        :param num_iterations: The number of iterations to try before
                               giving up
        :param alpha: The stepsize for the ikine solver
        """
        q = self.get_current_joint_config()
        goal = utils.create_homogeneous_transform_from_point(p)
        for i in xrange(num_iterations):
            # Calculate position error of the end effector
            curr = self.fkine(q)
            err = goal - curr

            # Convert error from homogeneous to xyz space
            err = utils.create_point_from_homogeneous_transform(err)

            # Get the psudoinverse of the Jacobian
            J = self.jacob0(q)
            vel_J = J[0:3, :]

            # Increment q a tiny bit
            delta_q = np.linalg.pinv(vel_J) * err
            delta_q = np.squeeze(np.asarray(delta_q))
            q = q + (alpha * delta_q.flatten())

            if abs(np.linalg.norm(err)) <= 1e-6:
                return q
        raise ValueError("Could not find solution.")

    def jacob0(self, q=None):
        """
        Calculates the jacobian in the world frame by finding it in
        the tool frame and then converting to the world frame
        :param q: Optional joint configuration to compute the jacobian on
                  (1xN numpy vector)
        """
        J = self.jacobn(q)
        eet = self.fkine(q)
        rotation = utils.get_rotation_from_homogeneous_transform(eet)
        zeros = np.zeros((3, 3))
        a1 = np.hstack((rotation, zeros))
        a2 = np.hstack((zeros, rotation))
        J = np.vstack((a1, a2)) * J
        return J

    def jacobn(self, q=None):
        """
        Calculates the jacobian in the tool frame
        :param q: Optional joint configuration to compute the jacobian on
                  (1xN numpy vector)
        """
        J = np.zeros((6, self.num_links))
        U = self.tool
        I = range(self.num_links - 1, -1, -1)
        for i, link in zip(I, self.links[::-1]):
            if np.any(q):
                U = link.compute_transformation_matrix(q[i]) * U
            else:
                U = link.transform_matrix * U

            d = np.array([-U[0, 0] * U[1, 3] + U[1, 0] * U[0, 3],
                          -U[0, 1] * U[1, 3] + U[1, 1] * U[0, 3],
                          -U[0, 2] * U[1, 3] + U[1, 2] * U[0, 3]])
            delta = U[2, 0:3]

            J[:, i] = np.vstack((d, delta)).flatten()
        return J

    def plot(self, ax):
        """Plot our robot
        :param ax: axes of plot
        """
        for link in self.links:
            link.plot(ax)

    def update_link_positions(self, q_new):
        """
        Walk through all the links and update their positions.
        :param q_new: New joint config
        """

        for i, link in enumerate(self.links):
            if i == 0:
                link.base_pos = utils.create_point_from_homogeneous_transform(self.base)
            else:
                link.base_pos = self.links[i - 1].end_pos

            if link.length == 0:
                link.end_pos = link.base_pos
            else:
                # Compute FKine up to that link endpoint
                # to get the location in homogenous coords
                t = self.fkine(q=q_new, links=range(i + 1))
                # Then convert that to world space
                end_pos = utils.create_point_from_homogeneous_transform(t).T
                link.end_pos = end_pos.A1

    def end_effector_position(self):
        """Return end effector position"""
        return self.links[-1].end_pos
<commit_msg>Fixed base bug to transform base cords in to homogeneous cords<commit_after>"""
A robot arm
"""
import numpy as np
import utils

class Arm:

    def __init__(self, links, q0, name, base=None, tool=None):
        """
        :param links: Vector of Link objects (1xN numpy vector)
        :param q0: The default (resting state) joint configuration
                   (1xN numpy vector)
        :param name: Name of the arm
        :param base: Base position of the arm
        """
        self.num_links = links.size
        self.links = links
        self.q0 = q0
        self.name = name

        if base is None:
            self.base = np.identity(4)
        else:
            self.base = utils.create_homogeneous_transform_from_point(base)

        if tool is None:
            self.tool = np.identity(4)
        else:
            self.tool = tool

        # Set the arm to its default position
        self.reset()
        self.update_link_positions(q0)

    def update_link_angle(self, link, new_angle):
        """
        Updates the given link's angle with the given angle
        :param link: The link you want to update, given as a integer
        :param new_angle: The link's new angle
        """
        self.links[link].theta = new_angle
        self.update_link_positions(new_angle)

    def get_current_joint_config(self):
        """
        Gets the current joint configuration from the links
        """
        q = np.zeros(self.num_links)
        for i, link in enumerate(self.links):
            q[i] = link.theta
        return q

    def reset(self):
        """
        Resets the arm back to its resting state, i.e. q0
        """
        for link, q in zip(self.links, self.q0):
            link.set_theta(q)

    def fkine(self, q=None, links=None):
        """
        Computes the forward kinematics of the arm using the current joint
        configuration or a given joint configuration
        :param q: (Optional) joint configuration to compute the FK on
                  (1xN numpy vector)
        :param links: (Optional) Specify which links to run fkine on
        """
        if links is None:
            links = range(self.num_links)

        t = self.base
        for i, link in zip(links, self.links):
            if np.any(q):
                t = t * link.compute_transformation_matrix(q[i])
            else:
                t = t * link.transform_matrix
        t = t * self.tool
        return t

    def ikine(self, p, num_iterations=1000, alpha=0.1):
        """
        Computes the inverse kinematics to find the correct joint
        configuration to reach a given point
        :param p: The point we want to solve the inverse kinematics for
        :param num_iterations: The number of iterations to try before
                               giving up
        :param alpha: The stepsize for the ikine solver
        """
        q = self.get_current_joint_config()
        goal = utils.create_homogeneous_transform_from_point(p)
        for i in xrange(num_iterations):
            # Calculate position error of the end effector
            curr = self.fkine(q)
            err = goal - curr

            # Convert error from homogeneous to xyz space
            err = utils.create_point_from_homogeneous_transform(err)

            # Get the psudoinverse of the Jacobian
            J = self.jacob0(q)
            vel_J = J[0:3, :]

            # Increment q a tiny bit
            delta_q = np.linalg.pinv(vel_J) * err
            delta_q = np.squeeze(np.asarray(delta_q))
            q = q + (alpha * delta_q.flatten())

            if abs(np.linalg.norm(err)) <= 1e-6:
                return q
        raise ValueError("Could not find solution.")

    def jacob0(self, q=None):
        """
        Calculates the jacobian in the world frame by finding it in
        the tool frame and then converting to the world frame
        :param q: Optional joint configuration to compute the jacobian on
                  (1xN numpy vector)
        """
        J = self.jacobn(q)
        eet = self.fkine(q)
        rotation = utils.get_rotation_from_homogeneous_transform(eet)
        zeros = np.zeros((3, 3))
        a1 = np.hstack((rotation, zeros))
        a2 = np.hstack((zeros, rotation))
        J = np.vstack((a1, a2)) * J
        return J

    def jacobn(self, q=None):
        """
        Calculates the jacobian in the tool frame
        :param q: Optional joint configuration to compute the jacobian on
                  (1xN numpy vector)
        """
        J = np.zeros((6, self.num_links))
        U = self.tool
        I = range(self.num_links - 1, -1, -1)
        for i, link in zip(I, self.links[::-1]):
            if np.any(q):
                U = link.compute_transformation_matrix(q[i]) * U
            else:
                U = link.transform_matrix * U

            d = np.array([-U[0, 0] * U[1, 3] + U[1, 0] * U[0, 3],
                          -U[0, 1] * U[1, 3] + U[1, 1] * U[0, 3],
                          -U[0, 2] * U[1, 3] + U[1, 2] * U[0, 3]])
            delta = U[2, 0:3]

            J[:, i] = np.vstack((d, delta)).flatten()
        return J

    def plot(self, ax):
        """Plot our robot
        :param ax: axes of plot
        """
        for link in self.links:
            link.plot(ax)

    def update_link_positions(self, q_new):
        """
        Walk through all the links and update their positions.
        :param q_new: New joint config
        """

        for i, link in enumerate(self.links):
            if i == 0:
                link.base_pos = utils.create_point_from_homogeneous_transform(self.base)
            else:
                link.base_pos = self.links[i - 1].end_pos

            if link.length == 0:
                link.end_pos = link.base_pos
            else:
                # Compute FKine up to that link endpoint
                # to get the location in homogenous coords
                t = self.fkine(q=q_new, links=range(i + 1))
                # Then convert that to world space
                end_pos = utils.create_point_from_homogeneous_transform(t).T
                link.end_pos = end_pos.A1

    def end_effector_position(self):
        """Return end effector position"""
        return self.links[-1].end_pos
<|endoftext|>


<commit_before>import mailpile.app
import mailpile.commands
import mailpile.ui

# Load the standard plugins
from mailpile.plugins import *

__all__ = ['Mailpile',
           "app", "commands", "plugins", "mailutils", "search", "ui", "util"]

class Mailpile(object):
  """This object provides a simple Python API to Mailpile."""

  def __init__(self, ui=mailpile.ui.UserInteraction, workdir=None):
    self._config = mailpile.app.ConfigManager(workdir=workdir)
    self._session = mailpile.ui.Session(self._config)
    self._session.config.load(self._session)
    self._session.main = True
    self._ui = self._session.ui = ui()
    for (cmd, cls) in mailpile.commands.COMMANDS.values():
      cmd, fnc = self._mk_action(cmd)
      if cls.SYNOPSIS:
        fnc.__doc__ = '%s(%s)  # %s' % (cmd, cls.SYNOPSIS, cls.__doc__)
      else:
        fnc.__doc__ = '%s()  # %s' % (cmd, cls.__doc__)
      setattr(self, cmd.replace('/', '_'), fnc)

  def _mk_action(self, cmd):
    if cmd.endswith('='):
      cmd = cmd[:-1]
      def fnc(*args):
        return mailpile.commands.Action(self._session, cmd, args)
      return cmd, fnc
    else:
      def fnc():
        return mailpile.commands.Action(self._session, cmd, '')
      return cmd, fnc

<commit_msg>Add Interact() and PEP8<commit_after>import mailpile.app
import mailpile.commands
import mailpile.ui

# Load the standard plugins
from mailpile.plugins import *

__all__ = ['Mailpile',
           "app", "commands", "plugins", "mailutils", "search", "ui", "util"]

class Mailpile(object):
    """This object provides a simple Python API to Mailpile."""

    def __init__(self, ui=mailpile.ui.UserInteraction, workdir=None):
        self._config = mailpile.app.ConfigManager(workdir=workdir)
        self._session = mailpile.ui.Session(self._config)
        self._session.config.load(self._session)
        self._session.main = True
        self._ui = self._session.ui = ui()
        for (cmd, cls) in mailpile.commands.COMMANDS.values():
            cmd, fnc = self._mk_action(cmd)
            if cls.SYNOPSIS:
                fnc.__doc__ = '%s(%s)  # %s' % (cmd, cls.SYNOPSIS, cls.__doc__)
            else:
                fnc.__doc__ = '%s()  # %s' % (cmd, cls.__doc__)
            setattr(self, cmd.replace('/', '_'), fnc)

    def _mk_action(self, cmd):
        if cmd.endswith('='):
            cmd = cmd[:-1]

            def fnc(*args):
                return mailpile.commands.Action(self._session, cmd, args)
        else:

            def fnc():
                return mailpile.commands.Action(self._session, cmd, '')
        return cmd, fnc

    def Interact(self):
        return mailpile.app.Interact(self._session)
<|endoftext|>