The Aglyph Context fluent API¶
Release: | 3.0.0.post1 |
---|
- Introduction to the Context fluent API
- Using component references with the Context fluent API
- Using the Context fluent API to define templates
- Letting Aglyph introspect dotted names
- Overview of the Context fluent API methods
Introduction to the Context fluent API¶
The easiest way to configure Aglyph programmatically is to use the
“fluent” API exposed by aglyph.context.Context
.
The Context fluent API consists of a set of chained-call-style methods
(exposed on Context
or its subclasses) that can be used to define
components of any kind (prototype, singleton, borg, weakref) as well
as templates.
There are six (6) “entry points” into the Context fluent API:
aglyph.context.Context.prototype()
to define a prototype componentaglyph.context.Context.singleton()
to define a singleton componentaglyph.context.Context.borg()
to define a borg componentaglyph.context.Context.weakref()
to define a weakref componentaglyph.context.Context.template()
to define a templateaglyph.context.Context.component()
to define any kind of component (more on this method later)
Each entry point returns a “builder” which can be used to further define the component or template.
For the sake of demonstration, assume we begin with the following initialization code:
from aglyph.assembler import Assembler
from aglyph.component import Reference as ref
from aglyph.context import Context
context = Context("example-context")
Let’s define a singleton component:
context.singleton("my-singleton")
Here we have provided a unique ID for the component (“my-singleton”). Since
this is not a dotted name, though, Aglyph doesn’t know which class to use
to create the object. We will provide the class name with a chained call to
the create()
method:
context.singleton("my-singleton").create("module.ClassName")
We could also have used the dotted name as the unique ID, in which case
calling create()
would not be necessary.
Now we wish to define the initialization arguments and keywords. We add
another chained call to the init()
method to do so. We can also break
the chained call sequence onto multiple lines for readability:
(context.singleton("my-singleton").
create("module.ClassName").
init("argument", keyword="keyword"))
Finally, we terminate the fluent sequence by calling the register()
method, which actually creates an aglyph.component.Component
and
adds it to the context:
(context.singleton("my-singleton").
create("module.ClassName").
init("argument", keyword="keyword").
register())
Note
Terminating the fluent builder with a call to register()
is
crucial. The component definition is not actually created or stored
in the context unless/until this method is called!
Now this context can be given to an assembler, and we can create an object:
assembler = Assembler(context)
my_singleton = assembler.assemble("my-singleton")
You can find many other examples of using the Context fluent API in the Aglyph cookbook.
A note regarding the component()
entry point method¶
The strategy-specific entry point methods (prototype()
, singleton()
,
borg()
and weakref()
) are implemented in terms of component()
.
For example, calling singleton("package.ClassName")
is the shorthand
equivalent of calling
component("package.ClassName").create(strategy="singleton")
.
If component("my-id")
is used to define a component and no explicit
strategy is specified, then the default strategy (“prototype”) is
assumed.
Warning
There is one “special case” that warrants explanation:
When specifying a member using the fluent API (i.e. objects of the component are “created” by attribute access on the object identified by a dotted name), then the creation strategy is implicitly set to “_imported” and SHOULD NOT be set explicitly. In short - consider member and strategy to be mutually exclusive.
Using component references with the Context fluent API¶
Let’s add another component to the context. This new component needs to be
injected with an object of “my-singleton” via a property. We will use an
aglyph.component.Reference
to provide the dependency:
(context.prototype("example-object").
create("module2.ExampleObject").
set(thing=ref("my-singleton")).
register())
Assembling an object of “example-object” will resolve “my-singleton” to the
same object we assembled earlier, and will set that object as the
module2.ExampleObject.thing
property.
Using the Context fluent API to define templates¶
Defining templates via the Context fluent API is identical to defining
components, with the exception that there is no create()
method for
template builders. Remember, templates cannot be assembled - they serve
only as a basis for further defining other templates or components.
Here is an example:
context.template("my-template").init("arg", keyword="kw").register()
(context.singleton("my-singleton", parent="my-template").
create("module.ClassName").
register())
Notice that we have added parent="my-template"
to the singleton method call.
Now when we assemble “my-singleton” its initializer will be called with the positional argument “arg” and the keyword argument keyword=”kw”.
Letting Aglyph introspect dotted names¶
If preferred, classes (or other callables) that will be defined as components or templates can be introspected by Aglyph. Let’s revisit the earlier example, slightly modified, to demonstrate this:
from module import ClassName
from module2 import ExampleObject
from aglyph.assembler import Assembler
from aglyph.component import Reference as ref
from aglyph.context import Context
context = Context("example-context")
(context.singleton(ClassName).
init("argument", keyword="keyword").
register())
(context.prototype("example-object").
create(ExampleObject).
set(thing=ref(ClassName)).
register())
assembler = Assembler(context)
example = assembler.assemble("example-object")
Notice that we call singleton(ClassName)
as the entry point. Aglyph
will automatically convert ClassName into its dotted name
“module.ClassName” and also use that value as the unique ID. This means
that the expicit call to create()
is now unnecessary, and so it has
been removed.
Next, notice the call to create(ExampleObject)
. We used
“example-object” as the component ID, so we must still tell Aglyph the
dotted name of the class. But instead of passing the dotted name as a
string, we again pass the class and let Aglyph determine the value.
Finally, notice that we use ref(ClassName)
when setting the thing
property. Like the fluent API entry point methods and create()
,
aglyph.component.Reference
is also capable of introspecting a
dotted name.
Warning
Callable object dotted-name introspection for nested callables
(e.g. nested classes) does not work in Python versions prior to 3.3
because the introspection depends on the Qualified name for classes
and functions (i.e. __qualname__
, specified in PEP 3155).
Overview of the Context fluent API methods¶
The Context fluent API is made up of a number of “mixin” classes that are combined in different ways to support describing templates and components.
These classes are never instantiated directly; rather, the “entry point”
methods are exposed as members of aglyph.context.Context
that
return either a aglyph.context._ComponentBuilder
or a
aglyph.context._TemplateBuilder
; and those builder classes
inherit relevant methods from the mixin classes.
Note
For reference, here is the class hierarchy for the Context fluent API
(all classes are defined in the aglyph.context
namespace):
-
class
Context
(_ContextBuilder)¶
-
class
_TemplateBuilder
(_InjectionBuilderMixin, _LifecycleBuilderMixin, _RegistrationMixin)¶
-
class
_ComponentBuilder
(_CreationBuilderMixin, _TemplateBuilder)¶
“Entry point” methods to create component and template builders¶
-
_ContextBuilder.
component
(component_id_spec, parent=None)[source]¶ Return a fluent
Component
builder for component_id_spec.Parameters: - component_id_spec – a context-unique identifier for this component; or the object whose dotted name will identify this component
- parent – the context-unique identifier for this component’s parent template or component definition; or the object whose dotted name identifies this component’s parent definition
New in version 3.0.0: This method is an entry point into The Aglyph Context fluent API.
-
_ContextBuilder.
prototype
(component_id_spec, parent=None)[source]¶ Return a
prototype
Component
builder for a component identified by component_id_spec.Parameters: - component_id_spec – a context-unique identifier for this component; or the object whose dotted name will identify this component
- parent – the context-unique identifier for this component’s parent template or component definition; or the object whose dotted name identifies this component’s parent definition
New in version 3.0.0: This method is an entry point into The Aglyph Context fluent API.
-
_ContextBuilder.
singleton
(component_id_spec, parent=None)[source]¶ Return a
singleton
Component
builder for a component identified by component_id_spec.Parameters: - component_id_spec – a context-unique identifier for this component; or the object whose dotted name will identify this component
- parent – the context-unique identifier for this component’s parent template or component definition; or the object whose dotted name identifies this component’s parent definition
New in version 3.0.0: This method is an entry point into The Aglyph Context fluent API.
-
_ContextBuilder.
borg
(component_id_spec, parent=None)[source]¶ Return a
borg
Component
builder for a component identified by component_id_spec.Parameters: - component_id_spec – a context-unique identifier for this component; or the object whose dotted name will identify this component
- parent – the context-unique identifier for this component’s parent template or component definition; or the object whose dotted name identifies this component’s parent definition
New in version 3.0.0: This method is an entry point into The Aglyph Context fluent API.
-
_ContextBuilder.
weakref
(component_id_spec, parent=None)[source]¶ Return a
weakref
Component
builder for a component identified by component_id_spec.Parameters: - component_id_spec – a context-unique identifier for this component; or the object whose dotted name will identify this component
- parent – the context-unique identifier for this component’s parent template or component definition; or the object whose dotted name identifies this component’s parent definition
New in version 3.0.0: This method is an entry point into The Aglyph Context fluent API.
-
_ContextBuilder.
template
(template_id_spec, parent=None)[source]¶ Return a
Template
builder for a template identified by template_spec.Parameters: - template_id_spec – a context-unique identifier for this template; or the object whose dotted name will identify this template
- parent – the context-unique identifier for this template’s parent template or component definition; or the object whose dotted name identifies this template’s parent definition
New in version 3.0.0: This method is an entry point into The Aglyph Context fluent API.
Describing object creation for component builders¶
-
_CreationBuilderMixin.
create
(dotted_name=None, factory=None, member=None, strategy=None)[source]¶ Specify the object creation aspects of a component being defined.
Parameters: - dotted_name – an importable dotted name or an object whose dotted name will be introspected
- factory – names a
callable
member of the object represented by the dotted name - member – names any member of the object represented by the dotted name
- strategy – specifies the component assembly strategy
Returns: self (to support chained calls)
Any keyword whose value is
None
will be ignored (i.e.None
values are not explicitly set).Note
The member and strategy keywords should be treated as mutually exclusive. Any component definition that specifies a member is implicitly assigned the special strategy “_imported”.
Describing dependencies and lifecycle methods for template and component builders¶
-
_InjectionBuilderMixin.
init
(*args, **keywords)[source]¶ Specify the initialization arguments (positional and keyword) for templates and/or components.
Parameters: Returns: self (to support chained calls)
Note
Successive calls to this method on the same instance have a cumulative effect; the list of positional arguments is extended, and the dictionary of keyword arguments is updated.
-
_InjectionBuilderMixin.
set
(*pairs, **attributes)[source]¶ Specify the setter (method/attribute/property) depdendencies for tempaltes and/or components.
Parameters: - pairs – a sequence of (name, value) 2-tuples (optional)
- attributes – a mapping of name->value dependencies
Returns: self (to support chained calls)
Note
Successive calls to this method on the same instance have a cumulative effect (i.e. the attributes mapping is updated).
-
_LifecycleBuilderMixin.
call
(after_inject=None, before_clear=None)[source]¶ Specify the names of lifecycle methods to be called for templates and/or components.
Parameters: - after_inject – the name of the method to call after a component has been assembled but before it is returned to the caller
- before_clear – the name of the method to call immediately before a singleton, borg, or weakref object is evicted from the internal cache
Returns: self (to support chained calls)
Any keyword whose value is
None
will be ignored (i.e.None
values are not explicitly set).