aglyph.component — Defining components and their dependencies

Release:3.0.0.post1

The classes in this module are used to define components and their dependencies within an Aglyph context (aglyph.context.Context).

Components and Templates

aglyph.component.Component tells an aglyph.assembler.Assembler how to create objects of a particular type. The component defines the initialization and/or attribute dependencies for objects of that type, as well as the assembly strategy and any lifecycle methods that should be called.

aglyph.component.Template is used to describe dependencies (initialization and/or attribute) and lifecylce methods that are shared by multiple components. Templates are similar to abstract classes; they cannot be assembled, but are instead used as “parents” of other components (or templates) to achieve a sort of “configuration inheritance.”

Note

Both Component and Template may serve as the parent of any other component or template; but only components may be assembled.

References and Evaluators

A aglyph.component.Reference may be used as the value of any initialization argument (positional or keyword) or attribute in a component or template. Its value must be the unique ID of a component in the same context. At assembly time, the assembler will resolve the reference into an object of the component to which it refers.

An aglyph.component.Evaluator is similar to a functools.partial() object. It stores a callable factory (function or class) and related initialization arguments, and can be called repeatedly to produce new objects. (Unlike a functools.partial() object, though, an Evaluator will resolve any initialization argument that is a aglyph.component.Reference, functools.partial(), or Evaluator before calling the factory.)

Strategies and Lifecycle methods

aglyph.component.Strategy defines the assembly strategies supported by Aglyph (“prototype”, “singleton”, “borg”, “weakref” and “_imported”).

LifecycleState defines assmebly states for components at which Aglyph supports calling named methods on the objects of those components. (Such methods may be used to perform specialized initialization or disposal, for example.)

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

Define the component assembly strategies implemented by Aglyph.

“prototype”

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

Note

“prototype” is the default assembly strategy for Aglyph components that do not specify a member name.

“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.unique_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”

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.

“_imported”

New in version 3.0.0.

Note

The “_imported” strategy is only valid (and is the only allowed value) when member_name is specified for a component.

Since this strategy is implicitly assigned and is intended for internal use by Aglyph itself, it is not exposed on the Strategy named tuple.

An already-created (loaded) object is obtained from an imported module or class (as opposed to creating the object directly). Such components will always resolve (i.e. be assembled) to the same objects; but those objects are not cached by Aglyph as they will exhibit “natural” singleton behavior so long as the containing module is referenced in sys.modules.

It is not necessary to explicitly set the strategy to “_imported” when using member_name - Aglyph will default to “_imported” when it sees a non-empty member_name defined.

Warning

Explicitly setting strategy=”_imported” without specifying member_name will raise AglyphError.

Specifying member_name with any explicit strategy other than “_imported” will ignore the explicit strategy, change it to “_imported” internally, and issue a UserWarning to that effect.

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.Reference[source]

Bases: str

A placeholder 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 can be used as an argument for an Evaluator, and can be assembled directly by an aglyph.assembler.Assembler.

Warning

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

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 for Aglyph runs under CPython 3.

Create a new reference to referent.

Parameters:referent – the object that the reference will represent
Raises:aglyph.AglyphError – if referent is a class, function, or module but cannot be imported

If referent is a string, it is assumed to be a valid Component.unique_id and its value is returned as a Reference.

If referent is a class, function, or module, its importable dotted name is returned as a Reference.

Warning

If referent is a class, function, or module, it must be importable.

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

Bases: aglyph.component._InitializationSupport

Perform lazy creation of objects.

Parameters:
  • factory – 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 factory.

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

  • If the argument value is a Reference, it is assembled (by an aglyph.assembler.Assembler 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.
factory

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

__call__(assembler)[source]

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

Parameters:assembler (aglyph.assembly.Assembler) – the assembler that will be used to assemble any Reference encountered in this evaluator’s positional and keyword arguments
class aglyph.component.Template(unique_id, parent_id=None, after_inject=None, before_clear=None)[source]

Bases: aglyph.component._DependencySupport

Support for configuring type 1 (setter) and type 2 (constructor) injection, and lifecycle methods.

Parameters:
  • unique_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
Raises:

ValueError – if unique_id is None or empty

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 takes precedence over any after_inject method name specified for the template’s parent or context.

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 takes precedence over any before_clear method name specified for the template’s parent or context.

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 (read-only).

before_clear

The name of the component object method that will be called immediately before the object is cleared from cache (read-only).

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=None, 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) – the context-unique identifier for this component
  • dotted_name (str) – an importable dotted name
  • factory_name (str) – names a callable member of objects of this component
  • member_name (str) – names any member of objects of this component
  • 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 is used as the component’s dotted name and must be an importable dotted name.

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.

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 any member (attribute, property, nested class).
  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.

New in version 3.0.0: When member_name is specified, the strategy must be “_imported”. Aglyph will use the “_imported” strategy automatically for components that specify member_name; setting strategy to anything other than “_imported” when specifying member_name will issue UserWarning.

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__!

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

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 takes precedence over any after_inject method names specified for the component’s parent or context.

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 takes precedence over any before_clear method names specified for the component’s parent or context.

Warning

The before_clear keyword argument has no meaning for, and is ignored by, “prototype” components. If before_clear is specified for a prototype component, a UserWarning is issued when the component is defined, and the component’s before_clear attribute is set to None.

Warning

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 aglyph.assembler.clear_weakrefs() method will issue a RuntimeWarning (see that method’s documentation for more details).

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:

  • be assembled directly by an aglyph.assembler.Assembler
  • identify other components as dependencies (using a Reference)
  • be used by other components as a dependency
  • use common dependencies and behaviors (after_inject, before_clear) defined in a aglyph.component.Template
  • use any combination of the above behaviors
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 dotted name “module.Example.Nested” is not importable, and so cannot be used as a component’s unique_id or dotted_name. To assemble objects of this type, use factory_name to identify the callable factory (the Nested class, in this example) that is accessible through the importable “module.Example”:

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

Or using 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. The following example is equivalent to the above:

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

Or again using 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 example shows how to define a component that will produce the module.Example.Nested class itself when assembled:

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

Or using 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. The following example is equivalent to the above:

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

Or again using 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 args and keywords.

On assembly, if any initialization arguments and/or keyword arguments have been defined for such a component, they are discarded and a WARNING-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).