Other notable usage scenarios

Release:3.0.0.post1

Describe components for static methods, class methods, or nested classes

You may encounter cases where objects for which you want to describe components are created through static methods (@staticmethod), class methods (@classmethod), or nested classes.

Recall that an Aglyph component must have an importable dotted name. Unfortunately, the dotted names for static methods, class methods, and nested classes are not importable.

Aglyph components support an optional aglyph.component.Component.factory_name property to address these cases. The factory_name is itself a dotted name, but it is relative to aglyph.component.Component.dotted_name.

The following sample delorean.py module helps to illustrate the concepts, and will be used in the code examples below:

class EquipmentFoundry:

    class FluxCapacitor:

        @classmethod
        def with_capacitor_drive(capacitor_drive):
            """Return a fully-operational FluxCapacitor."""

    @staticmethod
    def get_default_capacitor_drive():
        """Return an experimental CapacitorDrive."""

The objects which we wish to describe as components (FluxCapacitor and CapacitorDrive), are created by callables having dotted names that are not importable:

  1. The class named delorean.EquipmentFoundry.FluxCapacitor.
  2. The class method named delorean.EquipmentFoundry.FluxCapacitor.with_capacitor_drive.
  3. The static method named delorean.EquipmentFoundry.get_default_capacitor_drive.

However, we can use either of the dotted names that are importable (the module named delorean and the class named delorean.EquipmentFoundry) to define our components, and specify relative dotted names as aglyph.component.Component.factory_name values to enable Aglyph to assemble FluxCapacitor and CapacitorDrive as components.

Using declarative XML configuration

In a delorean-context.xml document:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="delorean-context">
    <component id="default-drive"
            dotted-name="delorean"
            factory-name="EquipmentFoundry.get_default_capacitor_drive" />
    <component id="capacitor-nodrive"
            dotted-name="delorean.EquipmentFoundry"
            factory-name="FluxCapacitor" />
    <component id="capacitor-withdrive"
            dotted-name="delorean.EquipmentFoundry"
            factory-name="FluxCapacitor.with_capacitor_drive">
        <init>
            <arg ref="default-drive" />
        </init>
    </component>
</context>

Key things to note in this configuration:

  • For any of the components, we have the option of using either delorean or delorean.EquipmentFoundry as a component’s dotted name because both of these names are importable. Which we choose influences how the factory name must be specified - it must be relative to the dotted name.
  • Any factory name is just a dotted name - but split into its individual names, it must represent a callable object that can be obtained via attribute access on the module or class identified by the dotted name.

We can now assemble the “capacitor-nodrive” and “capacitor-withdrive” components as we would any other Aglyph components:

>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("delorean-context.xml"))
>>> flux_capacitor_without_drive = assembler.assemble("capacitor-nodrive")
>>> flux_capacitor_with_drive = assembler.assemble("capacitor-withdrive")

Note

If you remember nothing else, remember this:

  1. aglyph.component.Component.dotted_name must be importable.
  2. aglyph.component.Component.factory_name must be relative to the importable dotted_name.

Using fluent API configuration

Here is an equivalent programmatic configuration in a bindings.py module:

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

context = Context("delorean-context")
(context.component("default-drive").
    create("delorean", factory="EquipmentFoundry.get_default_capacitor_drive").
    register())
(context.component("capacitor-nodrive").
    create("delorean.EquipmentFoundry", factory="FluxCapacitor").
    register())
(context.component("capacitor-withdrive").
    create("delorean.EquipmentFoundry", factory="FluxCapacitor.with_capacitor_drive").
    init(ref("default-drive")).
    register())

Describe components for module or class members

Similar in nature to the factory_name property explained in the previous section, the aglyph.component.Component.member_name property provides a way to access objects that are not directly importable.

But there are two key differences between member_name and factory_name:

  1. The object identified by a member_name is not required to be callable. Instead, the object itself is considered to be the component object.
  2. Even if the object identified by a member_name is callable, Aglyph will not call it.

Note

As a consequence of #1, any initialization arguments or keywords that are specified for a component that also specifies a member_name are ignored (i.e. Aglyph does not initialize the member_name object). However, any “setter” dependencies (setter methods, fields, properties) defined for such a component are processed.

As a consequence of #2, you can define components whose objects are of any type, including class types, function types, and (sub)module types.

In the examples below, we will use the Python standard library http.server.HTTPServer class (whose initializer accepts a class object for the request handler class) to demonstrate one possible use of the aglyph.component.Component.member_name property.

Using declarative XML configuration

In a cookbook-context.xml document:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="cookbook-context">
    <component id="request-handler-class"
            dotted-name="http.server"
            member-name="BaseHTTPRequestHandler" />
    <component id="http-server" dotted-name="http.server.HTTPServer">
        <init>
            <arg>
                <tuple>
                    <str>localhost</str>
                    <int>8080</int>
                </tuple>
            </arg>
            <arg reference="request-handler-class" />
        </init>
    </component>
</context>

When the “http-server” component is assembled, its second initialization argument is actually the class http.server.BaseHTTPRequestHandler (as opposed to an instance thereof):

>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("cookbook-context.xml"))
>>> httpd = assembler.assemble("http-server")
>>> httpd.RequestHandlerClass
<class 'http.server.BaseHTTPRequestHandler'>
>>> assembler.assemble("request-handler-class") is httpd.RequestHandlerClass
True

Using fluent API configuration

Here is an equivalent programmatic configuration in a bindings.py module:

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

context = Context("cookbook-context")
(context.component("request-handler-class").
    create("http.server", member="BaseHTTPRequestHandler").
    register())
(context.component("http-server").
    create("http.server.HTTPServer").
    init(("localhost", 8080), ref("request-handler-class")).
    register())

Describe components for Python implementation-specific objects (Stackless, PyPy, IronPython, Jython)

Strictly speaking, there is nothing “special” (from an Aglyph perspective) about the examples presented in the following subsections. They just build upon the previous cookbook recipes Describe a simple component (a class or an unbound factory function), Use a reference to another component as a dependency, and Describe components for static methods, class methods, or nested classes to once again demonstrate that Aglyph can assemble any component that can be described using dotted name notation, even when the class or function is only available to a specific implementation of Python.

Example 1: Describe a component for a Stackless Python or PyPy tasklet

The Stackless Python and PyPy Python implementations support the stackless.tasklet wrapper, which allows any callable to run as a microthread.

The examples below demonstrate the Aglyph configuration for a variation of the sample code given in the Stackless Python “Tasklets” Wiki article.

Using declarative XML configuration

In a cookbook-context.xml document:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="cookbook-context">
    <component id="aCallable-func" dotted-name="cookbook"
            member-name="aCallable" />
    <component id="aCallable-task" dotted-name="stackless.tasklet">
        <init>
            <arg reference="aCallable-func" />
        </init>
    </component>
</context>

Assembling and running this tasklet looks like this:

>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("cookbook-context.xml"))
>>> task = assembler.assemble("aCallable-task")
>>> task.setup("assembled by Aglyph")
>>> task.run()
'aCallable: assembled by Aglyph'

Using fluent API configuration

Below is an example of programmatic configuration, but with a twist - we allow Aglyph to inject the function argument into the task so that we only need to assemble and run it. This works because the stackless.tasklet.setup method has setter method semantics.

In a bindings.py module:

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

context = Context("cookbook-context")
context.component("aCallable-func").create("cookbook", member="aCallable").register()
(context.component("aCallable-task").
    create("stackless.tasklet").
    init(ref("aCallable-func")).
    set(setup="injected by Aglyph").
    register())

Assembling and running this tasklet looks like this:

>>> from aglyph.assembler import Assembler
>>> from bindings import context
>>> assembler = Assembler(context)
>>> task = assembler.assemble("aCallable-task")
>>> task.run()
'aCallable: injected by Aglyph'

Example 2: Describe a component for a .NET XmlReader

IronPython developers have access to the .NET Framework Standard Library and any custom assemblies via the clr module, allowing any .NET namespace to be loaded into the IronPython runtime and used.

In the examples below, we use System.Xml.DtdProcessing, System.Xml.ValidationType, System.Xml.XmlReaderSettings, and System.Xml.XmlReader to configure an XML reader that parses a fictitious “AppConfig.xml” document.

Warning

When using IronPython, the .NET namespace for any class referenced in an Aglyph component must be loaded prior to asking Aglyph to assemble the component. (Otherwise, those classes would not be importable in IronPython.)

In the examples given below, this means that the following statements must be executed before aglyph.assembler.Assembler.assemble() or aglyph.binder.Binder.lookup() is called (because the “System.Xml” namespace is not present by default):

>>> import clr
>>> clr.AddReference("System.Xml")

Using declarative XML configuration

In a dotnet-context.xml document:

<?xml version="1.0" ?>
<context id="dotnet-context">
    <component id="dtd-parse" dotted-name="System.Xml"
            member-name="DtdProcessing.Parse" />
    <component id="dtd-validate" dotted-name="System.Xml"
            member-name="ValidationType.DTD" />
    <component id="xmlreader-settings" dotted-name="System.Xml.XmlReaderSettings">
        <attributes>
            <attribute name="IgnoreComments"><true /></attribute>
            <attribute name="IgnoreProcessingInstructions"><true /></attribute>
            <attribute name="IgnoreWhitespace"><true /></attribute>
            <attribute name="DtdProcessing" reference="dtd-parse" />
            <attribute name="ValidationType" reference="dtd-validate" />
        </attributes>
    </component>
    <component id="app-config-reader" dotted-name="System.Xml.XmlReader"
            factory-name="Create">
        <init>
            <arg><str>file:///C:/Example/Settings/AppConfig.xml</str></arg>
            <arg reference="xmlreader-settings" />
        </init>
    </component>
</context>

With the Aglyph context in place, we can now assemble an XML reader for our fictitious application configuration reader:

>>> import clr
>>> clr.AddReference("System.Xml")
>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("dotnet-context.xml"))
>>> assembler.assemble("app-config-reader")
<System.Xml.XmlValidatingReaderImpl object at 0x000000000000002B [System.Xml.XmlValidatingReaderImpl]>

Using fluent API configuration

Here is an equivalent programmatic configuration in a bindings.py module:

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

context = Context("dotnet-context")
(context.component("dtd-parse").
    create("System.Xml", member="DtdProcessing.Parse").
    register())
(context.component("dtd-validate").
    create("System.Xml", member="ValidationType.DTD").
    register())
(context.component("xmlreader-settings").
    create("System.Xml.XmlReaderSettings").
    set(
        IgnoreComments=True,
        IgnoreProcessingInstructions=True,
        IgnoreWhitespace=True,
        DtdProcessing=Reference("dtd-parse"),
        ValidationType=Reference("dtd-validate")).
    register())
(context.component("app-config-reader").
    create("System.Xml.XmlReader", factory_name="Create").
    init(
        "file:///C:/Example/Settings/AppConfig.xml",
        Reference("xmlreader-settings")).
    register())

The code to assemble the fictitious application configuration reader looks like this:

>>> import clr
>>> clr.AddReference("System.Xml")
>>> from aglyph.assembler import Assembler
>>> from bindings import context
>>> assembler = Assembler(context)
>>> assembler.assemble("app-config-reader")
<System.Xml.XmlValidatingReaderImpl object at 0x000000000000002B [System.Xml.XmlValidatingReaderImpl]>

Example 3: Describe a component for a Java™ LinkedHashMap

Jython developers have direct access to the Java™ Platform API and any custom JARs in the runtime CLASSPATH.

In the examples below, we use java.util.Collections#synchronizedMap(java.util.Map) and java.util.LinkedHashMap to configure a thread-safe, insertion-order hash map.

Using declarative XML configuration

In a java-context.xml document:

<?xml version="1.0" encoding="UTF-8" ?>
<context id="java-context">
    <component id="java.util.LinkedHashMap" />
    <component id="threadsafe-ordered-map" dotted-name="java.util.Collections"
            factory-name="synchronizedMap">
        <init>
            <arg reference="java.util.LinkedHashMap" />
        </init>
    </component>
</context>

To assemble our map:

>>> from aglyph.assembler import Assembler
>>> from aglyph.context import XMLContext
>>> assembler = Assembler(XMLContext("java-context.xml"))
>>> mapping = assembler.assemble("threadsafe-ordered-map")
>>> mapping.__class__
<type 'java.util.Collections$SynchronizedMap'>

Using fluent API configuration

Here is an equivalent programmatic configuration in a bindings.py module:

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

context = Context("java-context")
context.component("java.util.LinkedHashMap")
(context.component("threadsafe-ordered-map").
    create("java.util.Collections", factory="synchronizedMap").
    init(Reference("java.util.LinkedHashMap")).
    register())

Assembling the map looks like this:

>>> from aglyph.assembler import Assembler
>>> from bindings import context
>>> assembler = Assembler(context)
>>> mapping = assembler.assemble("threadsafe-ordered-map")
>>> mapping.__class__
<type 'java.util.Collections$SynchronizedMap'>

Use a custom XML parser for XMLContext

Aglyph uses the xml.etree.ElementTree API for processing context documents. By default, ElementTree uses the Expat XML parser (via xml.etree.ElementTree.XMLParser) to build element structures.

However, developers may subclass xml.etree.ElementTree.XMLParser to use any XML parser; simply pass an instance of the subclass to aglyph.context.XMLContext as the parser keyword argument.

Note

For a real, working example, please refer to aglyph._compat.CLRXMLParser, which is an xml.etree.ElementTree.XMLParser subclass that uses the .NET System.Xml.XmlReader parser.

Clear the Aglyph singleton, weakref, and borg memory caches

aglyph.assembler.Assembler automatically caches objects of singleton and weakref components, as well as the shared-state dictionaries of borg components, in memory. There is no automatic eviction strategy.

These caches may be cleared explicitly by calling aglyph.assembler.Assembler.clear_singletons(), aglyph.assembler.Assembler.clear_weakrefs(), or aglyph.assembler.Assembler.clear_borgs(), respectively. Each method returns a list of component IDs that were evicted.

Warning

There are some limitations on weakref caching, particularly with respect to lifecycle methods. Please see aglyph.assembler.Assembler.clear_weakrefs() for details.