Other notable usage scenarios¶
Release: | 3.0.0.post1 |
---|
- Describe components for static methods, class methods, or nested classes
- Describe components for module or class members
- Describe components for Python implementation-specific objects (Stackless, PyPy, IronPython, Jython)
- Use a custom XML parser for XMLContext
- Clear the Aglyph singleton, weakref, and borg memory caches
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:
- The class named
delorean.EquipmentFoundry.FluxCapacitor
. - The class method named
delorean.EquipmentFoundry.FluxCapacitor.with_capacitor_drive
. - 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
ordelorean.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:
aglyph.component.Component.dotted_name
must be importable.aglyph.component.Component.factory_name
must be relative to the importabledotted_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
:
- The object identified by a
member_name
is not required to be callable. Instead, the object itself is considered to be the component object. - 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.