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__, "")
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)
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
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
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__
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
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
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)
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
def __str__(self): return "<%s %r @%08x>" % ( name_of(self.__class__), self._unique_id, id(self))
def __str__(self): return "<%s %s @%08x>" % ( name_of(self.__class__), self._factory.__name__, id(self))
def __repr__(self): return "%s.%s(%r, *%r **%r)" % ( self.__class__.__module__, name_of(self.__class__), self._factory, self._args, self._keywords)
def test_name_of_module_level_function(self): self.assertEqual("outer_function", name_of(outer_function))
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()))
def __str__(self): return "<%s %r @%08x>" % (name_of( self.__class__), self._context_id, id(self))
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)
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)
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))
def __str__(self): return "<%s @%08x %s>" % ( name_of(self.__class__), id(self), self._assembler)
def __repr__(self): return "%s.%s()" % ( self.__class__.__module__, name_of(self.__class__))
def __str__(self): return "<%s @%08x>" % (name_of(self.__class__), id(self))
def __repr__(self): return "%s.%s(%r)" % ( self.__class__.__module__, name_of(self.__class__), self._context)
def __str__(self): return "<%s @%08x %s>" % ( name_of(self.__class__), id(self), self._context)
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)
def __str__(self): return "<%s %r @%08x>" % ( name_of(self.__class__), self._context_id, id(self))
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__, "")
def __str__(self): return "<%s @%08x %s>" % (name_of( self.__class__), id(self), self._context)
def __repr__(self): return "%s.%s(%r)" % (self.__class__.__module__, name_of( self.__class__), self._context)
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)
def __repr__(self): return "%s.%s()" % (self.__class__.__module__, name_of(self.__class__))
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__, "")
def test_name_of_module_level_class(self): self.assertEqual("ModuleClass", name_of(ModuleClass))