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 aRuntimeWarning
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:- The method named by the object’s
Component.<lifecycle-state>
property. - If the object’s
Component.parent_id
is notNone
, the method named by the corresponding parentTemplate.<lifecycle-state>
orComponent.<lifecycle-state>
property. (If necessary, lookup continues by examining the parent-of-the-parent and so on.) - 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.- The method named by the object’s
-
class
aglyph.component.
Reference
[source]¶ Bases:
unicode
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 aReference
can be either a dotted-name or a user-provided unique ID.A
Reference
can be used as an argument for anEvaluator
, and can be assembled directly by anaglyph.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 ofReference
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 asstr
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 aReference
.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: An
Evaluator
is similar to afunctools.partial()
in that they both collect a function and related arguments into acallable
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 aReference
, afunctools.partial()
, or even anotherEvaluator
, 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 anaglyph.assembler.Assembler
reference passed to__call__()
) - If the argument value is an
Evaluator
or afunctools.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.
- If the argument value is a
-
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 emptyNote
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 aComponent
and use that component’s ID as theComponent.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
orTemplate.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()
, oraglyph.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
, orclassmethod
. Seefactory_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:
- member_name is not restricted to callable members; it may identify any member (attribute, property, nested class).
- 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
, andclassmethod
obejcts, as well as simple attributes or properties, as components and dependencies. Seemember_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 issueUserWarning
.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 anotherComponent
) 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()
, oraglyph.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’sbefore_clear
attribute is set toNone
.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 aRuntimeWarning
(see that method’s documentation for more details).Once a
Component
instance is initialized, theargs
(list
),keywords
(dict
), andattributes
(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 ofdotted_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 viastaticmethod
orclassmethod
.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
ordotted_name
. To assemble objects of this type, usefactory_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, andfactory_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, andmember_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 anyargs
andkeywords
.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).