Exemplo n.º 1
0
 def test_unique_id_cannot_be_empty(self):
     # use __class__ so test is reusable for Template & Component
     e_expected = ValueError(
         "%s unique ID must not be None or empty" %
             name_of(self._support.__class__))
     assertRaisesWithMessage(
         self, e_expected, self._support.__class__, "")
Exemplo n.º 2
0
 def __repr__(self):
     return (
         "%s.%s(%r, dotted_name=%r, factory_name=%r, member_name=%r, "
         "strategy=%r, parent_id=%r, after_inject=%r, before_clear=%r)") % (
             self.__class__.__module__, name_of(self.__class__),
             self._unique_id, self._dotted_name, self._factory_name,
             self._member_name, self._strategy, self._parent_id,
             self._after_inject, self._before_clear)
Exemplo n.º 3
0
    def register(self, definition):
        """Add a component or template *definition* to this context.

        :arg definition:
           a :class:`Component` or :class:`Template` object
        :raise AglyphError:
           if a component or template with the same unique ID is already
           registered in this context

        .. note::
           To **replace** an already-registered component or template
           with the same unique ID, use :meth:`dict.__setitem__`
           directly.

        """
        if definition.unique_id in self:
            raise AglyphError(
                "%s with ID %r already mapped in %s" %
                (name_of(definition.__class__), definition.unique_id, self))
        self[definition.unique_id] = definition
Exemplo n.º 4
0
    def register(self, definition):
        """Add a component or template *definition* to this context.

        :arg definition:
           a :class:`Component` or :class:`Template` object
        :raise AglyphError:
           if a component or template with the same unique ID is already
           registered in this context

        .. note::
           To **replace** an already-registered component or template
           with the same unique ID, use :meth:`dict.__setitem__`
           directly.

        """
        if definition.unique_id in self:
            raise AglyphError(
                "%s with ID %r already mapped in %s" % (
                    name_of(definition.__class__), definition.unique_id, self))
        self[definition.unique_id] = definition
Exemplo n.º 5
0
def format_dotted_name(obj):
    """Return the importable dotted-name string for *obj*.

    :param obj:
       an **importable** class, function, or module
    :return:
       a dotted name representing *obj*
    :rtype:
       :obj:`str`
    :raise AglyphError:
       if *obj* does not have a resolvable (importable) dotted name

    The dotted name returned by this function is a *"dotted_name.NAME"*
    or *"dotted_name"* string for *obj* that represents a valid absolute
    import statement according to the following productions:

    .. productionlist::
       absolute_import_stmt: "from" dotted_name "import" NAME
                           : | "import" dotted_name
       dotted_name: NAME ('.' NAME)*

    .. note::
       This function is the inverse of :func:`resolve_dotted_name`.

    .. warning::
       This function will attempt to use the ``__qualname__`` attribute,
       which is only available in Python 3.3+. When ``__qualname__`` is
       **not** available, ``__name__`` is used instead.

       .. seealso:: :pep:`3155`, :func:`aglyph._compat.name_of`

    """
    if not _importable(obj):
        raise AglyphError("%r does not have an importable dotted name" % obj)

    if not ismodule(obj):
        return "%s.%s" % (obj.__module__, name_of(obj))
    else:
        return obj.__name__
Exemplo n.º 6
0
def format_dotted_name(obj):
    """Return the importable dotted-name string for *obj*.

    :param obj:
       an **importable** class, function, or module
    :return:
       a dotted name representing *obj*
    :rtype:
       :obj:`str`
    :raise AglyphError:
       if *obj* does not have a resolvable (importable) dotted name

    The dotted name returned by this function is a *"dotted_name.NAME"*
    or *"dotted_name"* string for *obj* that represents a valid absolute
    import statement according to the following productions:

    .. productionlist::
       absolute_import_stmt: "from" dotted_name "import" NAME
                           : | "import" dotted_name
       dotted_name: NAME ('.' NAME)*

    .. note::
       This function is the inverse of :func:`resolve_dotted_name`.

    .. warning::
       This function will attempt to use the ``__qualname__`` attribute,
       which is only available in Python 3.3+. When ``__qualname__`` is
       **not** available, ``__name__`` is used instead.

       .. seealso:: :pep:`3155`, :func:`aglyph._compat.name_of`

    """
    if not _importable(obj):
        raise AglyphError("%r does not have an importable dotted name" % obj)

    if not ismodule(obj):
        return "%s.%s" % (obj.__module__, name_of(obj))
    else:
        return obj.__name__
Exemplo n.º 7
0
    def __init__(self, context_id, after_inject=None, before_clear=None):
        """
        :arg str context_id:
           an identifier for this context
        :keyword str after_inject:
           specifies the name of the method that will be called (if it
           exists) on **all** component objects after all of their
           dependencies have been injected
        :keyword str before_clear:
           specifies the name of the method that will be called (if it
           exists) on **all** singleton, borg, and weakref objects
           immediately before they are cleared from cache

        """
        #PYVER: arguments to super() are implicit under Python 3
        super(Context, self).__init__()

        if not context_id:
            raise AglyphError(
                "%s ID must not be None or empty" % name_of(self.__class__))

        self._context_id = context_id
        self._after_inject = after_inject
        self._before_clear = before_clear
Exemplo n.º 8
0
    def __init__(self, context_id, after_inject=None, before_clear=None):
        """
        :arg str context_id:
           an identifier for this context
        :keyword str after_inject:
           specifies the name of the method that will be called (if it
           exists) on **all** component objects after all of their
           dependencies have been injected
        :keyword str before_clear:
           specifies the name of the method that will be called (if it
           exists) on **all** singleton, borg, and weakref objects
           immediately before they are cleared from cache

        """
        #PYVER: arguments to super() are implicit under Python 3
        super(Context, self).__init__()

        if not context_id:
            raise AglyphError("%s ID must not be None or empty" %
                              name_of(self.__class__))

        self._context_id = context_id
        self._after_inject = after_inject
        self._before_clear = before_clear
Exemplo n.º 9
0
 def __repr__(self):
     return "%s.%s(%r, parent_id=%r, after_inject=%r, before_clear=%r)" % (
         self.__class__.__module__, name_of(self.__class__),
         self._unique_id, self._parent_id, self._after_inject,
         self._before_clear)
Exemplo n.º 10
0
    def __init__(
            self, unique_id, parent_id=None,
            after_inject=None, before_clear=None):
        """
        :arg str unique_id:
           context-unique identifier for this template
        :keyword str parent_id:
           specifies the ID of a template or component that describes
           the default dependencies and/or lifecyle methods for this
           template
        :keyword str after_inject:
           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
        :keyword str before_clear:
           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
        :raise ValueError:
           if *unique_id* is ``None`` or empty

        .. note::
           A ``Template`` cannot be assembled (it is equivalent to an
           abstract class).

           However, a :class:`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
           :class:`Component` and use that component's ID as the
           :attr:`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 :attr:`Component.parent_id`.

        *parent_id* is **another** :attr:`Component.unique_id` or
        :attr:`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
        :meth:`aglyph.assembler.Assembler.clear_singletons()`,
        :meth:`aglyph.assembler.Assembler.clear_borgs()`, or
        :meth:`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 :class:`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
           :attr:`logging.WARNING` message is emitted.

        """
        #PYVER: arguments to super() are implicit under Python 3
        super(Template, self).__init__()

        if not unique_id:
            raise ValueError(
                "%s unique ID must not be None or empty" %
                    name_of(self.__class__))

        self._unique_id = unique_id
        self._parent_id = parent_id
        self._after_inject = after_inject
        self._before_clear = before_clear
Exemplo n.º 11
0
 def __str__(self):
     return "<%s %r @%08x>" % (
         name_of(self.__class__), self._unique_id, id(self))
Exemplo n.º 12
0
 def __str__(self):
     return "<%s %s @%08x>" % (
         name_of(self.__class__), self._factory.__name__, id(self))
Exemplo n.º 13
0
 def __repr__(self):
     return "%s.%s(%r, *%r **%r)" % (
         self.__class__.__module__, name_of(self.__class__),
         self._factory, self._args, self._keywords)
Exemplo n.º 14
0
 def test_name_of_module_level_function(self):
     self.assertEqual("outer_function", name_of(outer_function))
Exemplo n.º 15
0
 def test_name_of_nested_function(self):
     self.assertEqual(
         "outer_function.<locals>.nested_function" if is_python_3
             else "nested_function",
         name_of(outer_function()))
Exemplo n.º 16
0
 def __str__(self):
     return "<%s %r @%08x>" % (name_of(
         self.__class__), self._context_id, id(self))
Exemplo n.º 17
0
    def __init__(
            self, source, parser=None,
            default_encoding=sys.getdefaultencoding()):
        """
        :arg source:
           a filename or stream from which XML data is read
        :keyword xml.etree.ElementTree.XMLParser parser:
           the ElementTree parser to use (instead of Aglyph's default)
        :keyword str default_encoding:
           the default character set used to encode certain element
           content
        :raise AglyphError:
           if unexpected elements are encountered, or if expected
           elements are *not* encountered, in the document structure

        In most cases, *parser* should be left unspecified. Aglyph's
        default parser will be sufficient for all but extreme edge
        cases.

        *default_encoding* is the character set used to encode
        ``<bytes>`` (or ``<str>`` under Python 2) element content when
        an **@encoding** attribute is *not* specified on those elements.
        It defaults to the system-dependent value of
        :func:`sys.getdefaultencoding`. **This is not related to the
        document encoding!**

        .. note::
           Aglyph uses a non-validating XML parser by default, so DTD
           conformance is **not** enforced at runtime. It is recommended
           that XML contexts be validated at least once (manually)
           during testing.

           An :class:`AglyphError` *will* be raised under certain
           conditions (an unexpected element is encounted, or an
           expected element is *not* encountered), but Aglyph does not
           "reinvent the wheel" by implementing strict validation in
           the parsing logic.

        .. warning::
           Although Aglyph contexts are :class:`dict` types,
           ``XMLContext`` does not permit the same unique ID to be
           (re-)mapped multiple times.

           Attempting to define more than one ``<component>`` or
           ``<template>`` with the same ID will raise
           :class:`AglyphError` when the document is parsed.

           **After** an Aglyph ``<context>`` document has been
           successfully parsed, a unique component or template ID can be
           re-mapped using standard :class:`dict` protocols.

           .. seealso::
              Validity constraint: ID
                 https://www.w3.org/TR/REC-xml/#id

        """
        if parser is None:
            parser = AglyphDefaultXMLParser(target=DoctypeTreeBuilder())
        tree = ET.parse(source, parser=parser)
        root = tree.getroot()
        if root.tag != "context":
            raise AglyphError("expected root <context>, not <%s>" % root.tag)

        #PYVER: arguments to super() are implicit under Python 3
        super(XMLContext, self).__init__(
            root.get("id"), after_inject=root.get("after-inject"),
            before_clear=root.get("before-clear"))

        # alias the correct _parse_str method based on Python version
        if is_python_3:
            self._parse_str = self.__parse_str_as_text
        else:
            self._parse_str = self.__parse_str_as_data

        self._default_encoding = default_encoding

        for element in list(root):
            if element.tag == "component":
                depsupport = self._create_component(element)
            elif element.tag == "template":
                depsupport = self._create_template(element)
            else:
                raise AglyphError(
                    "unexpected element: /context/%s" % element.tag)
            self.register(depsupport)
            self._process_dependencies(depsupport, element)

        self.__repr = "%s.%s(%r, parser=%r, default_encoding=%r)" % (
            self.__class__.__module__, name_of(self.__class__),
            source, parser, default_encoding)
Exemplo n.º 18
0
    def __init__(self,
                 source,
                 parser=None,
                 default_encoding=sys.getdefaultencoding()):
        """
        :arg source:
           a filename or stream from which XML data is read
        :keyword xml.etree.ElementTree.XMLParser parser:
           the ElementTree parser to use (instead of Aglyph's default)
        :keyword str default_encoding:
           the default character set used to encode certain element
           content
        :raise AglyphError:
           if unexpected elements are encountered, or if expected
           elements are *not* encountered, in the document structure

        In most cases, *parser* should be left unspecified. Aglyph's
        default parser will be sufficient for all but extreme edge
        cases.

        *default_encoding* is the character set used to encode
        ``<bytes>`` (or ``<str>`` under Python 2) element content when
        an **@encoding** attribute is *not* specified on those elements.
        It defaults to the system-dependent value of
        :func:`sys.getdefaultencoding`. **This is not related to the
        document encoding!**

        .. note::
           Aglyph uses a non-validating XML parser by default, so DTD
           conformance is **not** enforced at runtime. It is recommended
           that XML contexts be validated at least once (manually)
           during testing.

           An :class:`AglyphError` *will* be raised under certain
           conditions (an unexpected element is encounted, or an
           expected element is *not* encountered), but Aglyph does not
           "reinvent the wheel" by implementing strict validation in
           the parsing logic.

        .. warning::
           Although Aglyph contexts are :class:`dict` types,
           ``XMLContext`` does not permit the same unique ID to be
           (re-)mapped multiple times.

           Attempting to define more than one ``<component>`` or
           ``<template>`` with the same ID will raise
           :class:`AglyphError` when the document is parsed.

           **After** an Aglyph ``<context>`` document has been
           successfully parsed, a unique component or template ID can be
           re-mapped using standard :class:`dict` protocols.

           .. seealso::
              Validity constraint: ID
                 https://www.w3.org/TR/REC-xml/#id

        """
        if parser is None:
            parser = AglyphDefaultXMLParser(target=DoctypeTreeBuilder())
        tree = ET.parse(source, parser=parser)
        root = tree.getroot()
        if root.tag != "context":
            raise AglyphError("expected root <context>, not <%s>" % root.tag)

        #PYVER: arguments to super() are implicit under Python 3
        super(XMLContext, self).__init__(root.get("id"),
                                         after_inject=root.get("after-inject"),
                                         before_clear=root.get("before-clear"))

        # alias the correct _parse_str method based on Python version
        if is_python_3:
            self._parse_str = self.__parse_str_as_text
        else:
            self._parse_str = self.__parse_str_as_data

        self._default_encoding = default_encoding

        for element in list(root):
            if element.tag == "component":
                depsupport = self._create_component(element)
            elif element.tag == "template":
                depsupport = self._create_template(element)
            else:
                raise AglyphError("unexpected element: /context/%s" %
                                  element.tag)
            self.register(depsupport)
            self._process_dependencies(depsupport, element)

        self.__repr = "%s.%s(%r, parser=%r, default_encoding=%r)" % (
            self.__class__.__module__, name_of(
                self.__class__), source, parser, default_encoding)
Exemplo n.º 19
0
 def test_name_of_nested_class(self):
     # PEP-3155 - __qualname__ is only available in Python 3.3+
     self.assertEqual(
         "ModuleClass.NestedClass" if is_python_3 else "NestedClass",
         name_of(ModuleClass.NestedClass))
Exemplo n.º 20
0
 def __str__(self):
     return "<%s @%08x %s>" % (
         name_of(self.__class__), id(self), self._assembler)
Exemplo n.º 21
0
 def __repr__(self):
     return "%s.%s()" % (
         self.__class__.__module__, name_of(self.__class__))
Exemplo n.º 22
0
 def __str__(self):
     return "<%s @%08x>" % (name_of(self.__class__), id(self))
Exemplo n.º 23
0
 def __repr__(self):
     return "%s.%s(%r)" % (
         self.__class__.__module__, name_of(self.__class__), self._context)
Exemplo n.º 24
0
 def __str__(self):
     return "<%s @%08x %s>" % (
         name_of(self.__class__), id(self), self._context)
Exemplo n.º 25
0
 def __repr__(self):
     return "%s.%s(%r, %r, eager_init=%r)" % (
         self.__class__.__module__, name_of(self.__class__),
         self.bus, self._assembler, self._eager_init)
Exemplo n.º 26
0
 def __str__(self):
     return "<%s %r @%08x>" % (
         name_of(self.__class__), self._context_id, id(self))
Exemplo n.º 27
0
 def test_unique_id_cannot_be_empty(self):
     # use __class__ so test is reusable for Template & Component
     e_expected = ValueError("%s unique ID must not be None or empty" %
                             name_of(self._support.__class__))
     assertRaisesWithMessage(self, e_expected, self._support.__class__, "")
Exemplo n.º 28
0
 def __str__(self):
     return "<%s @%08x %s>" % (name_of(
         self.__class__), id(self), self._context)
Exemplo n.º 29
0
 def __repr__(self):
     return "%s.%s(%r)" % (self.__class__.__module__, name_of(
         self.__class__), self._context)
Exemplo n.º 30
0
 def __str__(self):
     return "<%s @%08x>" % (name_of(self.__class__), id(self))
Exemplo n.º 31
0
 def __repr__(self):
     return "%s.%s(%r, after_inject=%r, before_clear=%r)" % (
         self.__class__.__module__, name_of(self.__class__),
         self._context_id, self._after_inject, self._before_clear)
Exemplo n.º 32
0
 def __repr__(self):
     return "%s.%s()" % (self.__class__.__module__, name_of(self.__class__))
Exemplo n.º 33
0
 def test_context_id_cannot_be_empty(self):
     e_expected = AglyphError(
         "%s ID must not be None or empty" %
             name_of(self._context.__class__))
     assertRaisesWithMessage(self, e_expected, self._context.__class__, "")
Exemplo n.º 34
0
 def test_name_of_module_level_class(self):
     self.assertEqual("ModuleClass", name_of(ModuleClass))