<commit_before># OpenStackCharm() - base class for build OpenStack charms from for the
# reactive framework.

# need/want absolute imports for the package imports to work properly
from __future__ import absolute_import

import os
import subprocess
import contextlib
import collections

import six

import charmhelpers.contrib.openstack.templating as os_templating
import charmhelpers.contrib.openstack.utils as os_utils
import charmhelpers.core.hookenv as hookenv
import charmhelpers.core.host as ch_host
import charmhelpers.core.templating
import charmhelpers.fetch
import charms.reactive.bus

import charms_openstack.ip as os_ip

# _releases{} is a dictionary of release -> class that is instantiated
# according to the the release that is being requested.  i.e. a charm can
# handle more than one release.  The OpenStackCharm() derived class sets the
# `release` variable to indicate which release that the charm supports.
# Any subsequent releases that need a different/specialised charm uses the
# `release` class property to indicate that it handles that release onwards.
_releases = {}

# `_singleton` stores the instance of the class that is being used during a
# hook invocation.
_singleton = None

# List of releases that OpenStackCharm based charms know about
KNOWN_RELEASES = [
    'diablo',
    'essex',
    'folsom',
    'grizzly',
    'havana',
    'icehouse',
    'juno',
    'kilo',
    'liberty',
    'mitaka',
]

def get_charm_instance(release=None, *args, **kwargs):
    """Get an instance of the charm based on the release (or use the
    default if release is None).

    OS releases are in alphabetical order, so it looks for the first release
    that is provided if release is None, otherwise it finds the release that is
    before or equal to the release passed.

    Note that it passes args and kwargs to the class __init__() method.

    :param release: lc string representing release wanted.
    :returns: OpenStackCharm() derived class according to cls.releases
    """
    if len(_releases.keys()) == 0:
        raise RuntimeError("No derived OpenStackCharm() classes registered")
    # Note that this relies on OS releases being in alphabetica order
    known_releases = sorted(_releases.keys())
    cls = None
    if release is None:
        # take the latest version of the charm if no release is passed.
        cls = _releases[known_releases[-1]]
    elif release < known_releases[0]:
        raise RuntimeError(
            "Release {} is not supported by this charm. Earliest support is "
            "{} release".format(release, known_releases[0]))
    else:
        # try to find the release that is supported.
        for known_release in reversed(known_releases):
            if release >= known_release:
                cls = _releases[known_release]
                break
    if cls is None:
        raise RuntimeError("Release {} is not supported".format(release))
    return cls(release=release, *args, **kwargs)

class OpenStackCharmMeta(type):
    """Metaclass to provide a classproperty of 'singleton' so that class
    methods in the derived OpenStackCharm() class can simply use cls.singleton
    to get the instance of the charm.

    Thus cls.singleton is a singleton for accessing and creating the default
    OpenStackCharm() derived class.  This is to avoid a lot of boilerplate in
    the classmethods for the charm code.  This is because, usually, a
    classmethod is only called once per invocation of the script.

    Thus in the derived charm code we can do this:

        cls.singleton.instance_method(...)

    and this will instatiate the charm and call instance_method() on it.

    Note that self.singleton is also defined as a property for completeness so
    that cls.singleton and self.singleton give consistent results.
    """

    def __init__(cls, name, mro, members):
        """Receive the OpenStackCharm() (derived) class and store the release
        that it works against.  Each class defines a 'release' that it handles
        and the order of releases (as given in charmhelpers) determines (for
        any release) which OpenStackCharm() derived class is the handler for
        that class.  Note, that if the `name` is 'OpenStackCharm' then the
        function ignores the release, etc.

        :param name: string for class name.
        :param mro: tuple of base classes.
        :param members: dictionary of name to class attribute (f, p, a, etc.)
        """
        global _releases
        if name == 'OpenStackCharm':
            return
        if 'release' in members.keys():
            release = members['release']
            if release not in KNOWN_RELEASES:
                raise RuntimeError(
                    "Release {} is not a known OpenStack release"
                    .format(release))
            if release in _releases.keys():
                raise RuntimeError(
                    "Release {} defined more than once in classes {} and {} "
                    " (at least)"
                    .format(release, _releases[release].__name__, name))
            # store the class against the release.
            _releases[release] = cls
        else:
            raise RuntimeError(
                "class '{}' does not define a release that it supports. "
                "Please use the 'release' class property to define the "
                "release.".format(name))

    @property
    def singleton(cls):
        global _singleton
        if _singleton is None:
            _singleton = get_charm_instance()
        return _singleton

@six.add_metaclass(OpenStackCharmMeta)
class OpenStackCharm(object):
    """
    Base class for all OpenStack Charm classes;
    encapulates general OpenStack charm payload operations

    Theory:
    Derive form this class, set the name, first_release and releases class
    variables so that get_charm_instance() will create an instance of this
    charm.

    See the other class variables for details on what they are for and do.
    """

    # first_release = this is the first release in which this charm works
    release = 'icehouse'

    # The name of the charm (for printing, etc.)
    name = 'charmname'

    # List of packages to install
    packages = []

    # Dictionary mapping services to ports for public, admin and
    # internal endpoints
    api_ports = {}

    # Keystone endpoint type
    service_type = None

    # Default service for the charm
    default_service = None

    # A dictionary of:
    # {
    #    'config.file': ['list', 'of', 'services', 'to', 'restart'],
    #    'config2.file': ['more', 'services'],
    # }
    restart_map = {}

    # The command used to sync the database
    sync_cmd = []

    # The list of services that this charm manages
    services = []

    # The adapters class that this charm uses to adapt interfaces.
    adapters_class = None

    ha_resources = []
    adapters_class = None
    HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'

    @property
    def singleton(self):
        """Return the only instance of the charm class in this run"""
        # Note refers back to the Metaclass property for this charm.
        return self.__class__.singleton

    def __init__(self, interfaces=None, config=None, release=None):
        """Instantiate an instance of the class.

        Sets up self.config and self.adapter_instance if cls.adapters_class and
        interfaces has been set.

        :param interfaces: list of interface instances for the charm.
        :param config: the config for the charm (optionally None for
        automatically using config())
        """
        self.config = config or hookenv.config()
        self.release = release
        self.adapters_instance = None
        if interfaces and self.adapters_class:
            self.adapters_instance = self.adapters_class(interfaces)
        self.set_haproxy_stat_password()

    def all_packages(self):
        """List of packages to be installed

        @return ['pkg1', 'pkg2', ...]
        """
        _packages = []
        _packages.extend(self.packages)
        if self.enable_haproxy():
            _packages.append('haproxy')
        return _packages

    def full_restart_map(self):
        """Map of services to be restarted if a file changes

        @return {
                    'file1': ['svc1', 'svc3'],
                    'file2': ['svc2', 'svc3'],
                    ...
                }
        """
        _restart_map = self.restart_map.copy()
        if self.enable_haproxy():
            _restart_map[self.HAPROXY_CONF] = ['haproxy']
        return _restart_map

    def install(self):
        """Install packages related to this charm based on
        contents of self.packages attribute.
        """
        packages = charmhelpers.fetch.filter_installed_packages(
            self.all_packages())
        if packages:
            hookenv.status_set('maintenance', 'Installing packages')
            charmhelpers.fetch.apt_install(packages, fatal=True)
            # TODO need a call to assess_status(...) or equivalent so that we
            # can determine the workload status at the end of the handler.  At
            # the end of install the 'status' is stuck in maintenance until the
            # next hook is run.
        self.set_state('{}-installed'.format(self.name))

    def set_state(self, state, value=None):
        """proxy for charms.reactive.bus.set_state()"""
        charms.reactive.bus.set_state(state, value)

    def remove_state(self, state):
        """proxy for charms.reactive.bus.remove_state()"""
        charms.reactive.bus.remove_state(state)

    def api_port(self, service, endpoint_type=os_ip.PUBLIC):
        """Return the API port for a particular endpoint type from the
        self.api_ports{}.

        :param service: string for service name
        :param endpoing_type: one of charm.openstack.ip.PUBLIC| INTERNAL| ADMIN
        :returns: port (int)
        """
        return self.api_ports[service][endpoint_type]

    def configure_source(self):
        """Configure installation source using the config item
        'openstack-origin'

        This configures the installation source for deb packages and then
        updates the packages list on the unit.
        """
        os_utils.configure_installation_source(self.config['openstack-origin'])
        charmhelpers.fetch.apt_update(fatal=True)

    @property
    def region(self):
        """Return the OpenStack Region as contained in the config item 'region'
        """
        return self.config['region']

    @property
    def public_url(self):
        """Return the public endpoint URL for the default service as specified
        in the self.default_service attribute
        """
        return "{}:{}".format(os_ip.canonical_url(os_ip.PUBLIC),
                              self.api_port(self.default_service,
                                            os_ip.PUBLIC))

    @property
    def admin_url(self):
        """Return the admin endpoint URL for the default service as specificed
        in the self.default_service attribute
        """
        return "{}:{}".format(os_ip.canonical_url(os_ip.ADMIN),
                              self.api_port(self.default_service,
                                            os_ip.ADMIN))

    @property
    def internal_url(self):
        """Return the internal internal endpoint URL for the default service as
        specificated in the self.default_service attribtue
        """
        return "{}:{}".format(os_ip.canonical_url(os_ip.INTERNAL),
                              self.api_port(self.default_service,
                                            os_ip.INTERNAL))

    @contextlib.contextmanager
    def restart_on_change(self):
        """Restart the services in the self.restart_map{} attribute if any of
        the files identified by the keys changes for the wrapped call.

        This function is a @decorator that checks if the wrapped function
        changes any of the files identified by the keys in the
        self.restart_map{} and, if they change, restarts the services in the
        corresponding list.
        """
        checksums = {path: ch_host.path_hash(path)
                     for path in self.full_restart_map().keys()}
        yield
        restarts = []
        for path in self.full_restart_map():
            if ch_host.path_hash(path) != checksums[path]:
                restarts += self.full_restart_map()[path]
        services_list = list(collections.OrderedDict.fromkeys(restarts).keys())
        for service_name in services_list:
            ch_host.service_restart(service_name)

    def render_all_configs(self, adapters_instance=None):
        """Render (write) all of the config files identified as the keys in the
        self.restart_map{}

        Note: If the config file changes on storage as a result of the config
        file being written, then the services are restarted as per
        the restart_the_services() method.

        If adapters_instance is None then the self.adapters_instance is used
        that was setup in the __init__() method.

        :param adapters_instance: [optional] the adapters_instance to use.
        """
        self.render_configs(self.full_restart_map().keys(),
                            adapters_instance=adapters_instance)

    def render_configs(self, configs, adapters_instance=None):
        """Render the configuration files identified in the list passed as
        configs.

        If adapters_instance is None then the self.adapters_instance is used
        that was setup in the __init__() method.

        :param configs: list of strings, the names of the configuration files.
        :param adapters_instance: [optional] the adapters_instance to use.
        """
        if adapters_instance is None:
            adapters_instance = self.adapters_instance
        with self.restart_on_change():
            for conf in configs:
                charmhelpers.core.templating.render(
                    source=os.path.basename(conf),
                    template_loader=os_templating.get_loader(
                        'templates/', self.release),
                    target=conf,
                    context=adapters_instance)

    def render_with_interfaces(self, interfaces):
        """Render the configs using the interfaces passed; overrides any
        interfaces passed in the instance creation.

        :param interfaces: list of interface objects to render against
        """
        self.render_all_configs(
            adapters_instance=self.adapters_class(interfaces))

    def restart_all(self):
        """Restart all the services configured in the self.services[]
        attribute.
        """
        for svc in self.services:
            ch_host.service_restart(svc)

    def enable_haproxy(self):
        """Determine if haproxy is fronting the services

        @return True if haproxy is fronting the service"""
        return 'haproxy' in self.ha_resources

    def db_sync(self):
        """Perform a database sync using the command defined in the
        self.sync_cmd attribute. The services defined in self.services are
        restarted after the database sync.
        """
        sync_done = hookenv.leader_get(attribute='db-sync-done')
        if not sync_done:
            subprocess.check_call(self.sync_cmd)
            hookenv.leader_set({'db-sync-done': True})
            # Restart services immediatly after db sync as
            # render_domain_config needs a working system
            self.restart_all()

    def configure_ha_resources(self, hacluster):
        """Inform the ha subordinate about each service it should manage. The
        child class specifies the services via self.ha_resources

        @param hacluster interface
        """
        RESOURCE_TYPES = {
            'vips': self._add_ha_vips_config,
            'haproxy': self._add_ha_haproxy_config,
        }
        self.resources = CRM()
        if not self.ha_resources:
            return
        for res_type in self.ha_resources:
            RESOURCE_TYPES[res_type]()
        # TODO Remove hardcoded multicast port
        hacluster.bind_on(iface=self.config[IFACE_KEY], mcastport=4440)
        hacluster.manage_resources(self.resources)

    def _add_ha_vips_config(self):
        """Add a VirtualIP object for each user specified vip to self.resources
        """
        for vip in self.config.get(VIP_KEY, []).split():
            iface = (ip.get_iface_for_address(vip) or
                     self.config(IFACE_KEY))
            netmask = (ip.get_netmask_for_address(vip) or
                       self.config(CIDR_KEY))
            if iface is not None:
                self.resources.add(
                    ha.VirtualIP(
                        self.name,
                        vip,
                        nic=iface,
                        cidr=netmask,))

    def _add_ha_haproxy_config(self):
        """Add a InitService object for haproxy to self.resources
        """
        self.resources.add(
            ha.InitService(
                self.name,
                'haproxy',))

    def set_haproxy_stat_password(self):
        """Set a stats password for accessing haproxy statistics"""
        if not get_state('haproxy.stat.password'):
            set_state('haproxy.stat.password', pwgen(32))

<commit_msg>Refactor ha methods to push some functionality down into interface<commit_after># OpenStackCharm() - base class for build OpenStack charms from for the
# reactive framework.

# need/want absolute imports for the package imports to work properly
from __future__ import absolute_import

import os
import random
import string
import subprocess
import contextlib
import collections

import six

import charmhelpers.contrib.network.ip as ch_ip
import charmhelpers.contrib.openstack.templating as os_templating
import charmhelpers.contrib.openstack.utils as os_utils
import charmhelpers.core.hookenv as hookenv
import charmhelpers.core.host as ch_host
import charmhelpers.core.templating
import charmhelpers.fetch
import charms.reactive.bus

import charms_openstack.ip as os_ip

# _releases{} is a dictionary of release -> class that is instantiated
# according to the the release that is being requested.  i.e. a charm can
# handle more than one release.  The OpenStackCharm() derived class sets the
# `release` variable to indicate which release that the charm supports.
# Any subsequent releases that need a different/specialised charm uses the
# `release` class property to indicate that it handles that release onwards.
_releases = {}

# `_singleton` stores the instance of the class that is being used during a
# hook invocation.
_singleton = None

# List of releases that OpenStackCharm based charms know about
KNOWN_RELEASES = [
    'diablo',
    'essex',
    'folsom',
    'grizzly',
    'havana',
    'icehouse',
    'juno',
    'kilo',
    'liberty',
    'mitaka',
]

VIP_KEY = "vip"
CIDR_KEY = "vip_cidr"
IFACE_KEY = "vip_iface"

def get_charm_instance(release=None, *args, **kwargs):
    """Get an instance of the charm based on the release (or use the
    default if release is None).

    OS releases are in alphabetical order, so it looks for the first release
    that is provided if release is None, otherwise it finds the release that is
    before or equal to the release passed.

    Note that it passes args and kwargs to the class __init__() method.

    :param release: lc string representing release wanted.
    :returns: OpenStackCharm() derived class according to cls.releases
    """
    if len(_releases.keys()) == 0:
        raise RuntimeError("No derived OpenStackCharm() classes registered")
    # Note that this relies on OS releases being in alphabetica order
    known_releases = sorted(_releases.keys())
    cls = None
    if release is None:
        # take the latest version of the charm if no release is passed.
        cls = _releases[known_releases[-1]]
    elif release < known_releases[0]:
        raise RuntimeError(
            "Release {} is not supported by this charm. Earliest support is "
            "{} release".format(release, known_releases[0]))
    else:
        # try to find the release that is supported.
        for known_release in reversed(known_releases):
            if release >= known_release:
                cls = _releases[known_release]
                break
    if cls is None:
        raise RuntimeError("Release {} is not supported".format(release))
    return cls(release=release, *args, **kwargs)

class OpenStackCharmMeta(type):
    """Metaclass to provide a classproperty of 'singleton' so that class
    methods in the derived OpenStackCharm() class can simply use cls.singleton
    to get the instance of the charm.

    Thus cls.singleton is a singleton for accessing and creating the default
    OpenStackCharm() derived class.  This is to avoid a lot of boilerplate in
    the classmethods for the charm code.  This is because, usually, a
    classmethod is only called once per invocation of the script.

    Thus in the derived charm code we can do this:

        cls.singleton.instance_method(...)

    and this will instatiate the charm and call instance_method() on it.

    Note that self.singleton is also defined as a property for completeness so
    that cls.singleton and self.singleton give consistent results.
    """

    def __init__(cls, name, mro, members):
        """Receive the OpenStackCharm() (derived) class and store the release
        that it works against.  Each class defines a 'release' that it handles
        and the order of releases (as given in charmhelpers) determines (for
        any release) which OpenStackCharm() derived class is the handler for
        that class.  Note, that if the `name` is 'OpenStackCharm' then the
        function ignores the release, etc.

        :param name: string for class name.
        :param mro: tuple of base classes.
        :param members: dictionary of name to class attribute (f, p, a, etc.)
        """
        global _releases
        if name == 'OpenStackCharm':
            return
        if 'release' in members.keys():
            release = members['release']
            if release not in KNOWN_RELEASES:
                raise RuntimeError(
                    "Release {} is not a known OpenStack release"
                    .format(release))
            if release in _releases.keys():
                raise RuntimeError(
                    "Release {} defined more than once in classes {} and {} "
                    " (at least)"
                    .format(release, _releases[release].__name__, name))
            # store the class against the release.
            _releases[release] = cls
        else:
            raise RuntimeError(
                "class '{}' does not define a release that it supports. "
                "Please use the 'release' class property to define the "
                "release.".format(name))

    @property
    def singleton(cls):
        global _singleton
        if _singleton is None:
            _singleton = get_charm_instance()
        return _singleton

@six.add_metaclass(OpenStackCharmMeta)
class OpenStackCharm(object):
    """
    Base class for all OpenStack Charm classes;
    encapulates general OpenStack charm payload operations

    Theory:
    Derive form this class, set the name, first_release and releases class
    variables so that get_charm_instance() will create an instance of this
    charm.

    See the other class variables for details on what they are for and do.
    """

    # first_release = this is the first release in which this charm works
    release = 'icehouse'

    # The name of the charm (for printing, etc.)
    name = 'charmname'

    # List of packages to install
    packages = []

    # Dictionary mapping services to ports for public, admin and
    # internal endpoints
    api_ports = {}

    # Keystone endpoint type
    service_type = None

    # Default service for the charm
    default_service = None

    # A dictionary of:
    # {
    #    'config.file': ['list', 'of', 'services', 'to', 'restart'],
    #    'config2.file': ['more', 'services'],
    # }
    restart_map = {}

    # The command used to sync the database
    sync_cmd = []

    # The list of services that this charm manages
    services = []

    # The adapters class that this charm uses to adapt interfaces.
    adapters_class = None

    ha_resources = []
    adapters_class = None
    HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'

    @property
    def singleton(self):
        """Return the only instance of the charm class in this run"""
        # Note refers back to the Metaclass property for this charm.
        return self.__class__.singleton

    def __init__(self, interfaces=None, config=None, release=None):
        """Instantiate an instance of the class.

        Sets up self.config and self.adapter_instance if cls.adapters_class and
        interfaces has been set.

        :param interfaces: list of interface instances for the charm.
        :param config: the config for the charm (optionally None for
        automatically using config())
        """
        self.config = config or hookenv.config()
        self.release = release
        self.adapters_instance = None
        if interfaces and self.adapters_class:
            self.adapters_instance = self.adapters_class(interfaces)
        self.set_haproxy_stat_password()

    def all_packages(self):
        """List of packages to be installed

        @return ['pkg1', 'pkg2', ...]
        """
        _packages = []
        _packages.extend(self.packages)
        if self.enable_haproxy():
            _packages.append('haproxy')
        return _packages

    def full_restart_map(self):
        """Map of services to be restarted if a file changes

        @return {
                    'file1': ['svc1', 'svc3'],
                    'file2': ['svc2', 'svc3'],
                    ...
                }
        """
        _restart_map = self.restart_map.copy()
        if self.enable_haproxy():
            _restart_map[self.HAPROXY_CONF] = ['haproxy']
        return _restart_map

    def install(self):
        """Install packages related to this charm based on
        contents of self.packages attribute.
        """
        packages = charmhelpers.fetch.filter_installed_packages(
            self.all_packages())
        if packages:
            hookenv.status_set('maintenance', 'Installing packages')
            charmhelpers.fetch.apt_install(packages, fatal=True)
            # TODO need a call to assess_status(...) or equivalent so that we
            # can determine the workload status at the end of the handler.  At
            # the end of install the 'status' is stuck in maintenance until the
            # next hook is run.
        self.set_state('{}-installed'.format(self.name))

    def set_state(self, state, value=None):
        """proxy for charms.reactive.bus.set_state()"""
        charms.reactive.bus.set_state(state, value)

    def remove_state(self, state):
        """proxy for charms.reactive.bus.remove_state()"""
        charms.reactive.bus.remove_state(state)

    def api_port(self, service, endpoint_type=os_ip.PUBLIC):
        """Return the API port for a particular endpoint type from the
        self.api_ports{}.

        :param service: string for service name
        :param endpoing_type: one of charm.openstack.ip.PUBLIC| INTERNAL| ADMIN
        :returns: port (int)
        """
        return self.api_ports[service][endpoint_type]

    def configure_source(self):
        """Configure installation source using the config item
        'openstack-origin'

        This configures the installation source for deb packages and then
        updates the packages list on the unit.
        """
        os_utils.configure_installation_source(self.config['openstack-origin'])
        charmhelpers.fetch.apt_update(fatal=True)

    @property
    def region(self):
        """Return the OpenStack Region as contained in the config item 'region'
        """
        return self.config['region']

    @property
    def public_url(self):
        """Return the public endpoint URL for the default service as specified
        in the self.default_service attribute
        """
        return "{}:{}".format(os_ip.canonical_url(os_ip.PUBLIC),
                              self.api_port(self.default_service,
                                            os_ip.PUBLIC))

    @property
    def admin_url(self):
        """Return the admin endpoint URL for the default service as specificed
        in the self.default_service attribute
        """
        return "{}:{}".format(os_ip.canonical_url(os_ip.ADMIN),
                              self.api_port(self.default_service,
                                            os_ip.ADMIN))

    @property
    def internal_url(self):
        """Return the internal internal endpoint URL for the default service as
        specificated in the self.default_service attribtue
        """
        return "{}:{}".format(os_ip.canonical_url(os_ip.INTERNAL),
                              self.api_port(self.default_service,
                                            os_ip.INTERNAL))

    @contextlib.contextmanager
    def restart_on_change(self):
        """Restart the services in the self.restart_map{} attribute if any of
        the files identified by the keys changes for the wrapped call.

        This function is a @decorator that checks if the wrapped function
        changes any of the files identified by the keys in the
        self.restart_map{} and, if they change, restarts the services in the
        corresponding list.
        """
        checksums = {path: ch_host.path_hash(path)
                     for path in self.full_restart_map().keys()}
        yield
        restarts = []
        for path in self.full_restart_map():
            if ch_host.path_hash(path) != checksums[path]:
                restarts += self.full_restart_map()[path]
        services_list = list(collections.OrderedDict.fromkeys(restarts).keys())
        for service_name in services_list:
            ch_host.service_restart(service_name)

    def render_all_configs(self, adapters_instance=None):
        """Render (write) all of the config files identified as the keys in the
        self.restart_map{}

        Note: If the config file changes on storage as a result of the config
        file being written, then the services are restarted as per
        the restart_the_services() method.

        If adapters_instance is None then the self.adapters_instance is used
        that was setup in the __init__() method.

        :param adapters_instance: [optional] the adapters_instance to use.
        """
        self.render_configs(self.full_restart_map().keys(),
                            adapters_instance=adapters_instance)

    def render_configs(self, configs, adapters_instance=None):
        """Render the configuration files identified in the list passed as
        configs.

        If adapters_instance is None then the self.adapters_instance is used
        that was setup in the __init__() method.

        :param configs: list of strings, the names of the configuration files.
        :param adapters_instance: [optional] the adapters_instance to use.
        """
        if adapters_instance is None:
            adapters_instance = self.adapters_instance
        with self.restart_on_change():
            for conf in configs:
                charmhelpers.core.templating.render(
                    source=os.path.basename(conf),
                    template_loader=os_templating.get_loader(
                        'templates/', self.release),
                    target=conf,
                    context=adapters_instance)

    def render_with_interfaces(self, interfaces):
        """Render the configs using the interfaces passed; overrides any
        interfaces passed in the instance creation.

        :param interfaces: list of interface objects to render against
        """
        self.render_all_configs(
            adapters_instance=self.adapters_class(interfaces))

    def restart_all(self):
        """Restart all the services configured in the self.services[]
        attribute.
        """
        for svc in self.services:
            ch_host.service_restart(svc)

    def enable_haproxy(self):
        """Determine if haproxy is fronting the services

        @return True if haproxy is fronting the service"""
        return 'haproxy' in self.ha_resources

    def db_sync(self):
        """Perform a database sync using the command defined in the
        self.sync_cmd attribute. The services defined in self.services are
        restarted after the database sync.
        """
        sync_done = hookenv.leader_get(attribute='db-sync-done')
        if not sync_done:
            subprocess.check_call(self.sync_cmd)
            hookenv.leader_set({'db-sync-done': True})
            # Restart services immediatly after db sync as
            # render_domain_config needs a working system
            self.restart_all()

    def configure_ha_resources(self, hacluster):
        """Inform the ha subordinate about each service it should manage. The
        child class specifies the services via self.ha_resources

        @param hacluster interface
        """
        RESOURCE_TYPES = {
            'vips': self._add_ha_vips_config,
            'haproxy': self._add_ha_haproxy_config,
        }
        if not self.ha_resources:
            return
        for res_type in self.ha_resources:
           RESOURCE_TYPES[res_type](hacluster)
        hacluster.bind_resources(iface=self.config[IFACE_KEY])

    def _add_ha_vips_config(self, hacluster):
        """Add a VirtualIP object for each user specified vip to self.resources
        """
        for vip in self.config.get(VIP_KEY, '').split():
            iface = (ch_ip.get_iface_for_address(vip) or
                     self.config(IFACE_KEY))
            netmask = (ch_ip.get_netmask_for_address(vip) or
                       self.config(CIDR_KEY))
            if iface is not None:
                hacluster.add_vip(self.name, vip, iface, netmask)

    def _add_ha_haproxy_config(self, hacluster):
        """Add a InitService object for haproxy to self.resources
        """
        hacluster.add_init_service(self.name, 'haproxy')

    def set_haproxy_stat_password(self):
        """Set a stats password for accessing haproxy statistics"""
        if not charms.reactive.bus.get_state('haproxy.stat.password'):
            password = ''.join([
                random.choice(string.ascii_letters + string.digits)
                for n in range(32)])
            charms.reactive.bus.set_state('haproxy.stat.password', password)

<|endoftext|>


<commit_before>#!/usr/bin/env python

# Copyright (C) 2013 Steven Myint
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Checks code blocks in reStructuredText."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import argparse
import os
import subprocess
import sys
import tempfile

from docutils import core, nodes, utils, writers
from docutils.parsers import rst

__version__ = '0.3.5'

def check(source, filename='<string>', report_level=2, ignore=None):
    """Yield errors.

    Use lower report_level for noisier error output.

    Each yielded error is a tuple of the form:

        (line_number, message)

    Line numbers are indexed at 1 and are with respect to the full RST file.

    Each code block is checked asynchronously in a subprocess.

    """
    writer = CheckWriter(source, filename, ignore=ignore)
    core.publish_string(source, writer=writer,
                        source_path=filename,
                        settings_overrides={'report_level': report_level})

    for checker in writer.checkers:
        for error in checker():
            yield error

def check_file(filename, report_level=2, ignore=None):
    """Yield errors."""
    with open(filename) as input_file:
        contents = input_file.read()

    for error in check(contents,
                       filename=filename,
                       report_level=report_level,
                       ignore=ignore):
        yield error

class CodeBlockDirective(rst.Directive):

    """Code block directive."""

    has_content = True
    optional_arguments = 1

    def run(self):
        """Run directive."""
        try:
            language = self.arguments[0]
        except IndexError:
            language = ''
        code = '\n'.join(self.content)
        literal = nodes.literal_block(code, code)
        literal['classes'].append('code-block')
        literal['language'] = language
        return [literal]

rst.directives.register_directive('code-block', CodeBlockDirective)
rst.directives.register_directive('sourcecode', CodeBlockDirective)

def bash_checker(code):
    """Return checker."""
    run = run_in_subprocess(code, '.bash', ['bash', '-n'])

    def run_check():
        """Yield errors."""
        result = run()
        if result:
            (output, filename) = result
            prefix = filename + ': line '
            for line in output.splitlines():
                if not line.startswith(prefix):
                    continue
                message = line[len(prefix):]
                split_message = message.split(':', 1)
                yield (int(split_message[0]) - 1,
                       split_message[1].strip())
    return run_check

def c_checker(code):
    """Return checker."""
    return gcc_checker(code, '.c', [os.getenv('CC', 'gcc'), '-std=c99'])

def cpp_checker(code):
    """Return checker."""
    return gcc_checker(code, '.cpp', [os.getenv('CXX', 'g++'), '-std=c++0x'])

def gcc_checker(code, filename_suffix, arguments):
    """Return checker."""
    run = run_in_subprocess(code,
                            filename_suffix,
                            arguments + ['-pedantic', '-fsyntax-only'])

    def run_check():
        """Yield errors."""
        result = run()
        if result:
            (output, filename) = result
            prefix = filename + ':'
            for line in output.splitlines():
                if not line.startswith(prefix):
                    continue
                message = line[len(prefix):]
                split_message = message.split(':', 2)
                try:
                    line_number = int(split_message[0])
                except ValueError:
                    continue
                yield (line_number,
                       split_message[2].strip())

    return run_check

def python_checker(code):
    """Return checker."""
    def run_check():
        """Yield errors."""
        try:
            compile(code, '<string>', 'exec')
        except SyntaxError as exception:
            yield (int(exception.lineno), exception.msg)

    return run_check

def run_in_subprocess(code, filename_suffix, arguments):
    """Return None on success."""
    temporary_file = tempfile.NamedTemporaryFile(mode='w',
                                                 suffix=filename_suffix)
    temporary_file.write(code)
    temporary_file.flush()

    process = subprocess.Popen(arguments + [temporary_file.name],
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)

    def run():
        """Yield errors."""
        raw_result = process.communicate()
        if process.returncode != 0:
            return (raw_result[1].decode('utf-8'),
                    temporary_file.name)

    return run

class CheckTranslator(nodes.NodeVisitor):

    """Visits code blocks and checks for syntax errors in code."""

    def __init__(self, document, contents, filename, ignore):
        nodes.NodeVisitor.__init__(self, document)
        self.checkers = []
        self.contents = contents
        self.filename = filename
        self.ignore = ignore or []

    def visit_literal_block(self, node):
        """Check syntax of code block."""
        if 'code-block' not in node['classes']:
            return

        language = node.get('language')
        if language in self.ignore:
            return

        checker = {
            'bash': bash_checker,
            'c': c_checker,
            'cpp': cpp_checker,
            'python': python_checker,
            'rst': lambda source: lambda: check(source, ignore=self.ignore)
        }.get(language)

        if checker:
            run = checker(node.rawsource)

            def run_check():
                """Yield errors."""
                all_results = run()
                if all_results is not None:
                    if all_results:
                        for result in all_results:
                            error_offset = result[0] - 1

                            yield (
                                beginning_of_code_block(node, self.contents) +
                                error_offset,
                                '({}) {}'.format(language, result[1]))
                    else:
                        yield (self.filename, 0, 'unknown error')
            self.checkers.append(run_check)

        raise nodes.SkipNode

    def unknown_visit(self, node):
        """Ignore."""

    def unknown_departure(self, node):
        """Ignore."""

def beginning_of_code_block(node, full_contents):
    """Return line number of beginning of code block."""
    lines = full_contents.splitlines()
    line_number = node.line
    code_block_length = len(node.rawsource.splitlines())

    try:
        # Case where there are no extra spaces.
        if lines[line_number - 1].strip():
            return line_number - code_block_length + 1
    except IndexError:
        pass

    # The offsets are wrong if the RST text has multiple blank lines after the
    # code block. This is a workaround.
    for line_number in range(node.line, 1, -1):
        if lines[line_number - 2].strip():
            break

    return line_number - code_block_length

class CheckWriter(writers.Writer):

    """Runs CheckTranslator on code blocks."""

    def __init__(self, contents, filename, ignore):
        writers.Writer.__init__(self)
        self.checkers = []
        self.contents = contents
        self.filename = filename
        self.ignore = ignore

    def translate(self):
        """Run CheckTranslator."""
        visitor = CheckTranslator(self.document,
                                  contents=self.contents,
                                  filename=self.filename,
                                  ignore=self.ignore)
        self.document.walkabout(visitor)
        self.checkers += visitor.checkers

def main():
    """Return 0 on success."""
    parser = argparse.ArgumentParser(description=__doc__, prog='rstcheck')
    parser.add_argument('files', nargs='+',
                        help='files to check')
    parser.add_argument('--report', type=int, metavar='level', default=2,
                        help='report system messages at or higher than level; '
                             '1 info, 2 warning, 3 error, 4 severe, 5 none '
                             '(default: %(default)s)')
    parser.add_argument('--ignore', metavar='language', default='',
                        help='comma-separated list of languages to ignore')
    args = parser.parse_args()

    status = 0
    for filename in args.files:
        try:
            for error in check_file(filename,
                                    report_level=args.report,
                                    ignore=args.ignore.split(',')):
                print('{}:{}: (ERROR/3) {}'.format(filename,
                                                   error[0],
                                                   error[1]),
                      file=sys.stderr)
                status = 1
        except utils.SystemMessage:
            # docutils already prints a message to standard error.
            status = 1

    return status

if __name__ == '__main__':
    sys.exit(main())
<commit_msg>Simplify<commit_after>#!/usr/bin/env python

# Copyright (C) 2013 Steven Myint
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Checks code blocks in reStructuredText."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import argparse
import os
import subprocess
import sys
import tempfile

from docutils import core, nodes, utils, writers
from docutils.parsers import rst

__version__ = '0.3.5'

def check(source, filename='<string>', report_level=2, ignore=None):
    """Yield errors.

    Use lower report_level for noisier error output.

    Each yielded error is a tuple of the form:

        (line_number, message)

    Line numbers are indexed at 1 and are with respect to the full RST file.

    Each code block is checked asynchronously in a subprocess.

    """
    writer = CheckWriter(source, filename, ignore=ignore)
    core.publish_string(source, writer=writer,
                        source_path=filename,
                        settings_overrides={'report_level': report_level})

    for checker in writer.checkers:
        for error in checker():
            yield error

def check_file(filename, report_level=2, ignore=None):
    """Yield errors."""
    with open(filename) as input_file:
        contents = input_file.read()

    for error in check(contents,
                       filename=filename,
                       report_level=report_level,
                       ignore=ignore):
        yield error

class CodeBlockDirective(rst.Directive):

    """Code block directive."""

    has_content = True
    optional_arguments = 1

    def run(self):
        """Run directive."""
        try:
            language = self.arguments[0]
        except IndexError:
            language = ''
        code = '\n'.join(self.content)
        literal = nodes.literal_block(code, code)
        literal['classes'].append('code-block')
        literal['language'] = language
        return [literal]

rst.directives.register_directive('code-block', CodeBlockDirective)
rst.directives.register_directive('sourcecode', CodeBlockDirective)

def bash_checker(code):
    """Return checker."""
    run = run_in_subprocess(code, '.bash', ['bash', '-n'])

    def run_check():
        """Yield errors."""
        result = run()
        if result:
            (output, filename) = result
            prefix = filename + ': line '
            for line in output.splitlines():
                if not line.startswith(prefix):
                    continue
                message = line[len(prefix):]
                split_message = message.split(':', 1)
                yield (int(split_message[0]) - 1,
                       split_message[1].strip())
    return run_check

def c_checker(code):
    """Return checker."""
    return gcc_checker(code, '.c', [os.getenv('CC', 'gcc'), '-std=c99'])

def cpp_checker(code):
    """Return checker."""
    return gcc_checker(code, '.cpp', [os.getenv('CXX', 'g++'), '-std=c++0x'])

def gcc_checker(code, filename_suffix, arguments):
    """Return checker."""
    run = run_in_subprocess(code,
                            filename_suffix,
                            arguments + ['-pedantic', '-fsyntax-only'])

    def run_check():
        """Yield errors."""
        result = run()
        if result:
            (output, filename) = result
            prefix = filename + ':'
            for line in output.splitlines():
                if not line.startswith(prefix):
                    continue
                message = line[len(prefix):]
                split_message = message.split(':', 2)
                try:
                    line_number = int(split_message[0])
                except ValueError:
                    continue
                yield (line_number,
                       split_message[2].strip())

    return run_check

def python_checker(code):
    """Return checker."""
    def run_check():
        """Yield errors."""
        try:
            compile(code, '<string>', 'exec')
        except SyntaxError as exception:
            yield (int(exception.lineno), exception.msg)

    return run_check

def run_in_subprocess(code, filename_suffix, arguments):
    """Return None on success."""
    temporary_file = tempfile.NamedTemporaryFile(mode='w',
                                                 suffix=filename_suffix)
    temporary_file.write(code)
    temporary_file.flush()

    process = subprocess.Popen(arguments + [temporary_file.name],
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)

    def run():
        """Yield errors."""
        raw_result = process.communicate()
        if process.returncode != 0:
            return (raw_result[1].decode(),
                    temporary_file.name)

    return run

class CheckTranslator(nodes.NodeVisitor):

    """Visits code blocks and checks for syntax errors in code."""

    def __init__(self, document, contents, filename, ignore):
        nodes.NodeVisitor.__init__(self, document)
        self.checkers = []
        self.contents = contents
        self.filename = filename
        self.ignore = ignore or []

    def visit_literal_block(self, node):
        """Check syntax of code block."""
        if 'code-block' not in node['classes']:
            return

        language = node.get('language')
        if language in self.ignore:
            return

        checker = {
            'bash': bash_checker,
            'c': c_checker,
            'cpp': cpp_checker,
            'python': python_checker,
            'rst': lambda source: lambda: check(source, ignore=self.ignore)
        }.get(language)

        if checker:
            run = checker(node.rawsource)

            def run_check():
                """Yield errors."""
                all_results = run()
                if all_results is not None:
                    if all_results:
                        for result in all_results:
                            error_offset = result[0] - 1

                            yield (
                                beginning_of_code_block(node, self.contents) +
                                error_offset,
                                '({}) {}'.format(language, result[1]))
                    else:
                        yield (self.filename, 0, 'unknown error')
            self.checkers.append(run_check)

        raise nodes.SkipNode

    def unknown_visit(self, node):
        """Ignore."""

    def unknown_departure(self, node):
        """Ignore."""

def beginning_of_code_block(node, full_contents):
    """Return line number of beginning of code block."""
    lines = full_contents.splitlines()
    line_number = node.line
    code_block_length = len(node.rawsource.splitlines())

    try:
        # Case where there are no extra spaces.
        if lines[line_number - 1].strip():
            return line_number - code_block_length + 1
    except IndexError:
        pass

    # The offsets are wrong if the RST text has multiple blank lines after the
    # code block. This is a workaround.
    for line_number in range(node.line, 1, -1):
        if lines[line_number - 2].strip():
            break

    return line_number - code_block_length

class CheckWriter(writers.Writer):

    """Runs CheckTranslator on code blocks."""

    def __init__(self, contents, filename, ignore):
        writers.Writer.__init__(self)
        self.checkers = []
        self.contents = contents
        self.filename = filename
        self.ignore = ignore

    def translate(self):
        """Run CheckTranslator."""
        visitor = CheckTranslator(self.document,
                                  contents=self.contents,
                                  filename=self.filename,
                                  ignore=self.ignore)
        self.document.walkabout(visitor)
        self.checkers += visitor.checkers

def main():
    """Return 0 on success."""
    parser = argparse.ArgumentParser(description=__doc__, prog='rstcheck')
    parser.add_argument('files', nargs='+',
                        help='files to check')
    parser.add_argument('--report', type=int, metavar='level', default=2,
                        help='report system messages at or higher than level; '
                             '1 info, 2 warning, 3 error, 4 severe, 5 none '
                             '(default: %(default)s)')
    parser.add_argument('--ignore', metavar='language', default='',
                        help='comma-separated list of languages to ignore')
    args = parser.parse_args()

    status = 0
    for filename in args.files:
        try:
            for error in check_file(filename,
                                    report_level=args.report,
                                    ignore=args.ignore.split(',')):
                print('{}:{}: (ERROR/3) {}'.format(filename,
                                                   error[0],
                                                   error[1]),
                      file=sys.stderr)
                status = 1
        except utils.SystemMessage:
            # docutils already prints a message to standard error.
            status = 1

    return status

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


<commit_before>from cherrypy.test import test
test.prefer_parent_path()

import threading
import time
import unittest

import cherrypy
from cherrypy.process import wspbus

msg = "Listener %d on channel %s: %s."

class PublishSubscribeTests(unittest.TestCase):

    def get_listener(self, channel, index):
        def listener(arg=None):
            self.responses.append(msg % (index, channel, arg))
        return listener

    def test_builtin_channels(self):
        b = wspbus.Bus()

        self.responses, expected = [], []

        for channel in b.listeners:
            for index, priority in enumerate([100, 50, 0, 51]):
                b.subscribe(channel, self.get_listener(channel, index), priority)

        for channel in b.listeners:
            b.publish(channel)
            expected.extend([msg % (i, channel, None) for i in (2, 1, 3, 0)])
            b.publish(channel, arg=79347)
            expected.extend([msg % (i, channel, 79347) for i in (2, 1, 3, 0)])

        self.assertEqual(self.responses, expected)

    def test_custom_channels(self):
        b = wspbus.Bus()

        self.responses, expected = [], []

        custom_listeners = ('hugh', 'louis', 'dewey')
        for channel in custom_listeners:
            for index, priority in enumerate([None, 10, 60, 40]):
                b.subscribe(channel, self.get_listener(channel, index), priority)

        for channel in custom_listeners:
            b.publish(channel, 'ah so')
            expected.extend([msg % (i, channel, 'ah so') for i in (1, 3, 0, 2)])
            b.publish(channel)
            expected.extend([msg % (i, channel, None) for i in (1, 3, 0, 2)])

        self.assertEqual(self.responses, expected)

    def test_listener_errors(self):
        b = wspbus.Bus()

        self.responses, expected = [], []
        channels = [c for c in b.listeners if c != 'log']

        for channel in channels:
            b.subscribe(channel, self.get_listener(channel, 1))
            # This will break since the lambda takes no args.
            b.subscribe(channel, lambda: None, priority=20)

        for channel in channels:
            self.assertRaises(TypeError, b.publish, channel, 123)
            expected.append(msg % (1, channel, 123))

        self.assertEqual(self.responses, expected)

class BusMethodTests(unittest.TestCase):

    def log(self, bus):
        self._log_entries = []
        def logit(msg, level):
            self._log_entries.append(msg)
        bus.subscribe('log', logit)

    def assertLog(self, entries):
        self.assertEqual(self._log_entries, entries)

    def get_listener(self, channel, index):
        def listener(arg=None):
            self.responses.append(msg % (index, channel, arg))
        return listener

    def test_start(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('start', self.get_listener('start', index))

        b.start()
        try:
            # The start method MUST call all 'start' listeners.
            self.assertEqual(set(self.responses),
                             set([msg % (i, 'start', None) for i in range(num)]))
            # The start method MUST move the state to STARTED
            # (or EXITING, if errors occur)
            self.assertEqual(b.state, b.states.STARTED)
            # The start method MUST log its states.
            self.assertLog(['Bus STARTING', 'Bus STARTED'])
        finally:
            # Exit so the atexit handler doesn't complain.
            b.exit()

    def test_stop(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('stop', self.get_listener('stop', index))

        b.stop()

        # The stop method MUST call all 'stop' listeners.
        self.assertEqual(set(self.responses),
                         set([msg % (i, 'stop', None) for i in range(num)]))
        # The stop method MUST move the state to STOPPED
        self.assertEqual(b.state, b.states.STOPPED)
        # The stop method MUST log its states.
        self.assertLog(['Bus STOPPING', 'Bus STOPPED'])

    def test_graceful(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('graceful', self.get_listener('graceful', index))

        b.graceful()

        # The graceful method MUST call all 'graceful' listeners.
        self.assertEqual(set(self.responses),
                         set([msg % (i, 'graceful', None) for i in range(num)]))
        # The graceful method MUST log its states.
        self.assertLog(['Bus graceful'])

    def test_exit(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('stop', self.get_listener('stop', index))
            b.subscribe('exit', self.get_listener('exit', index))

        b.exit()

        # The exit method MUST call all 'stop' listeners,
        # and then all 'exit' listeners.
        self.assertEqual(set(self.responses),
                         set([msg % (i, 'stop', None) for i in range(num)] +
                             [msg % (i, 'exit', None) for i in range(num)]))
        # The exit method MUST move the state to EXITING
        self.assertEqual(b.state, b.states.EXITING)
        # The exit method MUST log its states.
        self.assertLog(['Bus STOPPING', 'Bus STOPPED', 'Bus EXITING', 'Bus EXITED'])

    def test_block(self):
        b = wspbus.Bus()
        self.log(b)

        def f():
            time.sleep(1)
            b.exit()
        def g():
            time.sleep(2)
        threading.Thread(target=f).start()
        threading.Thread(target=g).start()
        self.assertEqual(len(threading.enumerate()), 3)

        b.block()

        # The block method MUST wait for the EXITING state.
        self.assertEqual(b.state, b.states.EXITING)
        # The block method MUST wait for ALL non-main threads to finish.
        self.assertEqual(len(threading.enumerate()), 1)
        self.assertLog(['Bus STOPPING', 'Bus STOPPED',
                        'Bus EXITING', 'Bus EXITED',
                        'Waiting for child threads to terminate...'])

    # TODO: restart, wait, start_with_callback, log

if __name__ == "__main__":
    unittest.main()
<commit_msg>More test_bus goodness.<commit_after>from cherrypy.test import test
test.prefer_parent_path()

import threading
import time
import unittest

import cherrypy
from cherrypy.process import wspbus

msg = "Listener %d on channel %s: %s."

class PublishSubscribeTests(unittest.TestCase):

    def get_listener(self, channel, index):
        def listener(arg=None):
            self.responses.append(msg % (index, channel, arg))
        return listener

    def test_builtin_channels(self):
        b = wspbus.Bus()

        self.responses, expected = [], []

        for channel in b.listeners:
            for index, priority in enumerate([100, 50, 0, 51]):
                b.subscribe(channel, self.get_listener(channel, index), priority)

        for channel in b.listeners:
            b.publish(channel)
            expected.extend([msg % (i, channel, None) for i in (2, 1, 3, 0)])
            b.publish(channel, arg=79347)
            expected.extend([msg % (i, channel, 79347) for i in (2, 1, 3, 0)])

        self.assertEqual(self.responses, expected)

    def test_custom_channels(self):
        b = wspbus.Bus()

        self.responses, expected = [], []

        custom_listeners = ('hugh', 'louis', 'dewey')
        for channel in custom_listeners:
            for index, priority in enumerate([None, 10, 60, 40]):
                b.subscribe(channel, self.get_listener(channel, index), priority)

        for channel in custom_listeners:
            b.publish(channel, 'ah so')
            expected.extend([msg % (i, channel, 'ah so') for i in (1, 3, 0, 2)])
            b.publish(channel)
            expected.extend([msg % (i, channel, None) for i in (1, 3, 0, 2)])

        self.assertEqual(self.responses, expected)

    def test_listener_errors(self):
        b = wspbus.Bus()

        self.responses, expected = [], []
        channels = [c for c in b.listeners if c != 'log']

        for channel in channels:
            b.subscribe(channel, self.get_listener(channel, 1))
            # This will break since the lambda takes no args.
            b.subscribe(channel, lambda: None, priority=20)

        for channel in channels:
            self.assertRaises(TypeError, b.publish, channel, 123)
            expected.append(msg % (1, channel, 123))

        self.assertEqual(self.responses, expected)

class BusMethodTests(unittest.TestCase):

    def log(self, bus):
        self._log_entries = []
        def logit(msg, level):
            self._log_entries.append(msg)
        bus.subscribe('log', logit)

    def assertLog(self, entries):
        self.assertEqual(self._log_entries, entries)

    def get_listener(self, channel, index):
        def listener(arg=None):
            self.responses.append(msg % (index, channel, arg))
        return listener

    def test_start(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('start', self.get_listener('start', index))

        b.start()
        try:
            # The start method MUST call all 'start' listeners.
            self.assertEqual(set(self.responses),
                             set([msg % (i, 'start', None) for i in range(num)]))
            # The start method MUST move the state to STARTED
            # (or EXITING, if errors occur)
            self.assertEqual(b.state, b.states.STARTED)
            # The start method MUST log its states.
            self.assertLog(['Bus STARTING', 'Bus STARTED'])
        finally:
            # Exit so the atexit handler doesn't complain.
            b.exit()

    def test_stop(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('stop', self.get_listener('stop', index))

        b.stop()

        # The stop method MUST call all 'stop' listeners.
        self.assertEqual(set(self.responses),
                         set([msg % (i, 'stop', None) for i in range(num)]))
        # The stop method MUST move the state to STOPPED
        self.assertEqual(b.state, b.states.STOPPED)
        # The stop method MUST log its states.
        self.assertLog(['Bus STOPPING', 'Bus STOPPED'])

    def test_graceful(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('graceful', self.get_listener('graceful', index))

        b.graceful()

        # The graceful method MUST call all 'graceful' listeners.
        self.assertEqual(set(self.responses),
                         set([msg % (i, 'graceful', None) for i in range(num)]))
        # The graceful method MUST log its states.
        self.assertLog(['Bus graceful'])

    def test_exit(self):
        b = wspbus.Bus()
        self.log(b)

        self.responses = []
        num = 3
        for index in range(num):
            b.subscribe('stop', self.get_listener('stop', index))
            b.subscribe('exit', self.get_listener('exit', index))

        b.exit()

        # The exit method MUST call all 'stop' listeners,
        # and then all 'exit' listeners.
        self.assertEqual(set(self.responses),
                         set([msg % (i, 'stop', None) for i in range(num)] +
                             [msg % (i, 'exit', None) for i in range(num)]))
        # The exit method MUST move the state to EXITING
        self.assertEqual(b.state, b.states.EXITING)
        # The exit method MUST log its states.
        self.assertLog(['Bus STOPPING', 'Bus STOPPED', 'Bus EXITING', 'Bus EXITED'])

    def test_wait(self):
        b = wspbus.Bus()

        def f(method):
            time.sleep(0.2)
            getattr(b, method)()

        for method, states in [('start', [b.states.STARTED]),
                               ('stop', [b.states.STOPPED]),
                               ('start', [b.states.STARTING, b.states.STARTED]),
                               ('exit', [b.states.EXITING]),
                               ]:
            threading.Thread(target=f, args=(method,)).start()
            b.wait(states)

            # The wait method MUST wait for the given state(s).
            if b.state not in states:
                self.fail("State %r not in %r" % (b.state, states))

    def test_block(self):
        b = wspbus.Bus()
        self.log(b)

        def f():
            time.sleep(0.2)
            b.exit()
        def g():
            time.sleep(0.4)
        threading.Thread(target=f).start()
        threading.Thread(target=g).start()
        self.assertEqual(len(threading.enumerate()), 3)

        b.block()

        # The block method MUST wait for the EXITING state.
        self.assertEqual(b.state, b.states.EXITING)
        # The block method MUST wait for ALL non-main threads to finish.
        self.assertEqual(len(threading.enumerate()), 1)
        self.assertLog(['Bus STOPPING', 'Bus STOPPED',
                        'Bus EXITING', 'Bus EXITED',
                        'Waiting for child threads to terminate...'])

    def test_start_with_callback(self):
        b = wspbus.Bus()
        self.log(b)
        try:
            events = []
            def f(*args, **kwargs):
                events.append(("f", args, kwargs))
            def g():
                events.append("g")
            b.subscribe("start", g)
            b.start_with_callback(f, (1, 3, 5), {"foo": "bar"})
            # Give wait() time to run f()
            time.sleep(0.2)

            # The callback method MUST wait for the STARTED state.
            self.assertEqual(b.state, b.states.STARTED)
            # The callback method MUST run after all start methods.
            self.assertEqual(events, ["g", ("f", (1, 3, 5), {"foo": "bar"})])
        finally:
            b.exit()

    def test_log(self):
        b = wspbus.Bus()
        self.log(b)
        self.assertLog([])

        # Try a normal message.
        expected = []
        for msg in ["O mah darlin'"] * 3 + ["Clementiiiiiiiine"]:
            b.log(msg)
            expected.append(msg)
            self.assertLog(expected)

        # Try an error message
        try:
            foo
        except NameError:
            b.log("You are lost and gone forever", traceback=True)
            lastmsg = self._log_entries[-1]
            if "Traceback" not in lastmsg or "NameError" not in lastmsg:
                self.fail("Last log message %r did not contain "
                          "the expected traceback." % lastmsg)
        else:
            self.fail("NameError was not raised as expected.")

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


<commit_before>import enum

class ComparableEnum(enum.Enum):
    """
    An enum whose compare will try to convert value you compare it against
    to it's instance. Handly for writing code like:

    >>> a = 'test'
    >>> class Enum(ComparableEnum):
    >>>     A = 'test'
    >>> assert Enum.A == a

    Comparison order doesn't matter, so either are True:

    >>> Enum.A == 'test'
    >>> 'test' == Enum.A

    You can even compare enums across classes

    >>> class A(ComparableEnum):
    >>>     A = 1
    >>> class B(ComparableEnum):
    >>>     A = 1
    >>> assert A.A == B.A
    """

    def __eq__(self, other):
        if isinstance(other, enum.Enum) and not isinstance(other, self.__class__):
            other = other.value

        if not isinstance(other, self.__class__):
            return self.__class__(other) == self
        else:
            return super().__eq__(other)

class HashableIntEnum(enum.IntEnum):
    """
    An enum.IntEnum that implements hashability, stemming from it's values, as well
    as hashability
    """

    def __hash__(self) -> int:
        return hash(self.value)

class ComparableIntEnum(HashableIntEnum):
    """
    An enum.IntEnum that implements comparision, stemming from it's values, as well
    as hashability
    """

    def __hash__(self) -> int:
        return hash(self.value)

    def __lt__(self, other: 'ComparableIntEnum') -> bool:
        return self.value < other.value

    def __le__(self, other: 'ComparableIntEnum') -> bool:
        return self.value <= other.value

    def __gt__(self, other: 'ComparableIntEnum') -> bool:
        return self.value > other.value

    def __ge__(self, other: 'ComparableIntEnum') -> bool:
        return self.value >= other.value

    def __eq__(self, other: 'ComparableIntEnum') -> bool:
        return self.value == other.value
<commit_msg>doc mention<commit_after>import enum

class ComparableEnum(enum.Enum):
    """
    An enum whose compare will try to convert value you compare it against
    to it's instance. Handly for writing code like:

    >>> a = 'test'
    >>> class Enum(ComparableEnum):
    >>>     A = 'test'
    >>> assert Enum.A == a

    Comparison order doesn't matter, so either are True:

    >>> Enum.A == 'test'
    >>> 'test' == Enum.A

    Note, however, that following won't work:

    >>> 'test' in (Enum.A, )

    You can even compare enums across classes

    >>> class A(ComparableEnum):
    >>>     A = 1
    >>> class B(ComparableEnum):
    >>>     A = 1
    >>> assert A.A == B.A
    """

    def __eq__(self, other):
        if isinstance(other, enum.Enum) and not isinstance(other, self.__class__):
            other = other.value

        if not isinstance(other, self.__class__):
            return self.__class__(other) == self
        else:
            return super().__eq__(other)

class HashableIntEnum(enum.IntEnum):
    """
    An enum.IntEnum that implements hashability, stemming from it's values, as well
    as hashability
    """

    def __hash__(self) -> int:
        return hash(self.value)

class ComparableIntEnum(HashableIntEnum):
    """
    An enum.IntEnum that implements comparision, stemming from it's values, as well
    as hashability
    """

    def __hash__(self) -> int:
        return hash(self.value)

    def __lt__(self, other: 'ComparableIntEnum') -> bool:
        return self.value < other.value

    def __le__(self, other: 'ComparableIntEnum') -> bool:
        return self.value <= other.value

    def __gt__(self, other: 'ComparableIntEnum') -> bool:
        return self.value > other.value

    def __ge__(self, other: 'ComparableIntEnum') -> bool:
        return self.value >= other.value

    def __eq__(self, other: 'ComparableIntEnum') -> bool:
        return self.value == other.value
<|endoftext|>


<commit_before>from subprocess import call

import gdal
import mapnik
import osr

from config import to_file, to_path
from layer import Layer

def band_count(raster_file):
    ds = gdal.OpenShared(raster_file)
    if not ds:
        return 0
    band = 1
    while True:
        if not ds.GetRasterBand(band):
            return band - 1
        band += 1

def band_info(raster_file, band_index, nodata, use_file_nodata):
    ds = gdal.OpenShared(raster_file, gdal.GA_Update)
    band = ds.GetRasterBand(band_index)

    if not band.GetOverviewCount():
        call([
            'gdaladdo', '-ro', '-q', '-b', str(band_index), '-r', 'average', raster_file, '2', '4', '8'
        ])

    old_nodata = band.GetNoDataValue()
    try:
        if nodata:
            band.SetNoDataValue(nodata)
        elif not use_file_nodata:
            band.DeleteNoDataValue()

        if old_nodata == nodata:
            (min, max, mean, std_dev) = band.GetStatistics(True, True)
        else:
            (min, max, mean, std_dev) = band.ComputeStatistics(False)

        histogram = band.GetHistogram(min, max, 256, approx_ok=1)
    finally:
        band.SetNoDataValue(old_nodata)
    return {
        'nodata': nodata,
        'min': min,
        'max': max,
        'mean': mean,
        'stdDev': std_dev,
        'histogram': histogram
    }

def from_dict(layer_dict):
    return RasterLayer(
        id=layer_dict['id'],
        file=to_file(layer_dict['path']),
        bands=_Band.from_dict(layer_dict['bands'])
    )

# noinspection PyUnresolvedReferences
class RasterLayer(Layer):
    """Represents a map layer."""

    concurrent = False

    def __init__(self, id, file, bands):
        """Creates a raster layer.

        :param file: Path to raster image.
        :type file: str

        :param bands: Iterable of gwv.Band instances to include.
        :type bands: iterable
        """
        super(RasterLayer, self).__init__()
        self.id = id
        self.file = file
        # self._add_overview(bands)
        self.band_layers = [
            _BandLayer(file, band)
            for band in bands]
        self._persist_nodata(bands)

    def append_to(self, map):
        """Appends Mapnik layers and styles for the bands in this layer.

        :param map: The mapnik Map.
        :type map: mapnik.Map
        """
        for band_layer in self.band_layers:
            band_layer.append_to(map)

    def to_dict(self):
        return {
            'id': self.id,
            'type': 'raster',
            'path': to_path(self.file),
            'bands': [band_layer.band.to_dict() for band_layer in self.band_layers],
            'bounds': self.bounds()
        }

    def update(self, layer_dict):
        layer = self.to_dict()
        layer.update(layer_dict)
        updated_layer = from_dict(layer)
        bands = [band_layer.band for band_layer in updated_layer.band_layers]
        self._persist_nodata(bands)
        return updated_layer

    def _persist_nodata(self, bands):
        ds = gdal.OpenShared(self.file, gdal.GA_Update)
        for band in bands:
            gdal_band = ds.GetRasterBand(int(band.index))
            nodata = band.nodata
            if nodata:
                gdal_band.SetNoDataValue(float(nodata))
            else:
                gdal_band.DeleteNoDataValue()

    def features(self, lat, lng):
        return [
            self.band_value(band_layer, lat, lng)
            for band_layer in self.band_layers
            ]

    def band_value(self, band_layer, lat, lng):
        features = self.layer_features(band_layer.band.index - 1, lat, lng)
        if features:
            return features[0]['value']
        return None

    def _flatten(self, items):
        if items == []:
            return items
        if isinstance(items[0], list):
            return self._flatten(items[0]) + self._flatten(items[1:])
        return items[:1] + self._flatten(items[1:])

class _BandLayer(object):
    def __init__(self, file, band):
        self.file = file
        self.band = band
        self.style = self.create_style()
        self.mapnik_datasource = mapnik.Gdal(
            file=file,
            band=band.index,
            shared=True,
            nodata=band.nodata,
            nodata_tolerance=band.nodata_tolerance
        )
        self.mapnik_layer = self.create_mapnik_layer()

    def create_mapnik_layer(self):
        layer = mapnik.Layer(self.band.name)
        layer.srs = self.extract_srs(self.file)
        layer.datasource = self.mapnik_datasource
        layer.styles.append(self.band.name)
        return layer

    def extract_srs(self, file):
        ds = gdal.Open(file)
        projInfo = ds.GetProjection()
        spatialRef = osr.SpatialReference()
        spatialRef.ImportFromWkt(projInfo)
        return spatialRef.ExportToProj4()

    def create_style(self):
        symbolizer = mapnik.RasterSymbolizer()
        symbolizer.colorizer = self.create_colorizer()
        rule = mapnik.Rule()
        rule.symbols.append(symbolizer)
        style = mapnik.Style()
        style.comp_op = mapnik.CompositeOp.plus
        style.rules.append(rule)
        return style

    def create_colorizer(self):
        band = self.band
        colorizer = mapnik.RasterColorizer(mapnik.COLORIZER_LINEAR, mapnik.Color('#00000000'))
        for stop, color in band.palette:
            colorizer.add_stop(stop, mapnik.COLORIZER_LINEAR, mapnik.Color(str(color)))
        return colorizer

    def append_to(self, map):
        map.layers.append(self.mapnik_layer)
        map.append_style(self.band.name, self.style)

class _Band(object):
    """Represents a band to include in a map layer, and it's visualisation properties"""

    def __init__(self, index, nodata, nodata_tolerance, palette):
        """Creates a band.

        :param index: Index of band in image.
        :type index: int

        :param min: Value to map to first color in palette.
        :type min: float

        :param max: Value to map to last color in palette.
        :type max: float

        :param nodata: Value to assign pixels with no data.
        :type nodata: float

        :param nodata_tolerance: Distance from nodata value pixels still are considered nodata.
        :type nodata_tolerance: float

        :param palette: Iterable of hex-color strings, which will be used for the values in the band.
        :type palette: iterable
        """
        self.name = 'B' + str(index)
        self.index = index
        self.nodata = nodata
        self.nodata_tolerance = nodata_tolerance
        self.palette = palette

    def to_dict(self):
        return {
            'index': self.index,
            'nodata': self.nodata if self.nodata else '',
            'nodataTolerance': self.nodata_tolerance,
            'palette': self.palette
        }

    @staticmethod
    def from_dict(bands_dict):
        return [
            _Band(
                index=int(band['index']),
                nodata=float(band['nodata']) if band.get('nodata', None) else None,
                nodata_tolerance=float(band.get('nodataTolerance', 1e-12)),
                palette=band['palette']
            )
            for band in bands_dict
            ]
<commit_msg>fixed nodata value bandinfo request<commit_after>from subprocess import call

import gdal
import mapnik
import osr

from config import to_file, to_path
from layer import Layer

def band_count(raster_file):
    ds = gdal.OpenShared(raster_file)
    if not ds:
        return 0
    band = 1
    while True:
        if not ds.GetRasterBand(band):
            return band - 1
        band += 1

def band_info(raster_file, band_index, nodata, use_file_nodata):
    ds = gdal.OpenShared(raster_file, gdal.GA_Update)
    band = ds.GetRasterBand(band_index)

    if not band.GetOverviewCount():
        call([
            'gdaladdo', '-ro', '-q', '-b', str(band_index), '-r', 'average', raster_file, '2', '4', '8'
        ])

    old_nodata = band.GetNoDataValue()
    try:
        if nodata:
            band.SetNoDataValue(nodata)
        elif use_file_nodata:
            nodata = old_nodata
        else:
            band.DeleteNoDataValue()

        if old_nodata == nodata:
            (min, max, mean, std_dev) = band.GetStatistics(True, True)
        else:
            (min, max, mean, std_dev) = band.ComputeStatistics(False)

        histogram = band.GetHistogram(min, max, 256, approx_ok=1)
    finally:
        if old_nodata:
            band.SetNoDataValue(old_nodata)
        elif band.GetNoDataValue():
            band.DeleteNoDataValue()
    return {
        'nodata': nodata,
        'min': min,
        'max': max,
        'mean': mean,
        'stdDev': std_dev,
        'histogram': histogram
    }

def from_dict(layer_dict):
    return RasterLayer(
        id=layer_dict['id'],
        file=to_file(layer_dict['path']),
        bands=_Band.from_dict(layer_dict['bands'])
    )

# noinspection PyUnresolvedReferences
class RasterLayer(Layer):
    """Represents a map layer."""

    concurrent = False

    def __init__(self, id, file, bands):
        """Creates a raster layer.

        :param file: Path to raster image.
        :type file: str

        :param bands: Iterable of gwv.Band instances to include.
        :type bands: iterable
        """
        super(RasterLayer, self).__init__()
        self.id = id
        self.file = file
        # self._add_overview(bands)
        self.band_layers = [
            _BandLayer(file, band)
            for band in bands]
        self._persist_nodata(bands)

    def append_to(self, map):
        """Appends Mapnik layers and styles for the bands in this layer.

        :param map: The mapnik Map.
        :type map: mapnik.Map
        """
        for band_layer in self.band_layers:
            band_layer.append_to(map)

    def to_dict(self):
        return {
            'id': self.id,
            'type': 'raster',
            'path': to_path(self.file),
            'bands': [band_layer.band.to_dict() for band_layer in self.band_layers],
            'bounds': self.bounds()
        }

    def update(self, layer_dict):
        layer = self.to_dict()
        layer.update(layer_dict)
        updated_layer = from_dict(layer)
        bands = [band_layer.band for band_layer in updated_layer.band_layers]
        self._persist_nodata(bands)
        return updated_layer

    def _persist_nodata(self, bands):
        ds = gdal.OpenShared(self.file, gdal.GA_Update)
        for band in bands:
            gdal_band = ds.GetRasterBand(int(band.index))
            nodata = band.nodata
            if nodata:
                gdal_band.SetNoDataValue(float(nodata))
            else:
                gdal_band.DeleteNoDataValue()

    def features(self, lat, lng):
        return [
            self.band_value(band_layer, lat, lng)
            for band_layer in self.band_layers
            ]

    def band_value(self, band_layer, lat, lng):
        features = self.layer_features(band_layer.band.index - 1, lat, lng)
        if features:
            return features[0]['value']
        return None

    def _flatten(self, items):
        if items == []:
            return items
        if isinstance(items[0], list):
            return self._flatten(items[0]) + self._flatten(items[1:])
        return items[:1] + self._flatten(items[1:])

class _BandLayer(object):
    def __init__(self, file, band):
        self.file = file
        self.band = band
        self.style = self.create_style()
        self.mapnik_datasource = mapnik.Gdal(
            file=file,
            band=band.index,
            shared=True,
            nodata=band.nodata,
            nodata_tolerance=band.nodata_tolerance
        )
        self.mapnik_layer = self.create_mapnik_layer()

    def create_mapnik_layer(self):
        layer = mapnik.Layer(self.band.name)
        layer.srs = self.extract_srs(self.file)
        layer.datasource = self.mapnik_datasource
        layer.styles.append(self.band.name)
        return layer

    def extract_srs(self, file):
        ds = gdal.Open(file)
        projInfo = ds.GetProjection()
        spatialRef = osr.SpatialReference()
        spatialRef.ImportFromWkt(projInfo)
        return spatialRef.ExportToProj4()

    def create_style(self):
        symbolizer = mapnik.RasterSymbolizer()
        symbolizer.colorizer = self.create_colorizer()
        rule = mapnik.Rule()
        rule.symbols.append(symbolizer)
        style = mapnik.Style()
        style.comp_op = mapnik.CompositeOp.plus
        style.rules.append(rule)
        return style

    def create_colorizer(self):
        band = self.band
        colorizer = mapnik.RasterColorizer(mapnik.COLORIZER_LINEAR, mapnik.Color('#00000000'))
        for stop, color in band.palette:
            colorizer.add_stop(stop, mapnik.COLORIZER_LINEAR, mapnik.Color(str(color)))
        return colorizer

    def append_to(self, map):
        map.layers.append(self.mapnik_layer)
        map.append_style(self.band.name, self.style)

class _Band(object):
    """Represents a band to include in a map layer, and it's visualisation properties"""

    def __init__(self, index, nodata, nodata_tolerance, palette):
        """Creates a band.

        :param index: Index of band in image.
        :type index: int

        :param min: Value to map to first color in palette.
        :type min: float

        :param max: Value to map to last color in palette.
        :type max: float

        :param nodata: Value to assign pixels with no data.
        :type nodata: float

        :param nodata_tolerance: Distance from nodata value pixels still are considered nodata.
        :type nodata_tolerance: float

        :param palette: Iterable of hex-color strings, which will be used for the values in the band.
        :type palette: iterable
        """
        self.name = 'B' + str(index)
        self.index = index
        self.nodata = nodata
        self.nodata_tolerance = nodata_tolerance
        self.palette = palette

    def to_dict(self):
        return {
            'index': self.index,
            'nodata': self.nodata if self.nodata else '',
            'nodataTolerance': self.nodata_tolerance,
            'palette': self.palette
        }

    @staticmethod
    def from_dict(bands_dict):
        return [
            _Band(
                index=int(band['index']),
                nodata=float(band['nodata']) if band.get('nodata', None) else None,
                nodata_tolerance=float(band.get('nodataTolerance', 1e-12)),
                palette=band['palette']
            )
            for band in bands_dict
            ]
<|endoftext|>


<commit_before>import requests

from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone

from allauth.socialaccount.models import SocialApp, SocialAccount, SocialToken
from twitter import Twitter, OAuth

from scheduler.models import ScheduledPost

def post_to_facebook(post):
    try:
        account = SocialAccount.objects.get(user=post.user,
                                            provider=post.service)
    except SocialAccount.DoesNotExist:
        return None
    try:
        access_token = account.socialtoken_set.get().token
    except SocialToken.DoesNotExist:
        return None
    response = requests.post(
        "https://graph.facebook.com/{0}/feed?message={1}"
        "&access_token={2}".format(
            account.uid, post.status.encode("utf-8"), access_token
        ))
    if response.ok:
        post.is_posted = True
        post.save()

def post_to_twitter(post):
    app = SocialApp.objects.get(provider=post.service)
    try:
        account = SocialAccount.objects.get(user=post.user,
                                            provider=post.service)
    except SocialAccount.DoesNotExist:
        return None
    try:
        token = account.socialtoken_set.get().token
        token_secret = account.socialtoken_set.get().token_secret
    except SocialToken.DoesNotExist:
        return None
    twt = Twitter(auth=OAuth(token, token_secret, app.client_id, app.secret))
    twt.statuses.update(status=post.status.encode("utf-8"))
    post.is_posted = True
    post.save()

class Command(BaseCommand):
    help = 'Autopost to the social platforms'

    def handle(self, *args, **kwargs):
        now = timezone.now()
        next_min = now + timezone.timedelta(minutes=1)
        for post in ScheduledPost.objects.filter(is_posted=False,
                                                 scheduled_datetime__gte=now,
                                                 scheduled_datetime__lt=next_min):
            if post.service == "facebook":
                post_to_facebook(post)
            elif post.service == "twitter":
                post_to_twitter(post)
<commit_msg>Attach the link to the post for FB.<commit_after>import urllib
import re
import requests

from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone

from allauth.socialaccount.models import SocialApp, SocialAccount, SocialToken
from twitter import Twitter, OAuth

from scheduler.models import ScheduledPost

def post_to_facebook(post):
    try:
        account = SocialAccount.objects.get(user=post.user,
                                            provider=post.service)
    except SocialAccount.DoesNotExist:
        return None
    try:
        access_token = account.socialtoken_set.get().token
    except SocialToken.DoesNotExist:
        return None
    message = post.status.encode("utf-8")
    link = re.findall("https?://[^\s]+", post.status)
    params = {
        "message": message,
        "access_token": access_token
    }
    if link:
        params["link"] = link[0]
    response = requests.post(
        "https://graph.facebook.com/{0}/feed?{1}".format(
            account.uid,
            urllib.urlencode(params)
        ))
    if response.ok:
        post.is_posted = True
        post.save()

def post_to_twitter(post):
    app = SocialApp.objects.get(provider=post.service)
    try:
        account = SocialAccount.objects.get(user=post.user,
                                            provider=post.service)
    except SocialAccount.DoesNotExist:
        return None
    try:
        token = account.socialtoken_set.get().token
        token_secret = account.socialtoken_set.get().token_secret
    except SocialToken.DoesNotExist:
        return None
    twt = Twitter(auth=OAuth(token, token_secret, app.client_id, app.secret))
    twt.statuses.update(status=post.status.encode("utf-8"))
    post.is_posted = True
    post.save()

class Command(BaseCommand):
    help = 'Autopost to the social platforms'

    def handle(self, *args, **kwargs):
        now = timezone.now()
        next_min = now + timezone.timedelta(minutes=1)
        for post in ScheduledPost.objects.filter(is_posted=False,
                                                 scheduled_datetime__gte=now,
                                                 scheduled_datetime__lt=next_min):
            if post.service == "facebook":
                post_to_facebook(post)
            elif post.service == "twitter":
                post_to_twitter(post)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

def migrate(cr, version):
    cr.execute('ALTER TABLE carepoint_medical_prescription_order_line '
               'RENAME TO carepoint_rx_ord_ln')

    cr.execute('ALTER TABLE carepoint_carepoint_organization '
               'RENAME TO carepoint_org_bind')
<commit_msg>[FIX] connector_carepoint: Fix prescription migration * Add try/catch & rollback to db alterations in case server re-upgrades<commit_after># -*- coding: utf-8 -*-
# Copyright 2016 LasLabs Inc.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

_logger = logging.getLogger(__name__)

def migrate(cr, version):
    try:
        cr.execute('ALTER TABLE carepoint_medical_prescription_order_line '
                   'RENAME TO carepoint_rx_ord_ln')
    except Exception:
        cr.rollback()
        _logger.exception('Cannot perform migration')

    try:
        cr.execute('ALTER TABLE carepoint_carepoint_organization '
                   'RENAME TO carepoint_org_bind')
    except Exception:
        cr.rollback()
        _logger.exception('Cannot perform migration')
<|endoftext|>