aglyph.component — Defining components and their dependencies

Release:2.1.1

The classes in this module are used to define components and their dependencies.

aglyph.component.LifecycleState = LifecycleState(AFTER_INJECT='after_inject', BEFORE_CLEAR='before_clear')

Define the lifecycle states for which Aglyph will call object methods on your behalf.

Lifecycle methods

Lifecycle methods are called with no arguments (positional or keyword).

If a called lifecycle method raises an exception, the exception is caught, logged at logging.ERROR level (including a traceback) to the “aglyph.assembler.Assembler” channel, and a RuntimeWarning is issued.

A method may be registered for a lifecycle state by specifying the method name at the context (least specific), template, and/or component (most specific) level.

Note

Aglyph only calls one method on an object for any lifecycle state. Refer to The lifecycle method lookup process (below) for details.

Aglyph recognizes the following lifecycle states:

“after_inject”

A component object is in this state after all dependencies (both initialization arguments and attributes) have been injected into a newly-created instance, but before the object is cached and/or returned to the caller.

Aglyph will only call one “after_inject” method on any object, and will determine which method to call by using the lookup process described below.

“before_clear”

A component object is in this state after is has been removed from an internal cache (singleton, borg, or weakref), but before the object itself is actually discarded.

Aglyph will only call one “before_clear” method on any object, and will determine which method to call by using the lookup process described below.

The lifecycle method lookup process

Lifecyle methods may be specified at the context (least specific), template, and component (most specific) levels.

In order to determine which named method is called for a particular object, Aglyph looks up the appropriate lifecycle method name in the following order, using the first one found that is not None and is actually defined on the object:

  1. The method named by the object’s Component.<lifecycle-state> property.
  2. If the object’s Component.parent_id is not None, the method named by the corresponding parent Template.<lifecycle-state> or Component.<lifecycle-state> property. (If necessary, lookup continues by examining the parent-of-the-parent and so on.)
  3. The method named by the Context.<lifecycle-state> property.

When Aglyph finds a named lifecycle method that applies to an object, but the object itself does not define that method, a logging.WARNING message is emitted.

Note

Either a Component or Template may serve as the parent identified by a parent_id.

However, only a Component may actually be assembled into a usable object. (A Template is like an abstract class - it defines common dependencies and/or lifecycle methods, but it cannot be assembled.)

class aglyph.component.Template(template_id, parent_id=None, after_inject=None, before_clear=None)[source]

Bases: aglyph.component._DependencySupport

Parameters:
  • template_id (str) – context-unique identifier for this template
  • parent_id (str) – specifies the ID of a template or component that describes the default dependencies and/or lifecyle methods for this template
  • after_inject (str) – specifies the name of the method that will be called on objects of components that reference this template after all component dependencies have been injected
  • before_clear (str) – specifies the name of the method that will be called on objects of components that reference this template immediately before they are cleared from cache

Note

A Template cannot be assembled (it is equivalent to an abstract class).

However, a Component can also serve as a template, so if you need the ability to assemble an object and use its definition as the basis for other components, then define the default dependencies and/or lifecycle methods in a Component and use that component’s ID as the Component.parent_id in other components.

unique_id must be a user-provided identifier that is unique within the context to which this template is added. A component may then be instructed to use a template by specifying the same value for Component.parent_id.

parent_id is another Component.unique_id or Template.unique_id in the same context that descibes this template’s default dependencies and/or lifecycle methods.

after_inject is the name of a method of objects of this component that will be called after all dependencies have been injected, but before the object is returned to the caller. This method will be called with no arguments (positional or keyword). Exceptions raised by this method are not caught.

Note

Template.after_inject, if specified, replaces aglyph.context.Context.after_inject for any component that uses the template.

before_clear is the name of a method of objects of this component that will be called immediately before the object is cleared from cache via aglyph.assembler.Assembler.clear_singletons(), aglyph.assembler.Assembler.clear_borgs(), or aglyph.assembler.Assembler.clear_weakrefs().

Note

Template.before_clear, if specified, replaces aglyph.context.Context.before_clear for any component that uses the template.

Warning

The before_clear keyword argument has no meaning for and is ignored by “prototype” components. If before_clear is specified for a prototype, a RuntimeWarning will be issued.

For “weakref” components, there is a possibility that the object no longer exists at the moment when the before_clear method would be called. In such cases, the before_clear method is not called. No warning is issued, but a logging.WARNING message is emitted.

unique_id

Uniquely identifies this template in a context (read-only).

parent_id

Identifies this template’s parent template or component (read-only).

after_inject

The name of the component object method that will be called after all dependencies have been injected.

before_clear

The name of the component object method that will be called immediately before the object is cleared from cache.

Warning

This property is not applicable to “prototype” component objects, and is not guaranteed to be called for “weakref” component objects.

class aglyph.component.Component(component_id, dotted_name=None, factory_name=None, member_name=None, strategy='prototype', parent_id=None, after_inject=None, before_clear=None)[source]

Bases: aglyph.component.Template

Define a component and the dependencies needed to create a new object of that component at runtime.

Parameters:
  • component_id (str) – context-unique identifier for this component
  • dotted_name (str) – an importable dotted name
  • factory_name (str) – names a callable member of the object identified by component_id or dotted_name
  • member_name (str) – names any member of the object identified by component_id or dotted_name
  • strategy (str) – specifies the component assembly strategy
  • parent_id (str) – specifies the ID of a template or component that describes the default dependencies and/or lifecyle methods for this component
  • after_inject (str) – specifies the name of the method that will be called on objects of this component after all of its dependencies have been injected
  • before_clear (str) – specifies the name of the method that will be called on objects of this component immediately before they are cleared from cache
Raises:
  • aglyph.AglyphError – if both factory_name and member_name are specified
  • ValueError – if strategy is not a recognized assembly strategy

component_id must be a user-provided identifier that is unique within the context to which this component is added. An importable dotted name may be used (see aglyph.resolve_dotted_name()).

dotted_name, if provided, must be an importable dotted name (see aglyph.resolve_dotted_name()).

Note

If dotted_name is not specified, then component_id will be used as the component’s dotted name. In this case, component_id must be an importable dotted name.

New in version 2.0.0: the factory_name keyword argument

factory_name is the name of a callable member of dotted-name (i.e. a function, class, staticmethod, or classmethod). When provided, the assembler will call this member to create an object of this component.

factory_name enables Aglyph to inject dependencies into objects that can only be initialized via nested classes, staticmethod, or classmethod. See factory_name for details.

New in version 2.0.0: the member_name keyword argument

member_name is the name of a member of dotted-name, which may or may not be callable.

member_name differs from factory_name in two ways:

  1. member_name is not restricted to callable members; it may identify attributes and/or properties as well.
  2. When an assembler assembles a component with a member_name, initialization of the object is bypassed (i.e. the assembler will not call the member, and any initialization arguments defined for the component will be ignored).

member_name enables Aglyph to reference class, function, staticmethod, and classmethod obejcts, as well as simple attributes or properties, as components and dependencies. See member_name for details.

Note

Both factory_name and member_name can be dot-separated names to reference nested members.

Warning

The factory_name and member_name arguments are mutually exclusive. An exception is raised if both are provided.

strategy must be a recognized component assembly strategy, and defaults to Strategy.PROTOTYPE (“prototype”) if not specified.

Please see Strategy for a description of the component assembly strategies supported by Aglyph.

Warning

The Strategy.BORG (“borg”) component assembly strategy is only supported for classes that do not define or inherit __slots__!

New in version 2.1.0: the parent_id keyword argument

parent_id is the context-unique ID of a Template (or another Component) that defines default dependencies and/or lifecycle methods for this component.

New in version 2.1.0: the after_inject keyword argument

after_inject is the name of a method of objects of this component that will be called after all dependencies have been injected, but before the object is returned to the caller. This method will be called with no arguments (positional or keyword). Exceptions raised by this method are not caught.

Note

Component.after_inject, if specified, replaces either Template.after_inject (if this component also specifies parent_id) or aglyph.context.Context.after_inject.

New in version 2.1.0: the before_clear keyword argument

before_clear is the name of a method of objects of this component that will be called immediately before the object is cleared from cache via aglyph.assembler.Assembler.clear_singletons(), aglyph.assembler.Assembler.clear_borgs(), or aglyph.assembler.Assembler.clear_weakrefs().

Note

Component.before_clear, if specified, replaces either Template.before_clear (if this component also specifies parent_id) or aglyph.context.Context.before_clear.

Warning

The before_clear keyword argument has no meaning for and is ignored by “prototype” components. If before_clear is specified for a prototype, a RuntimeWarning will be issued.

For “weakref” components, there is a possibility that the object no longer exists at the moment when the before_clear method would be called. In such cases, the before_clear method is not called. No warning is issued, but a logging.WARNING message is emitted.

Once a Component instance is initialized, the args (list), keywords (dict), and attributes (collections.OrderedDict) members can be modified in-place to define the dependencies that must be injected into objects of this component at assembly time. For example:

component = Component("http.client.HTTPConnection")
component.args.append("ninthtest.info")
component.args.append(80)
component.keywords["strict"] = True
component.attributes["set_debuglevel"] = 1

In Aglyph, a component may:

component_id

The unique component identifier (read-only).

Deprecated since version 2.1.0: use unique_id instead.

dotted_name

The importable dotted name for objects of this component (read-only).

factory_name

The name of a callable member of dotted_name (read-only).

factory_name can be used to initialize objects of the component when a class is not directly importable (e.g. the component class is a nested class), or when component objects need to be initialized via staticmethod or classmethod.

Consider the following:

# module.py
class Example:
    class Nested:
        pass

The following examples show how to define a component that will produce an instance of the module.Example.Nested class when assembled.

Programmatic configuration using Component:

component = Component("nested-object",
                      dotted_name="module.Example",
                      factory_name="Nested")

Programmatic configuration using aglyph.binder.Binder:

from aglyph.binder import Binder
from module import Example

binder = Binder()
binder.bind("nested-object", to=Example, factory="Nested")

Declarative XML configuration:

<component id="nested-object" dotted-name="module.Example"
    factory-name="Nested" />

factory_name may also be a dot-separated name to specify an arbitrarily-nested callable:

Programmatic configuration using Component:

component = Component("nested-object", dotted_name="module",
                      factory_name="Example.Nested")

Programmatic configuration using aglyph.binder.Binder:

from aglyph.binder import Binder
import module

binder = Binder()
binder.bind("nested-object", to=module,
            factory="Example.Nested")

Declarative XML configuration:

<component id="nested-object" dotted-name="module"
    factory-name="Example.Nested" />

Note

The important thing to remember is that dotted_name must be importable, and factory_name must be accessible from the imported class or module via attribute access.

member_name

The name of any member of dotted_name (read-only).

member_name can be used to obtain an object directly from an importable module or class. The named member is simply accessed and returned (it is not called, even if it is callable).

Consider the following:

# module.py
class Example:
    class Nested:
        pass

The following examples show how to define a component that will produce the module.Example.Nested class itself when assembled.

Programmatic configuration using Component:

component = Component("nested-class",
                      dotted_name="module.Example",
                      member_name="Nested")

Programmatic configuration using aglyph.binder.Binder:

from aglyph.binder import Binder
from module import Example

binder = Binder()
binder.bind("nested-class", to=Example, member="Nested")

Declarative XML configuration:

<component id="nested-class" dotted-name="module.Example"
    member-name="Nested" />

member_name may also be a dot-separated name to specify an arbitrarily-nested member:

Programmatic configuration using Component:

component = Component("nested-class", dotted_name="module",
                      member_name="Example.Nested")

Programmatic configuration using aglyph.binder.Binder:

from aglyph.binder import Binder
import module

binder = Binder()
binder.bind("nested-class", to=module,
            member="Example.Nested")

Declarative XML configuration:

<component id="nested-class" dotted-name="module"
    member-name="Example.Nested" />

Note

The important thing to remember is that dotted_name must be importable, and member_name must be accessible from the imported class or module via attribute access.

Warning

When a component specifies member_name, initialization is assumed. In other words, Aglyph will not attempt to initialize the member, and will ignore any init_args or init_keywords.

On assembly, if any initialization arguments and/or keyword arguments have been defined for such a component, they are discarded and a WARN-level log record is emitted to the “aglyph.assembler.Assembler” channel.

Any attributes that have been specified for the component will still be processed as setter injection dependencies, however.

strategy

The component assembly strategy (read-only).

init_args

The positional arguments for constructor injection of component object dependencies.

Deprecated since version 2.1.0: use args instead.

Note

This property may not be set; it must be modified by reference.

init_keywords

The keyword arguments for constructor injection of component object dependencies.

Deprecated since version 2.1.0: use keywords instead.

Note

This property may not be set; it must be modified by reference.

aglyph.component.Strategy = Strategy(PROTOTYPE='prototype', SINGLETON='singleton', BORG='borg', WEAKREF='weakref')

Define the component assembly strategies implemented by Aglyph.

Changed in version 2.0.0: Strategy is now a named tuple. In prior versions, it was a class.

“prototype”

A new object is always created, initialized, wired, and returned.

Note

“prototype” is the default assembly strategy for Aglyph components.

“singleton”

The cached object is returned if it exists. Otherwise, the object is created, initialized, wired, cached, and returned.

Singleton component objects are cached by Component.unique_id.

“borg”

A new instance is always created. The shared-state is assigned to the new instance’s __dict__ if it exists. Otherwise, the new instance is initialized and wired, its instance __dict__ is cached, and then the instance is returned.

Borg component instance shared-states are cached by Component.component_id.

Warning

  • The borg assembly strategy is only supported for components that are non-builtin classes.
  • The borg assembly strategy is not supported for classes that define or inherit a __slots__ member.

“weakref”

New in version 2.1.0.

In the simplest terms, this is a “prototype” that can exhibit “singleton” behavior: as long as there is at least one “live” reference to the assembled object in the application runtime, then requests to assemble this component will return the same (cached) object.

When the only reference to the assembled object that remains is the cached weak reference, the Python garbage collector is free to destroy the object, at which point it is automatically removed from the Aglyph cache.

Subsequent requests to assemble the same component will cause a new object to be created, initialized, wired, cached (as a weak reference), and returned.

Note

Please refer to the weakref module for a detailed explanation of weak reference behavior.

class aglyph.component.Reference[source]

Bases: str

A place-holder used to refer to another Component.

A Reference is used as an alias to identify a component that is a dependency of another component. The value of a Reference can be either a dotted-name or a user-provided unique ID.

A Reference value MUST correspond to a component ID in the same context.

A Reference can be used as an argument for an Evaluator, and can be assembled directly by an aglyph.assembler.Assembler.

Note

In Python versions < 3.0, a Reference representing a dotted-name must consist only of characters in the ASCII subset of the source encoding (see PEP 0263).

But in Python versions >= 3.0, a Reference representing a dotted-name may contain non-ASCII characters (see PEP 3131).

However, a Reference may also represent a user-defined identifier. To accommodate all cases, the super class of Reference is “dynamic” with respect to the version of Python under which Aglyph is running (unicode under Python 2, str under Python 3). This documentation shows the base class as str because the Sphinx documentation generator runs under CPython 3.

class aglyph.component.Evaluator(func, *args, **keywords)[source]

Bases: aglyph.component._InitializationSupport

Perform lazy creation of objects.

Parameters:
  • func (callable) – any callable that returns an object
  • args (tuple) – the positional arguments to func
  • keywords (dict) – the keyword arguments to func

An Evaluator is similar to a functools.partial() in that they both collect a function and related arguments into a callable object with a simplified signature that can be called repeatedly to produce a new object.

Unlike a partial function, an Evaluator may have arguments that are not truly “frozen,” in the sense that any argument may be defined as a Reference, a functools.partial(), or even another Evaluator, which needs to be resolved (i.e. assembled/called) before calling func.

When an Evaluator is called, its arguments (positional and keyword) are each resolve in one of the following ways:

  • If the argument value is a Reference, it is assembled (by an aglyph.assembler.Assembler or aglyph.binder.Binder reference passed to __call__())
  • If the argument value is an Evaluator or a functools.partial(), it is called to produce its value.
  • If the argument is a dictionary or a sequence other than a string type, each item is resolved according to these rules.
  • If none of the above cases apply, the argument value is used as-is.

Note

An Evaluator can handle any level of nesting (e.g. a functools.partial() within an Evaluator within another Evaluator).

func

The callable that creates new objects (read-only).

__call__(assembler)[source]

Call func(*args, **keywords) and return the new object.

Parameters:assembler – a reference to an aglyph.assembly.Assembler or aglyph.binder.Binder

assembler is used to assemble any Reference that is encountered in the function arguments.