Source code for aglyph.integration.cherrypy

# -*- coding: UTF-8 -*-

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

"""Classes and utilities for integrating Aglyph with
`CherryPy <http://www.cherrypy.org/>`_.

.. versionadded:: 2.1.0

An example using XML configuration::

   from aglyph.assembler import Assembler
   from aglyph.context import XMLContext
   from aglyph.integration.cherrypy import AglyphDIPlugin
   import cherrypy

   context = XMLContext("my-aglyph-context.xml")
   assembler = Assembler(context)

   cherrypy.engine.aglyph = AglyphDIPlugin(cherrypy.engine, assembler)
   cherrypy.engine.aglyph.subscribe()

An example using the fluent configuration API::

   from aglyph.integration.cherrypy import AglyphDIPlugin
   from aglyph.context import Context
   import cherrypy

   context = Context("my-aglyph-context")
   context.template(...)
   context.prototype(...)
   context.singleton(...)
   context.borg(...)
   context.weakref(...)
   # and so on
   assembler = Assembler(context)

   cherrypy.engine.aglyph = AglyphDIPlugin(cherrypy.engine, assembler)
   cherrypy.engine.aglyph.subscribe()

In either scenario, you can now use Aglyph to assemble components in
your CherryPy application by publishing an "aglyph-assemble" event to
the `Web Site Process Bus
<https://cherrypy.readthedocs.org/en/latest/pkg/cherrypy.process.html#web-site-process-bus>`_.
This event requires an Aglyph component specification (either an ID or
an object whose dotted name is a component ID)::

   ...
   my_obj = cherrypy.engine.publish("aglyph-assemble", "my-id").pop()
   ...

"""

from __future__ import absolute_import

__author__ = "Matthew Zipay <mattz@ninthtest.info>"

import logging

# for logging, use self.bus.log rather than self.__log
from autologging import traced

from aglyph import __version__
from aglyph._compat import name_of

from cherrypy.process.plugins import SimplePlugin

__all__ = [
    "AglyphDIPlugin",
]

_log = logging.getLogger(__name__)


[docs]@traced class AglyphDIPlugin(SimplePlugin): """A `CherryPy <http://www.cherrypy.org/>`_ `plugin <https://cherrypy.readthedocs.org/en/latest/extend.html#plugins>`_ that provides Aglyph dependency injection support to CherryPy applications. The Aglyph DI plugin subscribes to the following channels: aglyph-assemble Publish a component ID to this channel to assemble the component. aglyph-init-singletons Publish to this channel to pre-assemble and cache all *singleton* components. aglyph-clear-singletons Publish to this channel to clear all cached *singleton* components. aglyph-init-borgs Publish to this channel to pre-assemble and cache the shared-states of all *borg* components. aglyph-clear-borgs Publish to this channel to clear all cached *borg* components. aglyph-clear-weakrefs Publish to this channel to clear all cached *weakref* components. """ def __init__(self, bus, assembler, eager_init=True): """ :arg cherrypy.process.wspbus.Bus bus: the CherryPy Web Site Process Bus :arg aglyph.assembler.Assembler assembler: the configured Aglyph assembler (or binder) :keyword bool eager_init: if ``True``, all *singleton* and *borg* components in the assembler's context will be pre-assembed and cached when the Aglyph DI plugin is started """ SimplePlugin.__init__(self, bus) self._assembler = assembler self._eager_init = eager_init @property def eager_init(self): """Return the current value of the eager initialization flag.""" return self._eager_init @eager_init.setter def eager_init(self, flag): """Set the eager initialization flag. :arg bool flag: whether (``True``) or not (``False``) the Aglyph DI plugin should pre-assemble and cache all *singleton* and *borg* components when the plugin is (re)started """ self._eager_init = flag
[docs] def start(self): """Subscribe to all Aglyph DI channels. aglyph-assemble Publish a component ID to this channel to assemble the component. aglyph-init-singletons Publish to this channel to pre-assemble and cache all *singleton* components. aglyph-clear-singletons Publish to this channel to clear all cached *singleton* components. aglyph-init-borgs Publish to this channel to pre-assemble and cache the shared-states of all *borg* components. aglyph-clear-borgs Publish to this channel to clear all cached *borg* components. aglyph-clear-weakrefs Publish to this channel to clear all cached *weakref* components. .. note:: If :attr:`eager_init` is ``True``, all *singleton* and *borg* components are pre-assembled and cached before the channels are subscribed. """ if self._eager_init: self.bus.log( "initializing Aglyph singleton and borg component objects") self.init_singletons() self.init_borgs() self.bus.log("starting Aglyph dependency injection support") self.bus.subscribe("aglyph-assemble", self.assemble) self.bus.subscribe("aglyph-init-singletons", self.init_singletons) self.bus.subscribe("aglyph-clear-singletons", self.clear_singletons) self.bus.subscribe("aglyph-init-borgs", self.init_borgs) self.bus.subscribe("aglyph-clear-borgs", self.clear_borgs) self.bus.subscribe("aglyph-clear-weakrefs", self.clear_weakrefs)
[docs] def stop(self): """Unsubscribe from all Aglyph DI channels. .. note:: After all Aglyph DI channels have been unsubscribed, the *singleton*, *borg*, and *weakref* caches are automatically cleared. """ self.bus.log("stopping Aglyph dependency injection support") self.bus.unsubscribe("aglyph-assemble", self.assemble) self.bus.unsubscribe("aglyph-init-singletons", self.init_singletons) self.bus.unsubscribe("aglyph-clear-singletons", self.clear_singletons) self.bus.unsubscribe("aglyph-init-borgs", self.init_borgs) self.bus.unsubscribe("aglyph-clear-borgs", self.clear_borgs) self.bus.unsubscribe("aglyph-clear-weakrefs", self.clear_weakrefs) self.bus.log( "clearing Aglyph singleton, borg, and weakref component objects") self.clear_singletons() self.clear_borgs() self.clear_weakrefs()
[docs] def assemble(self, component_spec): """Return the object assembled according to *component_spec*. :arg component_spec: a string representing a component dotted name or unique ID; or an importable class, function, or module :return: a complete object with all of its resolved dependencies This method handles messages published to the **aglyph-assemble** channel. """ self.bus.log("assembling %r" % component_spec) return self._assembler.assemble(component_spec)
[docs] def init_singletons(self): """Assemble and cache all singleton component objects. :return: the initialized singleton component IDs :rtype: :obj:`list` This method handles messages published to the **aglyph-init-singletons** channel. """ singleton_ids = self._assembler.init_singletons() if singleton_ids: self.bus.log("initialized singletons %s" % repr(singleton_ids)) return singleton_ids
[docs] def clear_singletons(self): """Evict all cached singleton component objects. :return: the evicted singleton component IDs :rtype: :obj:`list` This method handles messages published to the **aglyph-clear-singletons** channel. """ singleton_ids = self._assembler.clear_singletons() if singleton_ids: self.bus.log("cleared singletons %s" % repr(singleton_ids)) return singleton_ids
[docs] def init_borgs(self): """Assemble and cache the shared-states for all borg component objects. :return: the initialized borg component IDs :rtype: :obj:`list` This method handles messages published to the **aglyph-init-borgs** channel. """ borg_ids = self._assembler.init_borgs() if borg_ids: self.bus.log("initialized borgs %s" % repr(borg_ids)) return borg_ids
[docs] def clear_borgs(self): """Evict all cached borg component shared-states. :return: the evicted borg component IDs :rtype: :obj:`list` This method handles messages published to the **aglyph-clear-borgs** channel. """ borg_ids = self._assembler.clear_borgs() if borg_ids: self.bus.log("cleared borgs %s" % repr(borg_ids)) return borg_ids
[docs] def clear_weakrefs(self): """Evict all cached weakref component objects. :return: the evicted weakref component IDs :rtype: :obj:`list` This method handles messages published to the **aglyph-clear-weakrefs** channel. """ weakref_ids = self._assembler.clear_weakrefs() if weakref_ids: self.bus.log("cleared weakrefs %s" % repr(weakref_ids)) return weakref_ids
def __str__(self): return "<%s @%08x %s>" % ( name_of(self.__class__), id(self), self._assembler) def __repr__(self): return "%s.%s(%r, %r, eager_init=%r)" % ( self.__class__.__module__, name_of(self.__class__), self.bus, self._assembler, self._eager_init)