def init(confdict, ctx=None): """ Initializes this module acoording to the :ref:`SCORE module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`backend` :confdefault:`python` The shell backend to use. You can use either of the shells prvided by this module ("python", "ipython" and "bpython") or a string, that will be resolved using :func:`score.init.parse_dotted_path`. :confkey:`backend.autoinstall` :confdefault:`True` Whether the given *backend* should be installed automatically, if it was not found. :confkey:`callbacks` A :func:`list <score.init.parse_list>` of :func:`dotted python paths <score.init.parse_dotted_path>` to functions that will be called before the shell is spawned. Every callback will receive a `dict` representing the variables that will be available in the shell. The callbacks are free to add further variables to this list. """ conf = defaults.copy() conf.update(confdict) shell_cls = Shell.get(conf['backend']) if not shell_cls: shell_cls = parse_dotted_path(conf['backend']) backend = shell_cls(conf['backend.autoinstall']) callbacks = [] for path in parse_list(conf['callbacks']): callback = parse_dotted_path(path) if not callable(callback): raise ConfigurationError('Given callback not callable: %s' % path) callbacks.append(callback) return ConfiguredShellModule(ctx, backend, callbacks)
def _postprocess(data, objects=None): relationships = {} proxies = {} columns = {} if not objects: objects = {} classes = {} else: classes = dict((cls, parse_dotted_path(cls)) for cls in objects) for classname in data: classes[classname] = parse_dotted_path(classname) cls = classes[classname] relationships[classname] = {} proxies[classname] = {} for relationship in sa.inspect(cls).relationships: relationships[classname][relationship.key] = relationship columns[classname] = {} for column in sa.inspect(cls).columns: columns[classname][column.description] = column if classname not in objects: objects[classname] = {} objects[classname].update(dict((id, cls()) for id in data[classname])) for member in dir(cls): if member.startswith('__'): continue value = getattr(cls, member) if isinstance(value, AssociationProxy): proxies[classname][member] = value for classname in data: cls = classes[classname] for id in data[classname]: obj = objects[classname][id] if not data[classname][id]: continue for member in data[classname][id]: value = data[classname][id][member] if member in relationships[classname]: relcls = relationships[classname][member].argument if isinstance(relcls, sa.orm.Mapper): relcls = relcls.class_ else: relcls = relcls() if not isinstance(relcls, type): relcls = relcls.__class__ value = _replace_object(classes, objects, relcls, value) elif member in proxies[classname]: proxy = proxies[classname][member] col = proxy.attr[1].property.columns[0] if isinstance(col.type, type(cls)): value = _replace_object( classes, objects, relcls, value) else: value = map(lambda v: _convert_value(v, col), value) elif member in columns[classname]: value = _convert_value(value, columns[classname][member]) setattr(obj, member, value) return objects
def engine_from_config(config): """ A wrapper around :func:`sqlalchemy.engine_from_config`, that converts certain configuration values. Currently, the following configurations are processed: - ``sqlalchemy.echo`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.echo_pool`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.case_sensitive`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.module`` (using :func:`score.init.parse_dotted_path`) - ``sqlalchemy.poolclass`` (using :func:`score.init.parse_dotted_path`) - ``sqlalchemy.pool`` (using :func:`score.init.parse_call`) - ``sqlalchemy.pool_size`` (converted to `int`) - ``sqlalchemy.pool_recycle`` (converted to `int`) """ global _registered_utf8mb4 conf = dict() for key in config: if key in ('sqlalchemy.echo', 'sqlalchemy.echo_pool', 'sqlalchemy.case_sensitive'): conf[key] = parse_bool(config[key]) elif key in ('sqlalchemy.module', 'sqlalchemy.poolclass'): conf[key] = parse_dotted_path(config[key]) elif key == 'sqlalchemy.pool': conf[key] = parse_call(config[key]) elif key in ('sqlalchemy.pool_size', 'sqlalchemy.pool_recycle'): conf[key] = int(config[key]) else: conf[key] = config[key] if not _registered_utf8mb4 and 'utf8mb4' in conf.get('sqlalchemy.url', ''): import codecs codecs.register(lambda name: codecs.lookup('utf8') if name == 'utf8mb4' else None) _registered_utf8mb4 = True return sa.engine_from_config(conf)
def init(confdict, ctx=None): """ Initializes this module acoording to :ref:`our module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`sqlalchemy.*` All configuration values under this key will be passed to :func:`engine_from_config`, which in turn calls :func:`sqlalchemy.create_engine` with these configuration values as keyword arguments. Usually the following is sufficient:: sqlalchemy.url = postgresql://dbuser@localhost/projname :confkey:`base` :faint:`[default=None]` The dotted python path to the :ref:`base class <db_base>` to configure. See :func:`parse_dotted_path` for the syntax. :confkey:`destroyable` :faint:`[default=False]` Whether destructive operations may be performed on the database. This value prevents accidental deletion of important data on live servers. :confkey:`ctx.member` :faint:`[default=db]` The name of the :term:`context member`, that should be registered with the configured :mod:`score.ctx` module (if there is one). The default value allows you to always access a valid session within a :class:`score.ctx.Context` like this: >>> ctx.db.query(User).first() Providing the special value 'None' will disable registration of the context member, even if a configured :mod:`ctx module <score.ctx>` was provided. This function will initialize an sqlalchemy :ref:`Engine <sqlalchemy:engines_toplevel>` and the provided :ref:`base class <db_base>`. """ conf = defaults.copy() conf.update(confdict) engine = engine_from_config(conf) Base = None if 'base' in conf: Base = parse_dotted_path(conf['base']) Base.metadata.bind = engine ctx_member = None if ctx and conf['ctx.member'] not in (None, 'None'): ctx_member = conf['ctx.member'] db_conf = ConfiguredDbModule( engine, Base, parse_bool(conf['destroyable']), ctx_member) if ctx_member: def constructor(ctx): zope_tx = ZopeTransactionExtension( transaction_manager=ctx.tx_manager) return db_conf.Session(extension=zope_tx) ctx.register(ctx_member, constructor) return db_conf
def engine_from_config(config): """ A wrapper around :func:`sqlalchemy.engine_from_config`, that converts certain configuration values. Currently, the following configurations are processed: - ``sqlalchemy.case_sensitive`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.connect_args.cursorclass`` (using :func:`score.init.parse_dotted_path`) - ``sqlalchemy.echo`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.echo_pool`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.module`` (using :func:`score.init.parse_dotted_path`) - ``sqlalchemy.poolclass`` (using :func:`score.init.parse_dotted_path`) - ``sqlalchemy.pool`` (using :func:`score.init.parse_call`) - ``sqlalchemy.pool_size`` (converted to `int`) - ``sqlalchemy.pool_recycle`` (converted to `int`) Any other keys are used without conversion. """ conf = dict() for key in config: if key in ('sqlalchemy.echo', 'sqlalchemy.echo_pool', 'sqlalchemy.case_sensitive'): conf[key] = parse_bool(config[key]) elif key in ('sqlalchemy.module', 'sqlalchemy.poolclass'): conf[key] = parse_dotted_path(config[key]) elif key.startswith('sqlalchemy.connect_args.'): if 'sqlalchemy.connect_args' not in conf: conf['sqlalchemy.connect_args'] = {} value = config[key] key = key[len('sqlalchemy.connect_args.'):] if key == 'cursorclass': value = parse_dotted_path(value) conf['sqlalchemy.connect_args'][key] = value elif key == 'sqlalchemy.pool': conf[key] = parse_call(config[key]) elif key in ('sqlalchemy.pool_size', 'sqlalchemy.pool_recycle'): conf[key] = int(config[key]) else: conf[key] = config[key] return sa.engine_from_config(conf)
def init(confdict, ctx=None): """ Initializes this module acoording to :ref:`our module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`args.hosts` A list of hosts (as read by :func:`score.init.parse_list`) to pass to the :class:`Elasticsearch <elasticsearch.Elasticsearch>` constructor. :confkey:`args.*` Any other arguments to be passed to the :class:`Elasticsearch <elasticsearch.Elasticsearch>` constructor. :confkey:`index` :confdefault:`score` The index to use in all operations. :confkey:`keep_source` :confdefault:`False` Whether the `_source` field should be enabled. The default is `False`, since the canonical representation of objects should be in a database. :confkey:`ctx.member` :confdefault:`es` The name of the :term:`context member`, that should be registered with the configured :mod:`score.ctx` module (if there is one). The default value allows one to conveniently query the index: >>> response = ctx.es.client.search(body={"query": {"match_all": {}}}) >>> for hit in response['hits']['hits']: ... print(hit['_source']['title']) :confkey:`ctx.extensions` Additional extension classes for the context proxy. """ conf = defaults.copy() conf.update(confdict) connect_kwargs = parse_connect_conf(extract_conf(conf, 'args.')) if 'index' not in conf: conf['index'] = 'score' keep_source = parse_bool(conf['keep_source']) ctx_extensions = [DslExtension] for path in parse_list(conf['ctx.extensions']): class_ = parse_dotted_path(path) if not issubclass(class_, CtxProxy): raise InitializationError( 'score.es7', 'Ctx extensions classes must be sub-classes of CtxProxy') ctx_extensions.append(class_) es_conf = ConfiguredEs7Module(connect_kwargs, conf['index'], keep_source, ctx_extensions) if ctx and conf['ctx.member'] not in (None, 'None'): ctx.register(conf['ctx.member'], es_conf.get_ctx_proxy) return es_conf
def engine_from_config(config): """ A wrapper around :func:`sqlalchemy.engine_from_config`, that converts certain configuration values. Currently, the following configurations are processed: - ``sqlalchemy.echo`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.echo_pool`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.case_sensitive`` (using :func:`score.init.parse_bool`) - ``sqlalchemy.module`` (using :func:`score.init.parse_dotted_path`) - ``sqlalchemy.poolclass`` (using :func:`score.init.parse_dotted_path`) - ``sqlalchemy.pool`` (using :func:`score.init.parse_call`) - ``sqlalchemy.pool_size`` (converted to `int`) - ``sqlalchemy.pool_recycle`` (converted to `int`) """ if 'sqlalchemy.echo' in config: config['sqlalchemy.echo'] = parse_bool(config['sqlalchemy.echo']) if 'sqlalchemy.echo_pool' in config: config['sqlalchemy.echo_pool'] = \ parse_bool(config['sqlalchemy.echo_pool']) if 'sqlalchemy.case_sensitive' in config: config['sqlalchemy.case_sensitive'] = \ parse_bool(config['sqlalchemy.case_sensitive']) if 'sqlalchemy.module' in config: config['sqlalchemy.module'] = \ parse_dotted_path(config['sqlalchemy.module']) if 'sqlalchemy.poolclass' in config: config['sqlalchemy.poolclass'] = \ parse_dotted_path(config['sqlalchemy.poolclass']) if 'sqlalchemy.pool' in config: config['sqlalchemy.pool'] = parse_call(config['sqlalchemy.pool']) if 'sqlalchemy.pool_size' in config: config['sqlalchemy.pool_size'] = \ int(config['sqlalchemy.pool_size']) if 'sqlalchemy.pool_recycle' in config: config['sqlalchemy.pool_recycle'] = \ int(config['sqlalchemy.pool_recycle']) return sa.engine_from_config(config)
def init(confdict, ctx_conf, js_conf=None): conf = dict(defaults.items()) conf.update(confdict) server = parse_dotted_path(conf['server']) server.host = conf['host'] server.port = int(conf['port']) server.url = 'ws://%s:%d' % (server.host, server.port) if js_conf and conf['virtjs.path']: @js_conf.virtjs(conf['virtjs.path']) def ws(): # TODO: next two lines should be outside of function for increased # performance tpl = open(os.path.join(os.path.dirname(__file__), 'ws.js')).read() virtjs = tpl % conf['virtjs.name'] return virtjs ws_conf = ConfiguredWsModule(ctx_conf, server, parse_bool(conf['expose'])) server.conf = ws_conf return ws_conf
def init(confdict, ctx): """ Initializes this module acoording to :ref:`our module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`ruleset` :faint:`[default=RuleSet()]` A dotted path to an instance of :class:`RuleSet` in your project. This module will be initialized without any rules, if this configuration key is omitted, resulting in denial of every operation. :confkey:`authenticators` :faint:`[default=list()]` List of :class:`Authenticators` capable of determining the current actor. :confkey:`ctx.member` :faint:`[default=actor]` The :term:`context member` under which the current actor should be made available. Leaving this at its default will allow you to access the current actor as the following: >>> ctx.actor """ conf = defaults.copy() conf.update(confdict) if conf['ruleset'] in (None, 'None'): ruleset = RuleSet() else: ruleset = parse_dotted_path(conf['ruleset']) if 'authenticator' in conf: assert not conf['authenticators'] conf['authenticators'] = [conf['authenticator']] del conf['authenticator'] auth = ConfiguredAuthModule(ruleset, conf['ctx.member']) authenticator = NullAuthenticator() for line in reversed(parse_list(conf['authenticators'])): authenticator = parse_call(line, (auth, authenticator)) auth.authenticator = authenticator _register_ctx_actor(conf, ctx, auth) _register_ctx_permits(conf, ctx, auth) return auth
def init(confdict, ctx): """ Initializes this module acoording to :ref:`our module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`ruleset` :confdefault:`RuleSet()` A dotted path to an instance of :class:`RuleSet` in your project. This module will be initialized without any rules, if this configuration key is omitted, resulting in denial of every operation. :confkey:`authenticators` :confdefault:`list()` List of :class:`Authenticators` capable of determining the current actor. :confkey:`ctx.member` :confdefault:`actor` The :term:`context member` under which the current actor should be made available. Leaving this at its default will allow you to access the current actor as the following: >>> ctx.actor """ conf = defaults.copy() conf.update(confdict) if conf['ruleset'] in (None, 'None'): ruleset = RuleSet() else: ruleset = parse_dotted_path(conf['ruleset']) if 'authenticator' in conf: assert not conf['authenticators'] conf['authenticators'] = [conf['authenticator']] del conf['authenticator'] auth = ConfiguredAuthModule(ruleset, conf['ctx.member']) authenticator = NullAuthenticator() for line in reversed(parse_list(conf['authenticators'])): authenticator = parse_call(line, (auth, authenticator)) auth.authenticator = authenticator _register_ctx_actor(conf, ctx, auth) _register_ctx_permits(conf, ctx, auth) return auth
def _init_orm_backend(conf, session, orm, ctx): if 'orm.class' not in conf: return None if not conf['orm.class'] or conf['orm.class'] == 'None': return None if not orm: import score.session raise ConfigurationError( score.session, 'Need score.sa.orm in order to use `orm.class`') if not ctx: import score.session raise ConfigurationError(score.session, 'Need score.ctx in order to use `orm.class`') from .orm import OrmSessionMixin, OrmSession class_ = parse_dotted_path(conf['orm.class']) if not issubclass(class_, OrmSessionMixin): import score.session raise ConfigurationError( score.session, 'Configured `orm.class` must inherit OrmSessionMixin') if not hasattr(orm, 'ctx'): import score.session raise ConfigurationError( score.session, 'Configured score.sa.orm has not score.ctx configuration') if ctx != orm.ctx: import score.session raise ConfigurationError( score.session, 'Configured score.sa.orm uses different score.ctx dependency') return type( 'ConfiguredOrmSession', (OrmSession, ), { '_has_ctx': ctx is not None, '_conf': session, '_orm_conf': orm, '_orm_class': class_, '_orm': property(lambda self: orm.get_session(self._ctx)), })
def _init_db_backend(conf, session, db, ctx): if not db: return None if 'db.class' not in conf: return None if not conf['db.class'] or conf['db.class'] == 'None': return None from .db import DbSessionMixin, DbSession from zope.sqlalchemy import ZopeTransactionExtension class_ = parse_dotted_path(conf['db.class']) if not issubclass(class_, DbSessionMixin): import score.session raise ConfigurationError( score.session, 'Configured `db.class` must inherit DbSessionMixin') if ctx and db.ctx_member: def session(self): return getattr(self._ctx, db.ctx_member) elif ctx: def session(self): if not hasattr(self, '_db_session'): zope_tx = ZopeTransactionExtension( transaction_manager=self._ctx.tx_manager) self._db_session = db.Session(extension=zope_tx) return self._db_session else: def session(self): if not hasattr(self, '_db_session'): self._db_session = db.Session(extension=[]) return self._db_session return type('ConfiguredDbSession', (DbSession,), { '_has_ctx': ctx is not None, '_conf': session, '_db_conf': db, '_db_class': class_, '_db': property(session), })
def init(confdict): """ Initializes this module according to :ref:`our module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`container` The cache container configuration. A container defines a name, a backend and optionally a generator and an expire. The configuration key for a container starts with ``container`` followed by the name and the configuration keys for the container. For example, the following configuration:: container.greeter.backend = score.kvcache.backend.FileCache container.greeter.backend.path = /tmp/greeter.sqlite3 container.greeter.generator = dotted.path.to.greeting_generator container.greeter.expire = 1m The Backend config will be passed to :func:`score.init.init_object`. Have a look at the configurable backend's constructor parameters for further information about the backend's configurable keys. To make life easier for a huge set of container configurations, we serve the possibility to configure backend aliases that will replace the container's backend config if the name matches. For example:: backend.example_filecache = score.kvcache.backend.FileCache backend.example_filecache.path = /tmp/filecache.sqlite3 container.greeter.backend = example_filecache container.greeter.generator = dotted.path.to.greeting_generator container.greeter.expire = 1m container.counter.backend = example_filecache container.counter.generator = dotted.path.to.counting_generator container.counter.expire = 30 seconds """ containers = {} for container_conf in extract_conf(confdict, 'container.'): if not container_conf.endswith('.backend'): continue backend_key = 'container.%s' % container_conf backend_val = confdict[backend_key] if backend_val in extract_conf(confdict, 'backend.'): alias_conf = extract_conf(confdict, 'backend.%s' % backend_val) for k, v in alias_conf.items(): confdict.update({'%s%s' % (backend_key, k): v}) container_name = container_conf[:-len('.backend')] backend = parse_object(confdict, backend_key) generator_key = 'container.%s.generator' % container_name generator = None if generator_key in confdict: generator = parse_dotted_path(confdict[generator_key]) expire_key = 'container.%s.expire' % container_name expire = None if expire_key in confdict: expire = parse_time_interval(confdict[expire_key]) containers[container_name] = CacheContainer(container_name, backend, generator=generator, expire=expire) return ConfiguredKvCacheModule(containers)
def __init__(self, conf, next, actor_class=None, session_key='actor'): super().__init__(conf, next) self.session_key = session_key if isinstance(actor_class, str): actor_class = parse_dotted_path(actor_class) self.dbcls = actor_class
def init(confdict, ctx, db=None): """ Initializes this module acoording to :ref:`our module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`router` Path to the :class:`RouteConfiguration` containing the list of routes to compile. :confkey:`preroutes` :confdefault:`list()` List of :term:`preroute` functions to call before invoking the actual route. See :ref:`http_routing` for details. :confkey:`handler.*` Keys starting with "``handler.``" are interpreted as :ref:`error handlers <http_error_handler>`. :confkey:`debug` :confdefault:`False` Setting this to `True` will enable the `werkzeug debugger`_ for your application. :confkey:`urlbase` :confdefault:`None` This will be the prefix for all URLs generated by the module. The module will create relative URLs by default (i.e. `/Sir%20Lancelot`), but you can make it create absolute URLs by default by paassing this configuration value. If you configure this to be 'http://example.net/', your URL would be 'http://example.net/Sir%20Lancelot'. Note that you can always decide, whether a *certain* URL should be absolute or relative, by passing the appropriate argument to :meth:`ConfiguredHttpModule.url`. :confkey:`ctx.member.url` :confdefault:`url` The name of the :term:`context member` function for generating URLs. :confkey:`serve.ip` :confdefault:`0.0.0.0` This will be the ip address your HTTP server will bind_ to, when using :mod:`score.serve` to serve your application. :confkey:`serve.port` :confdefault:`8080` This will be the port of your HTTP server, when using :mod:`score.serve` to serve your application. :confkey:`serve.threaded` :confdefault:`False` Setting this to `True` will make your HTTP server threaded, which should increase its performance. Note that your application will need to be thread-safe_, if you want to enable this feature. .. _werkzeug debugger: http://werkzeug.pocoo.org/docs/0.11/debug/#using-the-debugger .. _bind: http://www.xeams.com/bindtoaddress.htm .. _thread-safe: https://en.wikipedia.org/wiki/Thread_safety """ conf = dict(defaults.items()) conf.update(confdict) if 'router' not in conf: import score.http raise ConfigurationError(score.http, 'No router provided') router = parse_dotted_path(conf['router']) preroutes = list(map(parse_dotted_path, parse_list(conf['preroutes']))) error_handlers = {} exception_handlers = {} for error, handler in extract_conf(conf, 'handler.').items(): if re.match('\d(\d\d|XX)', error): error_handlers[error] = parse_dotted_path(handler) else: error = parse_dotted_path(error) exception_handlers[error] = handler debug = parse_bool(conf['debug']) if not conf['urlbase']: conf['urlbase'] = '' http = ConfiguredHttpModule( ctx, db, router, preroutes, error_handlers, exception_handlers, debug, conf['urlbase'], conf['serve.ip'], int(conf['serve.port']), parse_bool(conf['serve.threaded'])) def constructor(ctx): def url(*args, **kwargs): return http.url(ctx, *args, **kwargs) return url ctx.register(conf['ctx.member.url'], constructor) return http
def init(confdict, ctx=None): """ Initializes this module acoording to :ref:`our module initialization guidelines <module_initialization>` with the following configuration keys: :confkey:`sqlalchemy.*` All configuration values under this key will be passed to :func:`engine_from_config`, which in turn calls :func:`sqlalchemy.create_engine` with these configuration values as keyword arguments. Usually the following is sufficient:: sqlalchemy.url = postgresql://dbuser@localhost/projname :confkey:`base` :faint:`[default=None]` The dotted python path to the :ref:`base class <db_base_class>` to configure, as interpreted by func:`score.init.parse_dotted_path`. :confkey:`destroyable` :faint:`[default=False]` Whether destructive operations may be performed on the database. This value prevents accidental deletion of important data on live servers. :confkey:`ctx.member` :faint:`[default=db]` The name of the :term:`context member`, that should be registered with the configured :mod:`score.ctx` module (if there is one). The default value allows you to always access a valid session within a :class:`score.ctx.Context` like this: >>> ctx.db.query(User).first() This function will initialize an sqlalchemy :ref:`Engine <sqlalchemy:engines_toplevel>` and the provided :ref:`base class <db_base_class>`. """ conf = defaults.copy() conf.update(confdict) engine = engine_from_config(conf) if not conf['base']: import score.db raise ConfigurationError(score.db, 'No base class configured') Base = parse_dotted_path(conf['base']) Base.metadata.bind = engine ctx_member = None if ctx and conf['ctx.member']: ctx_member = conf['ctx.member'] if engine.dialect.name == 'sqlite': @sa.event.listens_for(engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() cursor.execute("PRAGMA foreign_keys=ON") cursor.close() db_conf = ConfiguredDbModule( engine, Base, parse_bool(conf['destroyable']), ctx_member) if ctx_member: def constructor(ctx): zope_tx = ZopeTransactionExtension( transaction_manager=ctx.tx_manager) return db_conf.Session(extension=zope_tx) ctx.register(ctx_member, constructor) return db_conf
def __init__(self, conf, next, actor_class=None, session_key='actor'): super().__init__(conf, next) self.session_key = session_key if isinstance(actor_class, str): actor_class = parse_dotted_path(actor_class) self.dbcls = actor_class