<commit_before>import param

import iris
from cartopy import crs
from cartopy.feature import Feature
from cartopy.io.img_tiles import GoogleTiles
from holoviews.core import Element2D, Dimension
from holoviews.core import util
from holoviews.element import Text as HVText

from .cube import HoloCube

class GeoElement(Element2D):
    """
    Baseclass for Element2D types with associated cartopy
    coordinate reference system.
    """

    _abstract = True

    crs = param.ClassSelector(class_=crs.CRS, doc="""
        Cartopy coordinate-reference-system specifying the
        coordinate system of the data. Inferred automatically
        when GeoElement wraps Iris Feature object.""")

    def __init__(self, data, **kwargs):
        crs = None
        if isinstance(data, iris.cube.Cube):
            coord_sys = data.coord_system()
            if hasattr(coord_sys, 'as_cartopy_projection'):
                crs = coord_sys.as_cartopy_projection()
        elif isinstance(data, (Feature, GoogleTiles)):
            crs = data.crs

        supplied_crs = kwargs.get('crs', None)
        if supplied_crs and crs and crs != supplied_crs:
            raise ValueError('Supplied coordinate reference '
                             'system must match crs of the data.')
        elif crs:
            kwargs['crs'] = crs
        super(GeoElement, self).__init__(data, **kwargs)

    def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides):
        if 'crs' not in overrides: overrides['crs'] = self.crs
        return super(GeoElement, self).clone(data, shared_data, new_type,
                                             *args, **overrides)

class GeoFeature(GeoElement):
    """
    A GeoFeature represents a geographical feature
    specified as a cartopy Feature type.
    """

    group = param.String(default='GeoFeature')

    def __init__(self, data, **params):
        if not isinstance(data, Feature):
            raise TypeError('%s data has to be an cartopy Feature type'
                            % type(data).__name__)
        super(GeoFeature, self).__init__(data, **params)

class WMTS(GeoElement):
    """
    The WMTS Element represents a Web Map Tile Service
    specified as a tuple of the API URL and
    """

    group = param.String(default='WMTS')

    layer = param.String(doc="The layer on the tile service")

    def __init__(self, data, **params):
        if not isinstance(data, util.basestring):
            raise TypeError('%s data has to be a tile service URL'
                            % type(data).__name__)
        super(WMTS, self).__init__(data, **params)

class GeoTiles(GeoElement):
    """
    GeoTiles represents an image tile source to dynamically
    load data from depending on the zoom level.
    """

    group = param.String(default='GeoTiles')

    def __init__(self, data, **params):
        if not isinstance(data, GoogleTiles):
            raise TypeError('%s data has to be a cartopy GoogleTiles type'
                            % type(data).__name__)
        super(GeoTiles, self).__init__(data, **params)

class Points(GeoElement, HoloCube):
    """
    Points represent a collection of points with
    an associated cartopy coordinate-reference system.
    """

    kdims = param.List(default=['longitude', 'latitude'])

    group = param.String(default='Points')

class Contours(GeoElement, HoloCube):
    """
    Contours represents a 2D array of some quantity with
    some associated coordinates, which may be discretized
    into one or more contours.
    """

    kdims = param.List(default=[Dimension('x'), Dimension('y')])

    vdims = param.List(default=[Dimension('z')], bounds=(1, 1))

    group = param.String(default='Contours')

class Image(GeoElement, HoloCube):
    """
    Image represents a 2D array of some quantity with
    some associated coordinates.
    """

    kdims = param.List(default=[Dimension('x'), Dimension('y')])

    vdims = param.List(default=[Dimension('z')], bounds=(1, 1))

    group = param.String(default='Image')

class Text(HVText, GeoElement):
    """
    An annotation containing some text at an x, y coordinate
    along with a coordinate reference system.
    """
<commit_msg>Declared GeoTiles, WMTS and GeoFeature as auxiliary components<commit_after>import param

import iris
from cartopy import crs
from cartopy.feature import Feature
from cartopy.io.img_tiles import GoogleTiles
from holoviews.core import Element2D, Dimension
from holoviews.core import util
from holoviews.element import Text as HVText

from .cube import HoloCube

class GeoElement(Element2D):
    """
    Baseclass for Element2D types with associated cartopy
    coordinate reference system.
    """

    _abstract = True

    crs = param.ClassSelector(class_=crs.CRS, doc="""
        Cartopy coordinate-reference-system specifying the
        coordinate system of the data. Inferred automatically
        when GeoElement wraps Iris Feature object.""")

    def __init__(self, data, **kwargs):
        crs = None
        if isinstance(data, iris.cube.Cube):
            coord_sys = data.coord_system()
            if hasattr(coord_sys, 'as_cartopy_projection'):
                crs = coord_sys.as_cartopy_projection()
        elif isinstance(data, (Feature, GoogleTiles)):
            crs = data.crs

        supplied_crs = kwargs.get('crs', None)
        if supplied_crs and crs and crs != supplied_crs:
            raise ValueError('Supplied coordinate reference '
                             'system must match crs of the data.')
        elif crs:
            kwargs['crs'] = crs
        super(GeoElement, self).__init__(data, **kwargs)

    def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides):
        if 'crs' not in overrides: overrides['crs'] = self.crs
        return super(GeoElement, self).clone(data, shared_data, new_type,
                                             *args, **overrides)

class GeoFeature(GeoElement):
    """
    A GeoFeature represents a geographical feature
    specified as a cartopy Feature type.
    """

    group = param.String(default='GeoFeature')

    _auxiliary_component = True

    def __init__(self, data, **params):
        if not isinstance(data, Feature):
            raise TypeError('%s data has to be an cartopy Feature type'
                            % type(data).__name__)
        super(GeoFeature, self).__init__(data, **params)

class WMTS(GeoElement):
    """
    The WMTS Element represents a Web Map Tile Service
    specified as a tuple of the API URL and
    """

    group = param.String(default='WMTS')

    layer = param.String(doc="The layer on the tile service")

    _auxiliary_component = True

    def __init__(self, data, **params):
        if not isinstance(data, util.basestring):
            raise TypeError('%s data has to be a tile service URL'
                            % type(data).__name__)
        super(WMTS, self).__init__(data, **params)

class GeoTiles(GeoElement):
    """
    GeoTiles represents an image tile source to dynamically
    load data from depending on the zoom level.
    """

    group = param.String(default='GeoTiles')

    _auxiliary_component = True

    def __init__(self, data, **params):
        if not isinstance(data, GoogleTiles):
            raise TypeError('%s data has to be a cartopy GoogleTiles type'
                            % type(data).__name__)
        super(GeoTiles, self).__init__(data, **params)

class Points(GeoElement, HoloCube):
    """
    Points represent a collection of points with
    an associated cartopy coordinate-reference system.
    """

    kdims = param.List(default=['longitude', 'latitude'])

    group = param.String(default='Points')

class Contours(GeoElement, HoloCube):
    """
    Contours represents a 2D array of some quantity with
    some associated coordinates, which may be discretized
    into one or more contours.
    """

    kdims = param.List(default=[Dimension('x'), Dimension('y')])

    vdims = param.List(default=[Dimension('z')], bounds=(1, 1))

    group = param.String(default='Contours')

class Image(GeoElement, HoloCube):
    """
    Image represents a 2D array of some quantity with
    some associated coordinates.
    """

    kdims = param.List(default=[Dimension('x'), Dimension('y')])

    vdims = param.List(default=[Dimension('z')], bounds=(1, 1))

    group = param.String(default='Image')

class Text(HVText, GeoElement):
    """
    An annotation containing some text at an x, y coordinate
    along with a coordinate reference system.
    """
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2003-2009 Edgewall Software
# Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
# Copyright (C) 2005-2006 Matthew Good <trac@matt-good.net>
# Copyright (C) 2005-2006 Christopher Lenz <cmlenz@gmx.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Jonas Borgström <jonas@edgewall.com>
#         Matthew Good <trac@matt-good.net>
#         Christopher Lenz <cmlenz@gmx.de>

import pkg_resources
import os
import socket
import select
import sys
from SocketServer import ThreadingMixIn

from trac import __version__ as VERSION
from trac.util import autoreload, daemon
from trac.web.auth import BasicAuthentication, DigestAuthentication
from trac.web.main import dispatch_request
from trac.web.wsgi import WSGIServer, WSGIRequestHandler

class AuthenticationMiddleware(object):

    def __init__(self, application, auths, single_env_name=None):
        self.application = application
        self.auths = auths
        self.single_env_name = single_env_name
        if single_env_name:
            self.part = 0
        else:
            self.part = 1

    def __call__(self, environ, start_response):
        path_info = environ.get('PATH_INFO', '')
        path_parts = filter(None, path_info.split('/'))
        if len(path_parts) > self.part and path_parts[self.part] == 'login':
            env_name = self.single_env_name or path_parts[0]
            if env_name:
                auth = self.auths.get(env_name, self.auths.get('*'))
                if auth:
                    remote_user = auth.do_auth(environ, start_response)
                    if not remote_user:
                        return []
                    environ['REMOTE_USER'] = remote_user
        return self.application(environ, start_response)

class BasePathMiddleware(object):

    def __init__(self, application, base_path):
        self.base_path = '/' + base_path.strip('/')
        self.application = application

    def __call__(self, environ, start_response):
        path = environ['SCRIPT_NAME'] + environ.get('PATH_INFO', '')
        environ['PATH_INFO'] = path[len(self.base_path):]
        environ['SCRIPT_NAME'] = self.base_path
        return self.application(environ, start_response)

class TracEnvironMiddleware(object):

    def __init__(self, application, env_parent_dir, env_paths, single_env):
        self.application = application
        self.environ = {}
        self.environ['trac.env_path'] = None
        if env_parent_dir:
            self.environ['trac.env_parent_dir'] = env_parent_dir
        elif single_env:
            self.environ['trac.env_path'] = env_paths[0]
        else:
            self.environ['trac.env_paths'] = env_paths

    def __call__(self, environ, start_response):
        for k, v in self.environ.iteritems():
            environ.setdefault(k, v)
        return self.application(environ, start_response)

class TracHTTPServer(ThreadingMixIn, WSGIServer):
    daemon_threads = True

    def __init__(self, server_address, application, env_parent_dir, env_paths,
                 use_http_11=False):
        request_handlers = (TracHTTPRequestHandler, TracHTTP11RequestHandler)
        WSGIServer.__init__(self, server_address, application,
                            request_handler=request_handlers[bool(use_http_11)])

    if sys.version_info < (2, 6):
        def serve_forever(self, poll_interval=0.5):
            while True:
                r, w, e = select.select([self], [], [], poll_interval)
                if self in r:
                    self.handle_request()

class TracHTTPRequestHandler(WSGIRequestHandler):

    server_version = 'tracd/' + VERSION

    def address_string(self):
        # Disable reverse name lookups
        return self.client_address[:2][0]

class TracHTTP11RequestHandler(TracHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'

def main():
    from optparse import OptionParser, OptionValueError
    parser = OptionParser(usage='usage: %prog [options] [projenv] ...',
                          version='%%prog %s' % VERSION)

    auths = {}
    def _auth_callback(option, opt_str, value, parser, cls):
        info = value.split(',', 3)
        if len(info) != 3:
            raise OptionValueError("Incorrect number of parameters for %s"
                                   % option)

        env_name, filename, realm = info
        if env_name in auths:
            print >> sys.stderr, 'Ignoring duplicate authentication option ' \
                                 'for project: %s' % env_name
        else:
            auths[env_name] = cls(os.path.abspath(filename), realm)

    def _validate_callback(option, opt_str, value, parser, valid_values):
        if value not in valid_values:
            raise OptionValueError('%s must be one of: %s, not %s'
                                   % (opt_str, '|'.join(valid_values), value))
        setattr(parser.values, option.dest, value)

    def _octal(option, opt_str, value, parser):
        try:
            setattr(parser.values, option.dest, int(value, 8))
        except ValueError:
            raise OptionValueError('Invalid octal umask value: %r' % value)

    parser.add_option('-a', '--auth', action='callback', type='string',
                      metavar='DIGESTAUTH', callback=_auth_callback,
                      callback_args=(DigestAuthentication,),
                      help='[projectdir],[htdigest_file],[realm]')
    parser.add_option('--basic-auth', action='callback', type='string',
                      metavar='BASICAUTH', callback=_auth_callback,
                      callback_args=(BasicAuthentication,),
                      help='[projectdir],[htpasswd_file],[realm]')

    parser.add_option('-p', '--port', action='store', type='int', dest='port',
                      help='the port number to bind to')
    parser.add_option('-b', '--hostname', action='store', dest='hostname',
                      help='the host name or IP address to bind to')
    parser.add_option('--protocol', action='callback', type="string",
                      dest='protocol', callback=_validate_callback,
                      callback_args=(('http', 'scgi', 'ajp', 'fcgi'),),
                      help='http|scgi|ajp|fcgi')
    parser.add_option('-q', '--unquote', action='store_true',
                      dest='unquote',
                      help='unquote PATH_INFO (may be needed when using ajp)')
    parser.add_option('--http10', action='store_false', dest='http11',
                      help='use HTTP/1.0 protocol version instead of HTTP/1.1')
    parser.add_option('--http11', action='store_true', dest='http11',
                      help='use HTTP/1.1 protocol version (default)')
    parser.add_option('-e', '--env-parent-dir', action='store',
                      dest='env_parent_dir', metavar='PARENTDIR',
                      help='parent directory of the project environments')
    parser.add_option('--base-path', action='store', type='string', # XXX call this url_base_path?
                      dest='base_path',
                      help='the initial portion of the request URL\'s "path"')

    parser.add_option('-r', '--auto-reload', action='store_true',
                      dest='autoreload',
                      help='restart automatically when sources are modified')

    parser.add_option('-s', '--single-env', action='store_true',
                      dest='single_env', help='only serve a single '
                      'project without the project list', default=False)

    if os.name == 'posix':
        parser.add_option('-d', '--daemonize', action='store_true',
                          dest='daemonize',
                          help='run in the background as a daemon')
        parser.add_option('--pidfile', action='store',
                          dest='pidfile',
                          help='when daemonizing, file to which to write pid')
        parser.add_option('--umask', action='callback', type='string',
                          dest='umask', metavar='MASK', callback=_octal,
                          help='when daemonizing, file mode creation mask '
                          'to use, in octal notation (default 022)')

        try:
            import grp, pwd

            def _group(option, opt_str, value, parser):
                try:
                    value = int(value)
                except ValueError:
                    try:
                        value = grp.getgrnam(value)[2]
                    except KeyError:
                        raise OptionValueError('group not found: %r' % value)
                setattr(parser.values, option.dest, value)

            def _user(option, opt_str, value, parser):
                try:
                    value = int(value)
                except ValueError:
                    try:
                        value = pwd.getpwnam(value)[2]
                    except KeyError:
                        raise OptionValueError('user not found: %r' % value)
                setattr(parser.values, option.dest, value)

            parser.add_option('--group', action='callback', type='string',
                              dest='group', metavar='GROUP', callback=_group,
                              help='the group to run as')
            parser.add_option('--user', action='callback', type='string',
                              dest='user', metavar='USER', callback=_user,
                              help='the user to run as')
        except ImportError:
            pass

    parser.set_defaults(port=None, hostname='', base_path='', daemonize=False,
                        protocol='http', http11=True, umask=022, user=None,
                        group=None)
    options, args = parser.parse_args()

    if not args and not options.env_parent_dir:
        parser.error('either the --env-parent-dir option or at least one '
                     'environment must be specified')
    if options.single_env:
        if options.env_parent_dir:
            parser.error('the --single-env option cannot be used with '
                         '--env-parent-dir')
        elif len(args) > 1:
            parser.error('the --single-env option cannot be used with '
                         'more than one enviroment')
    if options.daemonize and options.autoreload:
        parser.error('the --auto-reload option cannot be used with '
                     '--daemonize')

    if options.port is None:
        options.port = {
            'http': 80,
            'scgi': 4000,
            'ajp': 8009,
            'fcgi': 8000,
        }[options.protocol]
    server_address = (options.hostname, options.port)

    # relative paths don't work when daemonized
    args = [os.path.abspath(a) for a in args]
    if options.env_parent_dir:
        options.env_parent_dir = os.path.abspath(options.env_parent_dir)
    if parser.has_option('pidfile') and options.pidfile:
        options.pidfile = os.path.abspath(options.pidfile)

    wsgi_app = TracEnvironMiddleware(dispatch_request,
                                     options.env_parent_dir, args,
                                     options.single_env)
    if auths:
        if options.single_env:
            project_name = os.path.basename(args[0])
            wsgi_app = AuthenticationMiddleware(wsgi_app, auths, project_name)
        else:
            wsgi_app = AuthenticationMiddleware(wsgi_app, auths)
    base_path = options.base_path.strip('/')
    if base_path:
        wsgi_app = BasePathMiddleware(wsgi_app, base_path)

    if options.protocol == 'http':
        def serve():
            addr, port = server_address
            if not addr or addr == '0.0.0.0':
                loc = '0.0.0.0:%s view at http://127.0.0.1:%s/%s' \
                       % (port, port, base_path)
            else:
                loc = 'http://%s:%s/%s' % (addr, port, base_path)

            try:
                httpd = TracHTTPServer(server_address, wsgi_app,
                                       options.env_parent_dir, args,
                                       use_http_11=options.http11)
            except socket.error, e:
                print 'Error starting Trac server on %s' % loc
                print e.strerror
                sys.exit(1)

            print 'Server starting in PID %i.' % os.getpid()
            print 'Serving on %s' % loc
            if options.http11:
                print 'Using HTTP/1.1 protocol version'
            httpd.serve_forever()
    elif options.protocol in ('scgi', 'ajp', 'fcgi'):
        def serve():
            server_cls = __import__('flup.server.%s' % options.protocol,
                                    None, None, ['']).WSGIServer
            flup_app = wsgi_app
            if options.unquote:
                from trac.web.fcgi_frontend import FlupMiddleware
                flup_app = FlupMiddleware(flup_app)
            ret = server_cls(flup_app, bindAddress=server_address).run()
            sys.exit(42 if ret else 0) # if SIGHUP exit with status 42

    try:
        if options.daemonize:
            daemon.daemonize(pidfile=options.pidfile, progname='tracd',
                             umask=options.umask)
        if options.group is not None:
            os.setgid(options.group)
        if options.user is not None:
            os.setuid(options.user)

        if options.autoreload:
            def modification_callback(file):
                print >> sys.stderr, 'Detected modification of %s, ' \
                                     'restarting.' % file
            autoreload.main(serve, modification_callback)
        else:
            serve()

    except OSError, e:
        print >> sys.stderr, '%s: %s' % (e.__class__.__name__, e)
        sys.exit(1)
    except KeyboardInterrupt:
        pass

if __name__ == '__main__':
    pkg_resources.require('Trac==%s' % VERSION)
    main()
<commit_msg>1.0.1dev: Correctly handle `socket.error` which has no attribute `strerror` on Python 2.5<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2003-2009 Edgewall Software
# Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
# Copyright (C) 2005-2006 Matthew Good <trac@matt-good.net>
# Copyright (C) 2005-2006 Christopher Lenz <cmlenz@gmx.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Jonas Borgström <jonas@edgewall.com>
#         Matthew Good <trac@matt-good.net>
#         Christopher Lenz <cmlenz@gmx.de>

import pkg_resources
import os
import socket
import select
import sys
from SocketServer import ThreadingMixIn

from trac import __version__ as VERSION
from trac.util import autoreload, daemon
from trac.web.auth import BasicAuthentication, DigestAuthentication
from trac.web.main import dispatch_request
from trac.web.wsgi import WSGIServer, WSGIRequestHandler

class AuthenticationMiddleware(object):

    def __init__(self, application, auths, single_env_name=None):
        self.application = application
        self.auths = auths
        self.single_env_name = single_env_name
        if single_env_name:
            self.part = 0
        else:
            self.part = 1

    def __call__(self, environ, start_response):
        path_info = environ.get('PATH_INFO', '')
        path_parts = filter(None, path_info.split('/'))
        if len(path_parts) > self.part and path_parts[self.part] == 'login':
            env_name = self.single_env_name or path_parts[0]
            if env_name:
                auth = self.auths.get(env_name, self.auths.get('*'))
                if auth:
                    remote_user = auth.do_auth(environ, start_response)
                    if not remote_user:
                        return []
                    environ['REMOTE_USER'] = remote_user
        return self.application(environ, start_response)

class BasePathMiddleware(object):

    def __init__(self, application, base_path):
        self.base_path = '/' + base_path.strip('/')
        self.application = application

    def __call__(self, environ, start_response):
        path = environ['SCRIPT_NAME'] + environ.get('PATH_INFO', '')
        environ['PATH_INFO'] = path[len(self.base_path):]
        environ['SCRIPT_NAME'] = self.base_path
        return self.application(environ, start_response)

class TracEnvironMiddleware(object):

    def __init__(self, application, env_parent_dir, env_paths, single_env):
        self.application = application
        self.environ = {}
        self.environ['trac.env_path'] = None
        if env_parent_dir:
            self.environ['trac.env_parent_dir'] = env_parent_dir
        elif single_env:
            self.environ['trac.env_path'] = env_paths[0]
        else:
            self.environ['trac.env_paths'] = env_paths

    def __call__(self, environ, start_response):
        for k, v in self.environ.iteritems():
            environ.setdefault(k, v)
        return self.application(environ, start_response)

class TracHTTPServer(ThreadingMixIn, WSGIServer):
    daemon_threads = True

    def __init__(self, server_address, application, env_parent_dir, env_paths,
                 use_http_11=False):
        request_handlers = (TracHTTPRequestHandler, TracHTTP11RequestHandler)
        WSGIServer.__init__(self, server_address, application,
                            request_handler=request_handlers[bool(use_http_11)])

    if sys.version_info < (2, 6):
        def serve_forever(self, poll_interval=0.5):
            while True:
                r, w, e = select.select([self], [], [], poll_interval)
                if self in r:
                    self.handle_request()

class TracHTTPRequestHandler(WSGIRequestHandler):

    server_version = 'tracd/' + VERSION

    def address_string(self):
        # Disable reverse name lookups
        return self.client_address[:2][0]

class TracHTTP11RequestHandler(TracHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'

def main():
    from optparse import OptionParser, OptionValueError
    parser = OptionParser(usage='usage: %prog [options] [projenv] ...',
                          version='%%prog %s' % VERSION)

    auths = {}
    def _auth_callback(option, opt_str, value, parser, cls):
        info = value.split(',', 3)
        if len(info) != 3:
            raise OptionValueError("Incorrect number of parameters for %s"
                                   % option)

        env_name, filename, realm = info
        if env_name in auths:
            print >> sys.stderr, 'Ignoring duplicate authentication option ' \
                                 'for project: %s' % env_name
        else:
            auths[env_name] = cls(os.path.abspath(filename), realm)

    def _validate_callback(option, opt_str, value, parser, valid_values):
        if value not in valid_values:
            raise OptionValueError('%s must be one of: %s, not %s'
                                   % (opt_str, '|'.join(valid_values), value))
        setattr(parser.values, option.dest, value)

    def _octal(option, opt_str, value, parser):
        try:
            setattr(parser.values, option.dest, int(value, 8))
        except ValueError:
            raise OptionValueError('Invalid octal umask value: %r' % value)

    parser.add_option('-a', '--auth', action='callback', type='string',
                      metavar='DIGESTAUTH', callback=_auth_callback,
                      callback_args=(DigestAuthentication,),
                      help='[projectdir],[htdigest_file],[realm]')
    parser.add_option('--basic-auth', action='callback', type='string',
                      metavar='BASICAUTH', callback=_auth_callback,
                      callback_args=(BasicAuthentication,),
                      help='[projectdir],[htpasswd_file],[realm]')

    parser.add_option('-p', '--port', action='store', type='int', dest='port',
                      help='the port number to bind to')
    parser.add_option('-b', '--hostname', action='store', dest='hostname',
                      help='the host name or IP address to bind to')
    parser.add_option('--protocol', action='callback', type="string",
                      dest='protocol', callback=_validate_callback,
                      callback_args=(('http', 'scgi', 'ajp', 'fcgi'),),
                      help='http|scgi|ajp|fcgi')
    parser.add_option('-q', '--unquote', action='store_true',
                      dest='unquote',
                      help='unquote PATH_INFO (may be needed when using ajp)')
    parser.add_option('--http10', action='store_false', dest='http11',
                      help='use HTTP/1.0 protocol version instead of HTTP/1.1')
    parser.add_option('--http11', action='store_true', dest='http11',
                      help='use HTTP/1.1 protocol version (default)')
    parser.add_option('-e', '--env-parent-dir', action='store',
                      dest='env_parent_dir', metavar='PARENTDIR',
                      help='parent directory of the project environments')
    parser.add_option('--base-path', action='store', type='string', # XXX call this url_base_path?
                      dest='base_path',
                      help='the initial portion of the request URL\'s "path"')

    parser.add_option('-r', '--auto-reload', action='store_true',
                      dest='autoreload',
                      help='restart automatically when sources are modified')

    parser.add_option('-s', '--single-env', action='store_true',
                      dest='single_env', help='only serve a single '
                      'project without the project list', default=False)

    if os.name == 'posix':
        parser.add_option('-d', '--daemonize', action='store_true',
                          dest='daemonize',
                          help='run in the background as a daemon')
        parser.add_option('--pidfile', action='store',
                          dest='pidfile',
                          help='when daemonizing, file to which to write pid')
        parser.add_option('--umask', action='callback', type='string',
                          dest='umask', metavar='MASK', callback=_octal,
                          help='when daemonizing, file mode creation mask '
                          'to use, in octal notation (default 022)')

        try:
            import grp, pwd

            def _group(option, opt_str, value, parser):
                try:
                    value = int(value)
                except ValueError:
                    try:
                        value = grp.getgrnam(value)[2]
                    except KeyError:
                        raise OptionValueError('group not found: %r' % value)
                setattr(parser.values, option.dest, value)

            def _user(option, opt_str, value, parser):
                try:
                    value = int(value)
                except ValueError:
                    try:
                        value = pwd.getpwnam(value)[2]
                    except KeyError:
                        raise OptionValueError('user not found: %r' % value)
                setattr(parser.values, option.dest, value)

            parser.add_option('--group', action='callback', type='string',
                              dest='group', metavar='GROUP', callback=_group,
                              help='the group to run as')
            parser.add_option('--user', action='callback', type='string',
                              dest='user', metavar='USER', callback=_user,
                              help='the user to run as')
        except ImportError:
            pass

    parser.set_defaults(port=None, hostname='', base_path='', daemonize=False,
                        protocol='http', http11=True, umask=022, user=None,
                        group=None)
    options, args = parser.parse_args()

    if not args and not options.env_parent_dir:
        parser.error('either the --env-parent-dir option or at least one '
                     'environment must be specified')
    if options.single_env:
        if options.env_parent_dir:
            parser.error('the --single-env option cannot be used with '
                         '--env-parent-dir')
        elif len(args) > 1:
            parser.error('the --single-env option cannot be used with '
                         'more than one enviroment')
    if options.daemonize and options.autoreload:
        parser.error('the --auto-reload option cannot be used with '
                     '--daemonize')

    if options.port is None:
        options.port = {
            'http': 80,
            'scgi': 4000,
            'ajp': 8009,
            'fcgi': 8000,
        }[options.protocol]
    server_address = (options.hostname, options.port)

    # relative paths don't work when daemonized
    args = [os.path.abspath(a) for a in args]
    if options.env_parent_dir:
        options.env_parent_dir = os.path.abspath(options.env_parent_dir)
    if parser.has_option('pidfile') and options.pidfile:
        options.pidfile = os.path.abspath(options.pidfile)

    wsgi_app = TracEnvironMiddleware(dispatch_request,
                                     options.env_parent_dir, args,
                                     options.single_env)
    if auths:
        if options.single_env:
            project_name = os.path.basename(args[0])
            wsgi_app = AuthenticationMiddleware(wsgi_app, auths, project_name)
        else:
            wsgi_app = AuthenticationMiddleware(wsgi_app, auths)
    base_path = options.base_path.strip('/')
    if base_path:
        wsgi_app = BasePathMiddleware(wsgi_app, base_path)

    if options.protocol == 'http':
        def serve():
            addr, port = server_address
            if not addr or addr == '0.0.0.0':
                loc = '0.0.0.0:%s view at http://127.0.0.1:%s/%s' \
                       % (port, port, base_path)
            else:
                loc = 'http://%s:%s/%s' % (addr, port, base_path)

            try:
                httpd = TracHTTPServer(server_address, wsgi_app,
                                       options.env_parent_dir, args,
                                       use_http_11=options.http11)
            except socket.error, e:
                print 'Error starting Trac server on %s' % loc
                print '[Errno %s] %s' % e.args
                sys.exit(1)

            print 'Server starting in PID %i.' % os.getpid()
            print 'Serving on %s' % loc
            if options.http11:
                print 'Using HTTP/1.1 protocol version'
            httpd.serve_forever()
    elif options.protocol in ('scgi', 'ajp', 'fcgi'):
        def serve():
            server_cls = __import__('flup.server.%s' % options.protocol,
                                    None, None, ['']).WSGIServer
            flup_app = wsgi_app
            if options.unquote:
                from trac.web.fcgi_frontend import FlupMiddleware
                flup_app = FlupMiddleware(flup_app)
            ret = server_cls(flup_app, bindAddress=server_address).run()
            sys.exit(42 if ret else 0) # if SIGHUP exit with status 42

    try:
        if options.daemonize:
            daemon.daemonize(pidfile=options.pidfile, progname='tracd',
                             umask=options.umask)
        if options.group is not None:
            os.setgid(options.group)
        if options.user is not None:
            os.setuid(options.user)

        if options.autoreload:
            def modification_callback(file):
                print >> sys.stderr, 'Detected modification of %s, ' \
                                     'restarting.' % file
            autoreload.main(serve, modification_callback)
        else:
            serve()

    except OSError, e:
        print >> sys.stderr, '%s: %s' % (e.__class__.__name__, e)
        sys.exit(1)
    except KeyboardInterrupt:
        pass

if __name__ == '__main__':
    pkg_resources.require('Trac==%s' % VERSION)
    main()
<|endoftext|>


<commit_before>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# lambda-tools documentation build configuration file, created by
# sphinx-quickstart on Sat Nov 18 20:30:50 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.intersphinx',
    'sphinx.ext.todo',
    'sphinx.ext.viewcode']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = 'lambda-tools'
copyright = '2017, James McKay'
author = 'James McKay'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

html_context = {
    'css_files': [
        '_static/theme_overrides.css',  # override wide tables in RTD theme
        ],
     }

# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
    '**': [
        'relations.html',  # needs 'show_related': True theme option to display
        'searchbox.html',
    ]
}

# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = 'lambda-toolsdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #
    # 'papersize': 'letterpaper',

    # The font size ('10pt', '11pt' or '12pt').
    #
    # 'pointsize': '10pt',

    # Additional stuff for the LaTeX preamble.
    #
    # 'preamble': '',

    # Latex figure (float) alignment
    #
    # 'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, 'lambda-tools.tex', 'lambda-tools Documentation',
     'James McKay', 'manual'),
]

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'lambda-tools', 'lambda-tools Documentation',
     [author], 1)
]

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (master_doc, 'lambda-tools', 'lambda-tools Documentation',
     author, 'lambda-tools', 'One line description of project.',
     'Miscellaneous'),
]

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
<commit_msg>Suspend CSS fixups<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# lambda-tools documentation build configuration file, created by
# sphinx-quickstart on Sat Nov 18 20:30:50 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.intersphinx',
    'sphinx.ext.todo',
    'sphinx.ext.viewcode']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = 'lambda-tools'
copyright = '2017, James McKay'
author = 'James McKay'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# html_context = {
#     'css_files': [
#         '_static/theme_overrides.css',  # override wide tables in RTD theme
#         ],
#      }

# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
    '**': [
        'relations.html',  # needs 'show_related': True theme option to display
        'searchbox.html',
    ]
}

# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = 'lambda-toolsdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #
    # 'papersize': 'letterpaper',

    # The font size ('10pt', '11pt' or '12pt').
    #
    # 'pointsize': '10pt',

    # Additional stuff for the LaTeX preamble.
    #
    # 'preamble': '',

    # Latex figure (float) alignment
    #
    # 'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, 'lambda-tools.tex', 'lambda-tools Documentation',
     'James McKay', 'manual'),
]

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'lambda-tools', 'lambda-tools Documentation',
     [author], 1)
]

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (master_doc, 'lambda-tools', 'lambda-tools Documentation',
     author, 'lambda-tools', 'One line description of project.',
     'Miscellaneous'),
]

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/': None}
<|endoftext|>


<commit_before>"""
homeassistant.actors
~~~~~~~~~~~~~~~~~~~~

This module provides actors that will react
to events happening within homeassistant.

"""

import os
import logging
from datetime import datetime, timedelta
import re

import requests

import homeassistant as ha
import homeassistant.util as util
from homeassistant.observers import (
    STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON, SUN_STATE_ABOVE_HORIZON,
    STATE_CATEGORY_ALL_DEVICES, DEVICE_STATE_HOME, DEVICE_STATE_NOT_HOME,
    STATE_ATTRIBUTE_NEXT_SUN_SETTING)

LIGHT_TRANSITION_TIME = timedelta(minutes=15)

EVENT_DOWNLOAD_FILE = "download_file"
EVENT_BROWSE_URL = "browse_url"
EVENT_CHROMECAST_YOUTUBE_VIDEO = "chromecast.play_youtube_video"
EVENT_TURN_LIGHT_ON = "turn_light_on"
EVENT_TURN_LIGHT_OFF = "turn_light_off"
EVENT_KEYBOARD_VOLUME_UP = "keyboard.volume_up"
EVENT_KEYBOARD_VOLUME_DOWN = "keyboard.volume_down"
EVENT_KEYBOARD_VOLUME_MUTE = "keyboard.volume_mute"
EVENT_KEYBOARD_MEDIA_PLAY_PAUSE = "keyboard.media_play_pause"

def _hue_process_transition_time(transition_seconds):
    """ Transition time is in 1/10th seconds
        and cannot exceed MAX_TRANSITION_TIME. """

    # Max transition time for Hue is 900 seconds/15 minutes
    return min(9000, transition_seconds * 10)

# pylint: disable=too-few-public-methods
class LightTrigger(object):
    """ Class to turn on lights based on state of devices and the sun
        or triggered by light events. """

    def __init__(self, eventbus, statemachine, device_tracker, light_control):
        self.eventbus = eventbus
        self.statemachine = statemachine
        self.light_control = light_control

        self.logger = logging.getLogger(__name__)

        # Track home coming of each seperate device
        for category in device_tracker.device_state_categories():
            ha.track_state_change(eventbus, category,
                               DEVICE_STATE_NOT_HOME, DEVICE_STATE_HOME,
                               self._handle_device_state_change)

        # Track when all devices are gone to shut down lights
        ha.track_state_change(eventbus, STATE_CATEGORY_ALL_DEVICES,
                           DEVICE_STATE_HOME, DEVICE_STATE_NOT_HOME,
                           self._handle_device_state_change)

        # Track every time sun rises so we can schedule a time-based
        # pre-sun set event
        ha.track_state_change(eventbus, STATE_CATEGORY_SUN,
                           SUN_STATE_BELOW_HORIZON, SUN_STATE_ABOVE_HORIZON,
                           self._handle_sun_rising)

        # If the sun is already above horizon
        # schedule the time-based pre-sun set event
        if statemachine.is_state(STATE_CATEGORY_SUN, SUN_STATE_ABOVE_HORIZON):
            self._handle_sun_rising(None, None, None)

        def handle_light_event(event):
            """ Hande a turn light on or off event. """
            light_id = event.data.get("light_id", None)
            transition_seconds = event.data.get("transition_seconds", None)

            if event.event_type == EVENT_TURN_LIGHT_ON:
                self.light_control.turn_light_on(light_id, transition_seconds)
            else:
                self.light_control.turn_light_off(light_id, transition_seconds)

        # Listen for light on and light off events
        eventbus.listen(EVENT_TURN_LIGHT_ON, handle_light_event)
        eventbus.listen(EVENT_TURN_LIGHT_OFF, handle_light_event)

    # pylint: disable=unused-argument
    def _handle_sun_rising(self, category, old_state, new_state):
        """The moment sun sets we want to have all the lights on.
           We will schedule to have each light start after one another
           and slowly transition in."""

        start_point = self._time_for_light_before_sun_set()

        def turn_on(light):
            """ Lambda can keep track of function parameters but not local
            parameters. If we put the lambda directly in the below statement
            only the last light would be turned on.. """
            return lambda now: self._turn_light_on_before_sunset(light)

        for index, light_id in enumerate(self.light_control.light_ids):
            ha.track_time_change(self.eventbus, turn_on(light_id),
                              point_in_time=start_point +
                                            index * LIGHT_TRANSITION_TIME)

    def _turn_light_on_before_sunset(self, light_id=None):
        """ Helper function to turn on lights slowly if there
            are devices home and the light is not on yet. """
        if self.statemachine.is_state(STATE_CATEGORY_ALL_DEVICES,
           DEVICE_STATE_HOME) and not self.light_control.is_light_on(light_id):

            self.light_control.turn_light_on(light_id,
                                             LIGHT_TRANSITION_TIME.seconds)

    def _handle_device_state_change(self, category, old_state, new_state):
        """ Function to handle tracked device state changes. """
        lights_are_on = self.light_control.is_light_on()

        light_needed = (not lights_are_on and
                        self.statemachine.is_state(STATE_CATEGORY_SUN,
                                                  SUN_STATE_BELOW_HORIZON))

        # Specific device came home ?
        if (category != STATE_CATEGORY_ALL_DEVICES and
            new_state['state'] == DEVICE_STATE_HOME):

            # These variables are needed for the elif check
            now = datetime.now()
            start_point = self._time_for_light_before_sun_set()

            # Do we need lights?
            if light_needed:

                self.logger.info(
                    "Home coming event for {}. Turning lights on".
                        format(category))

                self.light_control.turn_light_on()

            # Are we in the time span were we would turn on the lights
            # if someone would be home?
            # Check this by seeing if current time is later then the point
            # in time when we would start putting the lights on.
            elif start_point < now < self._next_sun_setting():

                # Check for every light if it would be on if someone was home
                # when the fading in started and turn it on if so
                for index, light_id in enumerate(self.light_control.light_ids):

                    if now > start_point + index * LIGHT_TRANSITION_TIME:
                        self.light_control.turn_light_on(light_id)

                    else:
                        # If this light didn't happen to be turned on yet so
                        # will all the following then, break.
                        break

        # Did all devices leave the house?
        elif (category == STATE_CATEGORY_ALL_DEVICES and
              new_state['state'] == DEVICE_STATE_NOT_HOME and lights_are_on):

            self.logger.info(("Everyone has left but lights are on. "
                              "Turning lights off"))
            self.light_control.turn_light_off()

    def _next_sun_setting(self):
        """ Returns the datetime object representing the next sun setting. """
        state = self.statemachine.get_state(STATE_CATEGORY_SUN)

        return ha.str_to_datetime(
                        state['attributes'][STATE_ATTRIBUTE_NEXT_SUN_SETTING])

    def _time_for_light_before_sun_set(self):
        """ Helper method to calculate the point in time we have to start
        fading in lights so that all the lights are on the moment the sun
        sets.
        """

        return (self._next_sun_setting() -
            LIGHT_TRANSITION_TIME * len(self.light_control.light_ids))

class HueLightControl(object):
    """ Class to interface with the Hue light system. """

    def __init__(self, host=None):
        try:
            import phue
        except ImportError:
            logging.getLogger(__name__).exception(("HueLightControl:"
                        "Error while importing dependency phue."))

            self.success_init = False

            return

        self.bridge = phue.Bridge(host)
        self.lights = self.bridge.get_light_objects()
        self.light_ids = [light.light_id for light in self.lights]

        self.success_init = True

    def is_light_on(self, light_id=None):
        """ Returns if specified or all light are on. """
        if not light_id:
            return sum([1 for light in self.lights if light.on]) > 0

        else:
            return self.bridge.get_light(light_id, 'on')

    def turn_light_on(self, light_id=None, transition_seconds=None):
        """ Turn the specified or all lights on. """
        if not light_id:
            light_id = self.light_ids

        command = {'on': True, 'xy': [0.5119, 0.4147], 'bri':164}

        if transition_seconds:
            command['transitiontime'] = _hue_process_transition_time(
                                                    transition_seconds)

        self.bridge.set_light(light_id, command)

    def turn_light_off(self, light_id=None, transition_seconds=None):
        """ Turn the specified or all lights off. """
        if not light_id:
            light_id = self.light_ids

        command = {'on': False}

        if transition_seconds:
            command['transitiontime'] = _hue_process_transition_time(
                                                    transition_seconds)

        self.bridge.set_light(light_id, command)

def setup_file_downloader(eventbus, download_path):
    """ Listens for download events to download files. """

    logger = logging.getLogger(__name__)

    if not os.path.isdir(download_path):

        logger.error(
            ("FileDownloader:"
             "Download path {} does not exist. File Downloader not active.").
            format(download_path))

        return False

    def download_file(event):
        """ Downloads file specified in the url. """

        try:
            req = requests.get(event.data['url'], stream=True)
            if req.status_code == 200:
                filename = None

                if 'content-disposition' in req.headers:
                    match = re.findall(r"filename=(\S+)",
                                       req.headers['content-disposition'])

                    if len(match) > 0:
                        filename = match[0].strip("'\" ")

                if not filename:
                    filename = os.path.basename(event.data['url']).strip()

                if not filename:
                    filename = "ha_download"

                # Remove stuff to ruin paths
                filename = util.sanitize_filename(filename)

                path, ext = os.path.splitext(os.path.join(download_path,
                                                          filename))

                # If file exist append a number. We test filename, filename_2..
                tries = 0
                while True:
                    tries += 1

                    name_suffix = "" if tries == 1 else "_{}".format(tries)
                    final_path = path + name_suffix + ext

                    if not os.path.isfile(final_path):
                        break

                logger.info("FileDownloader:{} -> {}".format(
                            event.data['url'], final_path))

                with open(final_path, 'wb') as fil:
                    for chunk in req.iter_content(1024):
                        fil.write(chunk)

        except requests.exceptions.ConnectionError:
            logger.exception("FileDownloader:ConnectionError occured for {}".
                format(event.data['url']))

    eventbus.listen(EVENT_DOWNLOAD_FILE, download_file)

    return True

def setup_webbrowser(eventbus):
    """ Listen for browse_url events and open
        the url in the default webbrowser. """

    import webbrowser

    eventbus.listen(EVENT_BROWSE_URL,
      lambda event: webbrowser.open(event.data['url']))

    return True

def setup_chromecast(eventbus, host):
    """ Listen for chromecast events. """
    from homeassistant.packages import pychromecast

    eventbus.listen("start_fireplace",
      lambda event: pychromecast.play_youtube_video(host, "eyU3bRy2x44"))

    eventbus.listen("start_epic_sax",
      lambda event: pychromecast.play_youtube_video(host, "kxopViU98Xo"))

    eventbus.listen(EVENT_CHROMECAST_YOUTUBE_VIDEO,
      lambda event: pychromecast.play_youtube_video(host, event.data['video']))

    return True

def setup_media_buttons(eventbus):
    """ Listen for keyboard events. """
    try:
        import pykeyboard
    except ImportError:
        logging.getLogger(__name__).exception(("MediaButtons:"
                    "Error while importing dependency PyUserInput."))

        return False

    keyboard = pykeyboard.PyKeyboard()
    keyboard.special_key_assignment()

    eventbus.listen(EVENT_KEYBOARD_VOLUME_UP,
      lambda event: keyboard.tap_key(keyboard.volume_up_key))

    eventbus.listen(EVENT_KEYBOARD_VOLUME_DOWN,
      lambda event: keyboard.tap_key(keyboard.volume_down_key))

    eventbus.listen(EVENT_KEYBOARD_VOLUME_MUTE,
      lambda event: keyboard.tap_key(keyboard.volume_mute_key))

    eventbus.listen(EVENT_KEYBOARD_MEDIA_PLAY_PAUSE,
      lambda event: keyboard.tap_key(keyboard.media_play_pause_key))

    return True
<commit_msg>Added support for keyboard prev and next track buttons<commit_after>"""
homeassistant.actors
~~~~~~~~~~~~~~~~~~~~

This module provides actors that will react
to events happening within homeassistant.

"""

import os
import logging
from datetime import datetime, timedelta
import re

import requests

import homeassistant as ha
import homeassistant.util as util
from homeassistant.observers import (
    STATE_CATEGORY_SUN, SUN_STATE_BELOW_HORIZON, SUN_STATE_ABOVE_HORIZON,
    STATE_CATEGORY_ALL_DEVICES, DEVICE_STATE_HOME, DEVICE_STATE_NOT_HOME,
    STATE_ATTRIBUTE_NEXT_SUN_SETTING)

LIGHT_TRANSITION_TIME = timedelta(minutes=15)

EVENT_DOWNLOAD_FILE = "download_file"
EVENT_BROWSE_URL = "browse_url"
EVENT_CHROMECAST_YOUTUBE_VIDEO = "chromecast.play_youtube_video"
EVENT_TURN_LIGHT_ON = "turn_light_on"
EVENT_TURN_LIGHT_OFF = "turn_light_off"
EVENT_KEYBOARD_VOLUME_UP = "keyboard.volume_up"
EVENT_KEYBOARD_VOLUME_DOWN = "keyboard.volume_down"
EVENT_KEYBOARD_VOLUME_MUTE = "keyboard.volume_mute"
EVENT_KEYBOARD_MEDIA_PLAY_PAUSE = "keyboard.media_play_pause"
EVENT_KEYBOARD_MEDIA_NEXT_TRACK = "keyboard.media_next_track"
EVENT_KEYBOARD_MEDIA_PREV_TRACK = "keyboard.media_prev_track"

def _hue_process_transition_time(transition_seconds):
    """ Transition time is in 1/10th seconds
        and cannot exceed MAX_TRANSITION_TIME. """

    # Max transition time for Hue is 900 seconds/15 minutes
    return min(9000, transition_seconds * 10)

# pylint: disable=too-few-public-methods
class LightTrigger(object):
    """ Class to turn on lights based on state of devices and the sun
        or triggered by light events. """

    def __init__(self, eventbus, statemachine, device_tracker, light_control):
        self.eventbus = eventbus
        self.statemachine = statemachine
        self.light_control = light_control

        self.logger = logging.getLogger(__name__)

        # Track home coming of each seperate device
        for category in device_tracker.device_state_categories():
            ha.track_state_change(eventbus, category,
                               DEVICE_STATE_NOT_HOME, DEVICE_STATE_HOME,
                               self._handle_device_state_change)

        # Track when all devices are gone to shut down lights
        ha.track_state_change(eventbus, STATE_CATEGORY_ALL_DEVICES,
                           DEVICE_STATE_HOME, DEVICE_STATE_NOT_HOME,
                           self._handle_device_state_change)

        # Track every time sun rises so we can schedule a time-based
        # pre-sun set event
        ha.track_state_change(eventbus, STATE_CATEGORY_SUN,
                           SUN_STATE_BELOW_HORIZON, SUN_STATE_ABOVE_HORIZON,
                           self._handle_sun_rising)

        # If the sun is already above horizon
        # schedule the time-based pre-sun set event
        if statemachine.is_state(STATE_CATEGORY_SUN, SUN_STATE_ABOVE_HORIZON):
            self._handle_sun_rising(None, None, None)

        def handle_light_event(event):
            """ Hande a turn light on or off event. """
            light_id = event.data.get("light_id", None)
            transition_seconds = event.data.get("transition_seconds", None)

            if event.event_type == EVENT_TURN_LIGHT_ON:
                self.light_control.turn_light_on(light_id, transition_seconds)
            else:
                self.light_control.turn_light_off(light_id, transition_seconds)

        # Listen for light on and light off events
        eventbus.listen(EVENT_TURN_LIGHT_ON, handle_light_event)
        eventbus.listen(EVENT_TURN_LIGHT_OFF, handle_light_event)

    # pylint: disable=unused-argument
    def _handle_sun_rising(self, category, old_state, new_state):
        """The moment sun sets we want to have all the lights on.
           We will schedule to have each light start after one another
           and slowly transition in."""

        start_point = self._time_for_light_before_sun_set()

        def turn_on(light):
            """ Lambda can keep track of function parameters but not local
            parameters. If we put the lambda directly in the below statement
            only the last light would be turned on.. """
            return lambda now: self._turn_light_on_before_sunset(light)

        for index, light_id in enumerate(self.light_control.light_ids):
            ha.track_time_change(self.eventbus, turn_on(light_id),
                              point_in_time=start_point +
                                            index * LIGHT_TRANSITION_TIME)

    def _turn_light_on_before_sunset(self, light_id=None):
        """ Helper function to turn on lights slowly if there
            are devices home and the light is not on yet. """
        if self.statemachine.is_state(STATE_CATEGORY_ALL_DEVICES,
           DEVICE_STATE_HOME) and not self.light_control.is_light_on(light_id):

            self.light_control.turn_light_on(light_id,
                                             LIGHT_TRANSITION_TIME.seconds)

    def _handle_device_state_change(self, category, old_state, new_state):
        """ Function to handle tracked device state changes. """
        lights_are_on = self.light_control.is_light_on()

        light_needed = (not lights_are_on and
                        self.statemachine.is_state(STATE_CATEGORY_SUN,
                                                  SUN_STATE_BELOW_HORIZON))

        # Specific device came home ?
        if (category != STATE_CATEGORY_ALL_DEVICES and
            new_state['state'] == DEVICE_STATE_HOME):

            # These variables are needed for the elif check
            now = datetime.now()
            start_point = self._time_for_light_before_sun_set()

            # Do we need lights?
            if light_needed:

                self.logger.info(
                    "Home coming event for {}. Turning lights on".
                        format(category))

                self.light_control.turn_light_on()

            # Are we in the time span were we would turn on the lights
            # if someone would be home?
            # Check this by seeing if current time is later then the point
            # in time when we would start putting the lights on.
            elif start_point < now < self._next_sun_setting():

                # Check for every light if it would be on if someone was home
                # when the fading in started and turn it on if so
                for index, light_id in enumerate(self.light_control.light_ids):

                    if now > start_point + index * LIGHT_TRANSITION_TIME:
                        self.light_control.turn_light_on(light_id)

                    else:
                        # If this light didn't happen to be turned on yet so
                        # will all the following then, break.
                        break

        # Did all devices leave the house?
        elif (category == STATE_CATEGORY_ALL_DEVICES and
              new_state['state'] == DEVICE_STATE_NOT_HOME and lights_are_on):

            self.logger.info(("Everyone has left but lights are on. "
                              "Turning lights off"))
            self.light_control.turn_light_off()

    def _next_sun_setting(self):
        """ Returns the datetime object representing the next sun setting. """
        state = self.statemachine.get_state(STATE_CATEGORY_SUN)

        return ha.str_to_datetime(
                        state['attributes'][STATE_ATTRIBUTE_NEXT_SUN_SETTING])

    def _time_for_light_before_sun_set(self):
        """ Helper method to calculate the point in time we have to start
        fading in lights so that all the lights are on the moment the sun
        sets.
        """

        return (self._next_sun_setting() -
            LIGHT_TRANSITION_TIME * len(self.light_control.light_ids))

class HueLightControl(object):
    """ Class to interface with the Hue light system. """

    def __init__(self, host=None):
        try:
            import phue
        except ImportError:
            logging.getLogger(__name__).exception(("HueLightControl:"
                        "Error while importing dependency phue."))

            self.success_init = False

            return

        self.bridge = phue.Bridge(host)
        self.lights = self.bridge.get_light_objects()
        self.light_ids = [light.light_id for light in self.lights]

        self.success_init = True

    def is_light_on(self, light_id=None):
        """ Returns if specified or all light are on. """
        if not light_id:
            return sum([1 for light in self.lights if light.on]) > 0

        else:
            return self.bridge.get_light(light_id, 'on')

    def turn_light_on(self, light_id=None, transition_seconds=None):
        """ Turn the specified or all lights on. """
        if not light_id:
            light_id = self.light_ids

        command = {'on': True, 'xy': [0.5119, 0.4147], 'bri':164}

        if transition_seconds:
            command['transitiontime'] = _hue_process_transition_time(
                                                    transition_seconds)

        self.bridge.set_light(light_id, command)

    def turn_light_off(self, light_id=None, transition_seconds=None):
        """ Turn the specified or all lights off. """
        if not light_id:
            light_id = self.light_ids

        command = {'on': False}

        if transition_seconds:
            command['transitiontime'] = _hue_process_transition_time(
                                                    transition_seconds)

        self.bridge.set_light(light_id, command)

def setup_file_downloader(eventbus, download_path):
    """ Listens for download events to download files. """

    logger = logging.getLogger(__name__)

    if not os.path.isdir(download_path):

        logger.error(
            ("FileDownloader:"
             "Download path {} does not exist. File Downloader not active.").
            format(download_path))

        return False

    def download_file(event):
        """ Downloads file specified in the url. """

        try:
            req = requests.get(event.data['url'], stream=True)
            if req.status_code == 200:
                filename = None

                if 'content-disposition' in req.headers:
                    match = re.findall(r"filename=(\S+)",
                                       req.headers['content-disposition'])

                    if len(match) > 0:
                        filename = match[0].strip("'\" ")

                if not filename:
                    filename = os.path.basename(event.data['url']).strip()

                if not filename:
                    filename = "ha_download"

                # Remove stuff to ruin paths
                filename = util.sanitize_filename(filename)

                path, ext = os.path.splitext(os.path.join(download_path,
                                                          filename))

                # If file exist append a number. We test filename, filename_2..
                tries = 0
                while True:
                    tries += 1

                    name_suffix = "" if tries == 1 else "_{}".format(tries)
                    final_path = path + name_suffix + ext

                    if not os.path.isfile(final_path):
                        break

                logger.info("FileDownloader:{} -> {}".format(
                            event.data['url'], final_path))

                with open(final_path, 'wb') as fil:
                    for chunk in req.iter_content(1024):
                        fil.write(chunk)

        except requests.exceptions.ConnectionError:
            logger.exception("FileDownloader:ConnectionError occured for {}".
                format(event.data['url']))

    eventbus.listen(EVENT_DOWNLOAD_FILE, download_file)

    return True

def setup_webbrowser(eventbus):
    """ Listen for browse_url events and open
        the url in the default webbrowser. """

    import webbrowser

    eventbus.listen(EVENT_BROWSE_URL,
      lambda event: webbrowser.open(event.data['url']))

    return True

def setup_chromecast(eventbus, host):
    """ Listen for chromecast events. """
    from homeassistant.packages import pychromecast

    eventbus.listen("start_fireplace",
      lambda event: pychromecast.play_youtube_video(host, "eyU3bRy2x44"))

    eventbus.listen("start_epic_sax",
      lambda event: pychromecast.play_youtube_video(host, "kxopViU98Xo"))

    eventbus.listen(EVENT_CHROMECAST_YOUTUBE_VIDEO,
      lambda event: pychromecast.play_youtube_video(host, event.data['video']))

    return True

def setup_media_buttons(eventbus):
    """ Listen for keyboard events. """
    try:
        import pykeyboard
    except ImportError:
        logging.getLogger(__name__).exception(("MediaButtons:"
                    "Error while importing dependency PyUserInput."))

        return False

    keyboard = pykeyboard.PyKeyboard()
    keyboard.special_key_assignment()

    eventbus.listen(EVENT_KEYBOARD_VOLUME_UP,
      lambda event: keyboard.tap_key(keyboard.volume_up_key))

    eventbus.listen(EVENT_KEYBOARD_VOLUME_DOWN,
      lambda event: keyboard.tap_key(keyboard.volume_down_key))

    eventbus.listen(EVENT_KEYBOARD_VOLUME_MUTE,
      lambda event: keyboard.tap_key(keyboard.volume_mute_key))

    eventbus.listen(EVENT_KEYBOARD_MEDIA_PLAY_PAUSE,
      lambda event: keyboard.tap_key(keyboard.media_play_pause_key))

    eventbus.listen(EVENT_KEYBOARD_MEDIA_NEXT_TRACK,
      lambda event: keyboard.tap_key(keyboard.media_next_track_key))

    eventbus.listen(EVENT_KEYBOARD_MEDIA_PREV_TRACK,
      lambda event: keyboard.tap_key(keyboard.media_prev_track_key))

    return True
<|endoftext|>


<commit_before># mysql/gaerdbms.py
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""
.. dialect:: mysql+gaerdbms
    :name: Google Cloud SQL
    :dbapi: rdbms
    :connectstring: mysql+gaerdbms:///<dbname>?instance=<instancename>
    :url: https://developers.google.com/appengine/docs/python/cloud-sql/\
developers-guide

    This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with
    minimal changes.

    .. versionadded:: 0.7.8

Pooling
-------

Google App Engine connections appear to be randomly recycled,
so the dialect does not pool connections.  The :class:`.NullPool`
implementation is installed within the :class:`.Engine` by
default.

"""

import os

from .mysqldb import MySQLDialect_mysqldb
from ...pool import NullPool
import re

def _is_dev_environment():
    return os.environ.get('SERVER_SOFTWARE', '').startswith('Development/')

class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):

    @classmethod
    def dbapi(cls):
        # from django:
        # http://code.google.com/p/googleappengine/source/
        #     browse/trunk/python/google/storage/speckle/
        # python/django/backend/base.py#118
        # see also [ticket:2649]
        # see also http://stackoverflow.com/q/14224679/34549
        from google.appengine.api import apiproxy_stub_map

        if _is_dev_environment():
            from google.appengine.api import rdbms_mysqldb
            return rdbms_mysqldb
        elif apiproxy_stub_map.apiproxy.GetStub('rdbms'):
            from google.storage.speckle.python.api import rdbms_apiproxy
            return rdbms_apiproxy
        else:
            from google.storage.speckle.python.api import rdbms_googleapi
            return rdbms_googleapi

    @classmethod
    def get_pool_class(cls, url):
        # Cloud SQL connections die at any moment
        return NullPool

    def create_connect_args(self, url):
        opts = url.translate_connect_args()
        if not _is_dev_environment():
            # 'dsn' and 'instance' are because we are skipping
            # the traditional google.api.rdbms wrapper
            opts['dsn'] = ''
            opts['instance'] = url.query['instance']
        return [], opts

    def _extract_error_code(self, exception):
        match = re.compile(r"^(\d+)L?:|^\((\d+)L?,").match(str(exception))
        # The rdbms api will wrap then re-raise some types of errors
        # making this regex return no matches.
        code = match.group(1) or match.group(2) if match else None
        if code:
            return int(code)

dialect = MySQLDialect_gaerdbms
<commit_msg>Update gaerdbms to highlight improved connection method<commit_after># mysql/gaerdbms.py
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""
.. dialect:: mysql+gaerdbms
    :name: Google Cloud SQL
    :dbapi: rdbms
    :connectstring: mysql+gaerdbms:///<dbname>?instance=<instancename>
    :url: https://developers.google.com/appengine/docs/python/cloud-sql/\
developers-guide

    This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with
    minimal changes.

    .. versionadded:: 0.7.8

    .. deprecated:: 1.0 Cloud SQL now recommends creating connections via the
        mysql dialect using the URL format
        `mysql://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>`

Pooling
-------

Google App Engine connections appear to be randomly recycled,
so the dialect does not pool connections.  The :class:`.NullPool`
implementation is installed within the :class:`.Engine` by
default.

"""

import os

from .mysqldb import MySQLDialect_mysqldb
from ...pool import NullPool
import re
from sqlalchemy.util import warn_deprecated

warn_deprecated(
    "Google Cloud SQL now recommends creating connections via the "
    "mysql dialect using the URL format "
    "mysql://root@/<dbname>?unix_socket=/cloudsql/<projectid>:<instancename>"
)

def _is_dev_environment():
    return os.environ.get('SERVER_SOFTWARE', '').startswith('Development/')

class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):

    @classmethod
    def dbapi(cls):
        # from django:
        # http://code.google.com/p/googleappengine/source/
        #     browse/trunk/python/google/storage/speckle/
        # python/django/backend/base.py#118
        # see also [ticket:2649]
        # see also http://stackoverflow.com/q/14224679/34549
        from google.appengine.api import apiproxy_stub_map

        if _is_dev_environment():
            from google.appengine.api import rdbms_mysqldb
            return rdbms_mysqldb
        elif apiproxy_stub_map.apiproxy.GetStub('rdbms'):
            from google.storage.speckle.python.api import rdbms_apiproxy
            return rdbms_apiproxy
        else:
            from google.storage.speckle.python.api import rdbms_googleapi
            return rdbms_googleapi

    @classmethod
    def get_pool_class(cls, url):
        # Cloud SQL connections die at any moment
        return NullPool

    def create_connect_args(self, url):
        opts = url.translate_connect_args()
        if not _is_dev_environment():
            # 'dsn' and 'instance' are because we are skipping
            # the traditional google.api.rdbms wrapper
            opts['dsn'] = ''
            opts['instance'] = url.query['instance']
        return [], opts

    def _extract_error_code(self, exception):
        match = re.compile(r"^(\d+)L?:|^\((\d+)L?,").match(str(exception))
        # The rdbms api will wrap then re-raise some types of errors
        # making this regex return no matches.
        code = match.group(1) or match.group(2) if match else None
        if code:
            return int(code)

dialect = MySQLDialect_gaerdbms
<|endoftext|>