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 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.
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 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
, if specified, replacesaglyph.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()
, oraglyph.assembler.Assembler.clear_weakrefs()
.Note
Template.before_clear
, if specified, replacesaglyph.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
, orclassmethod
. Seefactory_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:
- member_name is not restricted to callable members; it may identify attributes and/or properties as well.
- 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.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 anotherComponent
) 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 eitherTemplate.after_inject
(if this component also specifiesparent_id
) oraglyph.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()
, oraglyph.assembler.Assembler.clear_weakrefs()
.Note
Component.before_clear
, if specified, replaces eitherTemplate.before_clear
(if this component also specifiesparent_id
) oraglyph.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, 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.net") 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
oraglyph.binder.Binder
- 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
-
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 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 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, 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 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, 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 anyinit_args
orinit_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 aReference
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 anEvaluator
, and can be assembled directly by anaglyph.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 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 runs under CPython 3.
-
class
aglyph.component.
Evaluator
(func, *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 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 anaglyph.assembler.Assembler
oraglyph.binder.Binder
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.
Note
An
Evaluator
can handle any level of nesting (e.g. afunctools.partial()
within anEvaluator
within anotherEvaluator
).-
__call__
(assembler)[source]¶ Call
func(*args, **keywords)
and return the new object.Parameters: assembler – a reference to an aglyph.assembly.Assembler
oraglyph.binder.Binder
assembler is used to assemble any
Reference
that is encountered in the function arguments.
- If the argument value is a