# -*- coding: UTF-8 -*-
# Copyright (c) 2006-2015 Matthew Zipay <mattz@ninthtest.net>
#
# 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 :class:`aglyph.context.XMLContext`::
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 :class:`aglyph.binder.Binder`::
from aglyph.integration.cherrypy import AglyphDIPlugin
from bindings import binder
import cherrypy
cherrypy.engine.aglyph = AglyphDIPlugin(cherrypy.engine, binder)
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.net>"
__version__ = "2.1.0"
import logging
from cherrypy.process.plugins import SimplePlugin
__all__ = [
"AglyphDIPlugin",
]
_logger = logging.getLogger(__name__)
[docs]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
[docs] 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