aglyph.component — Defining components and their dependencies — Aglyph 3.0.0 documentation

# `aglyph.component` — Defining components and their dependencies

Release: | 3.0.0

The classes in this module are used to define components and their dependencies within an Aglyph context (`aglyph.context.Context`).

Components and Templates

`aglyph.component.Component` tells an `aglyph.assembler.Assembler` how to create objects of a particular type. The component defines the initialization and/or attribute dependencies for objects of that type, as well as the assembly strategy and any lifecycle methods that should be called.

`aglyph.component.Template` is used to describe dependencies (initialization and/or attribute) and lifecylce methods that are shared by multiple components. Templates are similar to abstract classes; they cannot be assembled, but are instead used as “parents” of other components (or templates) to achieve a sort of “configuration inheritance.”

Note

Both `Component` and `Template` may serve as the parent of any other component or template; but only components may be assembled.

References and Evaluators

A `aglyph.component.Reference` may be used as the value of any initialization argument (positional or keyword) or attribute in a component or template. Its value must be the unique ID of a component in the same context. At assembly time, the assembler will resolve the reference into an object of the component to which it refers.

An `aglyph.component.Evaluator` is similar to a `functools.partial()` object. It stores a callable factory (function or class) and related initialization arguments, and can be called repeatedly to produce new objects. (Unlike a `functools.partial()` object, though, an `Evaluator` will resolve any initialization argument that is a `aglyph.component.Reference`, `functools.partial()`, or `Evaluator` before calling the factory.)

Strategies and Lifecycle methods

`aglyph.component.Strategy` defines the assembly strategies supported by Aglyph (“prototype”, “singleton”, “borg”, “weakref” and “_imported”).

`LifecycleState` defines assmebly states for components at which Aglyph supports calling named methods on the objects of those components. (Such methods may be used to perform specialized initialization or disposal, for example.)

`aglyph.component.` `Strategy` = Strategy(PROTOTYPE='prototype', SINGLETON='singleton', BORG='borg', WEAKREF='weakref')

  Define the component assembly strategies implemented by Aglyph.

  “prototype”

  A new object is always created, initialized, wired, and returned.

  Note

  “prototype” is the default assembly strategy for Aglyph components that do not specify a member name.

  “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.unique_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”

  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.

  “_imported”

  New in version 3.0.0.

  Note

  The “_imported” strategy is only valid (and is the only allowed value) when member_name is specified for a component.

  Since this strategy is implicitly assigned and is intended for internal use by Aglyph itself, it is not exposed on the `Strategy` named tuple.

  An already-created (loaded) object is obtained from an imported module or class (as opposed to creating the object directly). Such components will always resolve (i.e. be assembled) to the same objects; but those objects are not cached by Aglyph as they will exhibit “natural” singleton behavior so long as the containing module is referenced in `sys.modules`.

  It is not necessary to explicitly set the strategy to “_imported” when using member_name - Aglyph will default to “_imported” when it sees a non-empty member_name defined.

  Warning

  Explicitly setting strategy=”_imported” without specifying member_name will raise `AglyphError`.

  Specifying member_name with any explicit strategy other than “_imported” will ignore the explicit strategy, change it to “_imported” internally, and issue a `UserWarning` to that effect.

`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 a `RuntimeWarning` 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:

  1. The method named by the object’s `Component.<lifecycle-state>` property.
  2. If the object’s `Component.parent_id` is not `None`, the method named by the corresponding parent `Template.<lifecycle-state>` or `Component.<lifecycle-state>` property. (If necessary, lookup continues by examining the parent-of-the-parent and so on.)
  3. 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.

  Note

  Either a `Component` or `Template` may serve as the parent identified by a `parent_id`.

  However, only a `Component` may actually be assembled into a usable object. (A `Template` is like an abstract class - it defines common dependencies and/or lifecycle methods, but it cannot be assembled.)

class `aglyph.component.` `Reference`

  Bases: `str`

  A placeholder 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 a `Reference` can be either a dotted-name or a user-provided unique ID.

  A `Reference` can be used as an argument for an `Evaluator`, and can be assembled directly by an `aglyph.assembler.Assembler`.

  Warning

  A `Reference` value MUST correspond to a component ID in the same context.

  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 of `Reference` 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 as `str` because the Sphinx documentation generator for Aglyph runs under CPython 3.

  Create a new reference to referent.

  Parameters: | referent – the object that the reference will represent
  Raises: | aglyph.AglyphError – if referent is a class, function, or module but cannot be imported

  If referent is a string, it is assumed to be a valid `Component.unique_id` and its value is returned as a `Reference`.

  If referent is a class, function, or module, its importable dotted name is returned as a `Reference`.

  Warning

  If referent is a class, function, or module, it must be importable.

class `aglyph.component.` `Evaluator`(factory, *args, **keywords)

  Bases: `aglyph.component._InitializationSupport`

  Perform lazy creation of objects.

  Parameters: | factory – any callable that returns an object args (tuple) – the positional arguments to func keywords (dict) – the keyword arguments to func

  An `Evaluator` is similar to a `functools.partial()` in that they both collect a function and related arguments into a `callable` 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 a `Reference`, a `functools.partial()`, or even another `Evaluator`, which needs to be resolved (i.e. assembled/called) before calling factory.

  When an `Evaluator` is called, its arguments (positional and keyword) are each resolved in one of the following ways:

  - If the argument value is a `Reference`, it is assembled (by an `aglyph.assembler.Assembler` reference passed to `__call__()`)
  - If the argument value is an `Evaluator` or a `functools.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.
  `factory`

    The `callable` that creates new objects (read-only).

  `__call__`(assembler)

    Call `factory(*args, **keywords)` and return the new object.

    Parameters: | assembler (aglyph.assembly.Assembler) – the assembler that will be used to assemble any `Reference` encountered in this evaluator’s positional and keyword arguments

class `aglyph.component.` `Template`(unique_id, parent_id=None, after_inject=None, before_clear=None)

  Bases: `aglyph.component._DependencySupport`

  Support for configuring type 1 (setter) and type 2 (constructor) injection, and lifecycle methods.

  Parameters: | unique_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
  Raises: | ValueError – if unique_id is `None` or empty

  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 a `Component` and use that component’s ID as the `Component.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` or `Template.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` takes precedence over any after_inject method name specified for the template’s parent or context.

  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()`, or `aglyph.assembler.Assembler.clear_weakrefs()`.

  Note

  `Template.before_clear` takes precedence over any before_clear method name specified for the template’s parent or context.

  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 (read-only).

  `before_clear`

    The name of the component object method that will be called immediately before the object is cleared from cache (read-only).

    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=None, parent_id=None, after_inject=None, before_clear=None)

  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) – the context-unique identifier for this component dotted_name (str) – an importable dotted name factory_name (str) – names a `callable` member of objects of this component member_name (str) – names any member of objects of this component 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 is used as the component’s dotted name and must be an importable dotted name.

  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`, or `classmethod`. See `factory_name` for details.

  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:

  1. member_name is not restricted to callable members; it may identify any member (attribute, property, nested class).
  2. 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`, and `classmethod` obejcts, as well as simple attributes or properties, as components and dependencies. See `member_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.

  New in version 3.0.0: When `member_name` is specified, the strategy must be “_imported”. Aglyph will use the “_imported” strategy automatically for components that specify member_name; setting strategy to anything other than “_imported” when specifying member_name will issue `UserWarning`.

  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__`!

  parent_id is the context-unique ID of a `Template` (or another `Component`) that defines default dependencies and/or lifecycle methods for this component.

  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` takes precedence over any after_inject method names specified for the component’s parent or context.

  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()`, or `aglyph.assembler.Assembler.clear_weakrefs()`.

  Note

  `Component.before_clear` takes precedence over any before_clear method names specified for the component’s parent or context.

  Warning

  The before_clear keyword argument has no meaning for, and is ignored by, “prototype” components. If before_clear is specified for a prototype component, a `UserWarning` is issued when the component is defined, and the component’s `before_clear` attribute is set to `None`.

  Warning

  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 `aglyph.assembler.clear_weakrefs()` method will issue a `RuntimeWarning` (see that method’s documentation for more details).

  Once a `Component` instance is initialized, the `args` (`list`), `keywords` (`dict`), and `attributes` (`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.info")
      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`
  - 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
  `dotted_name`

    The importable dotted name for objects of this component (read-only).

  `factory_name`

    The name of a `callable` member of `dotted_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 via `staticmethod` or `classmethod`.

    Consider the following:

        # module.py
        class Example:
            class Nested:
                pass

    The dotted name “module.Example.Nested” is not importable, and so cannot be used as a component’s `unique_id` or `dotted_name`. To assemble objects of this type, use `factory_name` to identify the callable factory (the Nested class, in this example) that is accessible through the importable “module.Example”:

        component = Component(
            "nested-object", dotted_name="module.Example",
            factory_name="Nested")

    Or using 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. The following example is equivalent to the above:

        component = Component(
            "nested-object", dotted_name="module",
            factory_name="Example.Nested")

    Or again using 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, and `factory_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 example shows how to define a component that will produce the `module.Example.Nested` class itself when assembled:

        component = Component(
            "nested-class", dotted_name="module.Example",
            member_name="Nested")

    Or using 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. The following example is equivalent to the above:

        component = Component(
            "nested-class", dotted_name="module",
            member_name="Example.Nested")

    Or again using 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, and `member_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 any `args` and `keywords`.

    On assembly, if any initialization arguments and/or keyword arguments have been defined for such a component, they are discarded and a WARNING-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).