Common usage scenarios

Release:3.0.0.post1

All examples are shown with XML configuration and programmatic configuration, where appropriate.

Describe a simple component (a class or an unbound factory function)

The simplest and most common kind of component is one that describes a module-level class or unbound factory function.

To demonstrate, we will describe a component for the Python standard library http.client.HTTPConnection class.

Note

Although we are using the Python standard library http.client.HTTPConnection class here, this example applies to any class or unbound function, whether defined as part of your application, the Python standard library, or a 3rd-party library.

Using declarative XML configuration

In the XML context document “cookbook-context.xml”:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="cookbook-context">
    <component id="http.client.HTTPConnection">
        <init>
            <arg><str>ninthtest.info</str></arg>
            <arg><int>80</int>
            <arg keyword="timeout"><int>5</int></arg>
        </init>
    </component>
</context>

To assemble and use this component:

>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("cookbook-context.xml"))
>>> conx = assembler.assemble("http.client.HTTPConnection")
>>> conx.request("GET", '/')
>>> response = conx.getresponse()
>>> print(response.status, response.reason)
200 OK

If we want to describe more than one component of the same class or function, then we need to use something other than the dotted name as the component IDs so that they are unique:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="cookbook-context">
    <component id="ninthtest-info-conx" dotted-name="http.client.HTTPConnection">
        <init>
            <arg><str>ninthtest.info</str></arg>
            <arg><int>80</int>
            <arg keyword="timeout"><int>5</int></arg>
        </init>
    </component>
    <component id="python-org-conx" dotted-name="http.client.HTTPConnection">
        <init>
            <arg><str>www.python.org</str></arg>
            <arg><int>80</int>
            <arg keyword="timeout"><int>5</int></arg>
        </init>
    </component>
</context>

Accordingly, we use the component IDs to assemble these components:

>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("cookbook-context.xml"))
>>> ninthtest_info = assembler.assemble("ninthtest-info-conx")
>>> python_org = assembler.assemble("python-org-conx")

Using fluent API configuration

Using The Aglyph Context fluent API to describe a simple component in a bindings.py module:

from http.client import HTTPConnection
from aglyph.context import Context

context = Context("cookbook-context")
context.prototype(HTTPConnection).init("ninthtest.info", 80, timeout=5).register()

To assemble and use the component:

>>> from aglyph.assembler import Assembler
>>> from bindings import context
>>> assembler = Assembler(context)
>>> conx = assembler.assemble("http.client.HTTPConnection")
>>> conx.request("GET", '/')
>>> response = conx.getresponse()
>>> print(response.status, response.reason)
200 OK

And like XML contexts, when we wish to use multiple components of the same dotted name, we must give them unique component IDs:

from http.client import HTTPConnection
from aglyph.context import Context

context = Context("cookbook-context")
(context.prototype("ninthtest-info-conx").
    create(HTTPConnection).
    init("ninthtest.info", 80, timeout=5).
    register())
(context.prototype("python-org-conx").
    create(HTTPConnection).
    init("www.python.org", 80, timeout=5).
    register())

Assembling these components now requires the custom component IDs:

>>> from aglyph.assembler import Assembler
>>> from bindings import context
>>> assembler = Assembler(context)
>>> ninthtest_info = assembler.assemble("ninthtest-info-conx")
>>> python_org = assembler.assemble("python-org-conx")

Describe any Python builtin type as a component

Python builtin types (e.g. int, list) can be identified by an importable dotted name, and so may be defined as components in Aglyph.

Using declarative XML configuration

Warning

The name of the module in which builtin types are defined differs between Python 2 and 3, so any Aglyph XML configuration that uses this approach will, by definition, not be compatible across Python versions.

The example given below uses the Python 3 builtins module. To make this example work on Python 2, the __builtin__ module would be used instead.

In the XML context document “cookbook-context.xml”:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="cookbook-context">
    <component id="foods" dotted-name="builtins.frozenset">
        <init>
            <arg>
                <list>
                    <str>spam</str>
                    <str>eggs</str>
                </list>
            </arg>
        </init>
    </component>
    <component id="opened-file" dotted-name="builtins.open">
        <init>
            <arg><str>/path/to/file.txt</str></arg>
            <arg keyword="encoding"><str>ISO-8859-1</str></arg>
        </init>
    </component>
</context>

Using fluent API configuration

Because the builtin types are accessible without having to do an explicit import, the fluent configuration is very simple.

In a bindings.py module:

from aglyph.context import Context

context = Context("cookbook-context")
context.prototype("foods").create(frozenset).init(["spam", "eggs"]).register()
(context.prototype("opened-file").
    create(open).
    init("/path/to/file.txt", encoding="ISO-8859-1").
    register())

Use a reference to another component as a dependency

An aglyph.component.Reference is a powerful mechanism for creating cross-references between components.

A Reference value is just a component ID, but a Reference triggers special behavior within an aglyph.assembler.Assembler or aglyph.component.Evaluator when it is encountered during assembly or evaluation (respectively): wherever the Reference appears, it will be automatically replaced with the fully-assembled component it identifies.

A Reference may be used in any of the following places, allowing for extremely flexible configurations:

In a nutshell: an aglyph.component.Reference may be used in any case where a value is being defined, and will be replaced at assembly-time by the fully-assembled component identified by that reference.

To demonstrate, we will describe components for the Python standard library urllib.request.Request class and urllib.request.urlopen() function. (The former will be referenced as a dependency for the latter.)

Using declarative XML configuration

In the “cookbook-context.xml” document:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="cookbook-context">
    <component id="ninthtest-home-page" dotted-name="urllib.request.Request">
        <init>
            <arg><str>http://ninthtest.info/</str></arg>
        </init>
    </component>
    <component id="ninthtest-url" dotted-name="urllib.request.urlopen">
        <init>
            <arg reference="ninthtest-home-page" />
            <arg keyword="timeout"><int>5</int></arg>
        </init>
    </component>
</context>

When the “ninthtest-url” component is assembled, the assembler will automatically assemble and inject the “ninthtest-home-page” component:

>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("cookbook-context.xml"))
>>> ninthtest_url = assembler.assemble("ninthtest-url")
>>> print(ninthtest_url.status, ninthtest_url.reason)
200 OK

Using fluent API configuration

In a bindings.py module:

from urllib.request import Request, urlopen
from aglyph.context import Context
from aglyph.component import Reference as ref

context = Context("cookbook-context")
(context.component("ninthtest-home-page").
    create(Request).
    init("http://ninthtest.info/").
    register())
(context.component("ninthtest-url").
    create(urlopen).
    init(ref("ninthtest-home-page"), timeout=5).
    register())

When the “ninthtest-url” component is assembled, the assembler will automatically assemble and inject the “ninthtest-home-page” component:

>>> from aglyph.assembler import Assembler
>>> from bindings import context
>>> assembler = Assembler(context)
>>> ninthtest_url = assembler.assemble("ninthtest-url")
>>> print(ninthtest_url.status, ninthtest_url.reason)
200 OK

Defer the resolution of injection values until assembly time

When specifying the values that should be injected into an object of a component as it is assembled, it is sometimes desired (or necessary) that those values be resolved at the time the component is being assembled.

The textbook example of such a case is a component that accepts some mutable sequence type (e.g. a list) as an injection value. If the value (the list) were resolved at the time the component is being defined, then all objects of that component would share a reference to the same list This means that changes to the list belonging to any instance will actually apply to all instances.

In almost all cases, this is not desired behavior. What we actually desire is for each instance of the component to have its own copy of the list.

The solution to this problem is to specify a dependency such that its actual value is determined on-the-fly when the component is being assembled. Aglyph supports several ways of accomplishing this.

Use a Reference to defer the assembly of a component

Whenever an aglyph.component.Reference is used to identify a component as a dependency, that component is not assembled until the parent component is assembled.

Using declarative XML configuration

Aglyph automatically creates an aglyph.component.Reference for any <reference> element encountered, or for any <arg>, <attribute>, <key>, or <value> element that specifies a reference attribute:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="cookbook-context">
    <component id="cookbook-formatter" dotted-name="logging.Formatter">
        <init>
            <arg><str>%(asctime)s %(levelname)s %(message)s</str></arg>
        </init>
    </component>
    <component id="cookbook-handler" dotted-name="logging.handlers.RotatingFileHandler">
        <init>
            <arg><str>/var/log/cookbook.log</str></arg>
            <arg keyword="maxBytes"><int>1048576</int></arg>
            <arg keyword="backupCount"><int>3</int></arg>
        </init>
        <attributes>
            <attribute name="setFormatter">
                <reference id="cookbook-formatter" />
            </attribute>
        </attributes>
    </component>
    <component id="cookbook-logger" dotted-name="logging.getLogger" strategy="singleton">
        <init>
            <arg><str>cookbook</str></arg>
        </init>
        <attributes>
            <attribute name="addHandler" ref="cookbook-handler" />
        </attributes>
    </component>
</context>

Using fluent API configuration

In a bindings.py module:

from aglyph.context import Context
from aglyph.component import Reference as ref

context = Context("cookbook-context")
(context.component("cookbook-formatter").create("logging.Formatter").
    init("%(asctime)s %(levelname)s %(message)s").register())
(context.component("cookbook-handler").create("logging.handlers.RotatingFileHandler").
    init("/var/log/cookbook.log", maxBytes=1048576, backupCount=3).
    set(setFormatter=ref("cookbook-formatter")).
    register())
(context.singleton("cookbook-logger").create("logging.getLogger").
    init("cookbook").
    attributes(addHandler=ref("cookbook-handler")).
    register())

Use a partial function or an Evaluator to defer the evaluation of a runtime value

Though almost all scenarios can be addressed by using components and References, in some cases you may prefer that a dependency is not defined as another component. In Aglyph, you can defer the evaluation of such a value until component assembly time by using either a functools.partial object or an aglyph.component.Evaluator.

A partial function and an Evaluator serve the same purpose, and share the same signature - (func, *args, **keywords) - but an Evaluator is capable of recognizing and assembling any aglyph.component.Reference that appears in args or keywords, while a partial function is not.

Note

When the arguments and/or keywords to a callable must specify an aglyph.component.Reference, use aglyph.component.Evaluator. Otherwise, use either functools.partial or an Evaluator.

Using declarative XML configuration

When using XML configuration, an aglyph.component.Evaluator is automatically created for any <list>, <tuple>, or <dict>.

There is no support for explicitly specifying either a functools.partial or an aglyph.component.Evaluator. There is nothing preventing you from declaring a component of functools.partial or aglyph.component.Evaluator, though the usefulness of the latter is questionable (and would very strongly suggest that a simpler configuration is possible).

In the following “cookbook-context.xml” document, the “states” keyword argument is automatically turned into an aglyph.component.Evaluator:

<?xml version="1.0" ?>
<context id="cookbook-context">
    <component id="cookbook.WorkflowManager">
        <init>
            <arg keyword="states">
                <dict>
                    <item>
                        <key><str>UNA</str></key>
                        <value><str>Unassigned</str></value>
                    </item>
                    <item>
                        <key><str>OPE</str></key>
                        <value><str>Open (Assigned)</str></value>
                    </item>
                    <item>
                        <key><str>CLO</str></key>
                        <value><str>Closed</str></value>
                    </item>
                </dict>
            </arg>
        </init>
    </component>
</context>

Using fluent API configuration

Warning

Unlike XML configuration, there is no provision for automatically creating an aglyph.component.Evaluator when using The Aglyph Context fluent API. Any value that should be the result of a functools.partial or an aglyph.component.Evaluator must be explicitly specified as such.

In a bindings.py module:

import functools
from aglyph.context import Context

context = Context("cookbook-context")
(context.component("cookbook.WorkflowManager").
    init(states=functools.partial(
        dict,
        UNA="Unassigned",
        OPE="Open (Assigned)",
        CLO="Closed")).
    register())

Declare a method to be called on an object after its dependencies have been injected

At times it may be desirable (or necessary) to call an “initialization” method on an assembled object before it is returned to the caller for use. For this purpose, Aglyph allows you to declare such a method name at the context, template, and/or component level.

Note

Please refer to The lifecycle method lookup process to understand how Aglyph determines which lifecycle method to call on an object when multiple options are declared at the context, template, and/or component level for a given object.

In the following examples, we assume that all of the objects implement a prepare() method. In such cases, a context-level after_inject lifecycle method may be appropriate. Alternatively, it could be declared in a template (see Component inheritance using templates).

Regardless of the configuration approach, the behavior is that the assembled object’s prepare() method is called after the object is injected but before the object is returned to the caller.

Using declarative XML configuration

Note the use of the after-inject attribute on the <context> element:

<?xml version="1.0" ?>
<context id="cookbook-context" after-inject="prepare">
   <component id="object-a" dotted-name="cookbook.PreparableObjectA" />
   <component id="object-b" dotted-name="cookbook.PreparableObjectB" />
   <component id="object-c" dotted-name="cookbook.PreparableObjectC" />
</context>

Using fluent API configuration

Note the use of the after_inject keyword argument to the call(...) method:

from aglyph.context import Context

context = Context("cookbook-context", after_inject="prepare")
context.component("object-a").create("cookbook.PreparableObjectA").register()
context.component("object-b").create("cookbook.PreparableObjectB").register()
context.component("object-c").create("cookbook.PreparableObjectC").register()

Additionally, the fluent API supports a call(...) method that may be used to specify a lifecycle method:

context.component("cookbook.AnotherObject").call(after_inject="ready").register()

Declare a method to be called on a singleton, borg, or weakref object before it is cleared from cache

At times it may be desirable (or necessary) to call a “finalization” method on a cached object before it is cleared from Aglyph’s internal cache. For this purpose, Aglyph allows you to declare such a method name at the context, template, and/or component level.

Note

Please refer to The lifecycle method lookup process to understand how Aglyph determines which lifecycle method to call on an object when multiple options are declared at the context, template, and/or component level for a given object.

Following is an example that declares a singleton GNU dbm key/date store using Python’s dbm.gnu module. The store is configured to be opened in “fast” mode, meaning that writes are not synchronized. When using GDBM, it is important that every file opened is also closed (which causes any pending writes to be synchronized - i.e. written to disk). The example shows how to declare that the close() method is to be called before the cached GDBM object is evicted from the Aglyph singleton cache.

Using declarative XML configuration

Here we declare the close() method for the before_clear lifecycle state of the component by using the before-clear attribute of the <component> element:

<?xml version="1.0" ?>
<context id="cookbook-context">
   <component id="store" dotted-name="dbm.gnu" factory-name="open"
         strategy="singleton" before-clear="close">
      <init>
         <arg><str>/var/cookbook-store.db</str></arg>
         <arg><str>cf</str></arg>
      </init>
   </component>
</context>

Using fluent API configuration

Here we use the before_clear keyword argument when binding the GDBM store object:

from aglyph.context import Context

context = Context("cookbook-context")
(context.singleton("store").
    create("dbm.gnu", factory="open").
    init("/var/cookbook-store.db", "cf").
    call(before_clear="close").
    register())

Warning

Be careful when declaring before_clear lifecycle methods for weakref component objects, as the nature of weak references means that Aglyph cannot guarantee that the object still exists when the aglyph.assembler.Assembler.clear_weakrefs() method is called! Please refer to weakref for details.

Avoid circular dependencies

Consider two components, A and B. If B is a dependency of A, and A is also a dependency of B, then a circular dependency exists:

<component id="cookbook.A">
    <init>
        <arg reference="cookbook.B"/>
    </init>
</comonent>
<component id="cookbook.B">
    <init>
        <arg reference="cookbook.A"/>
    </init>
</comonent>

Aglyph will raise aglyph.AglyphError when it detects a circular reference during assembly.

Note

In software design in general, circular dependencies are frowned upon because they can lead to problems ranging from increased maintenance costs to infinite recursion and memory leaks. The existence of a circular dependency usually implies that the design can be improved to avoid such a relationship.