def _parse_dict(self, dict_element): """Return a :obj:`dict` evaluator object parsed from *dict_element*. :arg xml.etree.ElementTree.Element dict_element: a ``<dict>`` element :rtype: :class:`aglyph.component.Evaluator` .. note:: The evaluator returned by this method produces a new :obj:`dict` object each time it is called. """ # a list of 2-tuples, (key, value), used to initialize a dictionary items = [] for element in list(dict_element): if element.tag != "item": raise AglyphError("unexpected element: dict/%s" % element.tag) children = list(element) child_tags = [child.tag for child in children] if child_tags == ["key", "value"]: key_element, value_element = children else: raise AglyphError("expected item/key, item/value; found %s" % ", ".join("item/%s" % ctag for ctag in child_tags)) items.append((self._unserialize_element_value(key_element), self._unserialize_element_value(value_element))) return evaluate(dict, items)
def _process_value_element(self, value_element, parent_tag): """Create a usable Python object from *value_element*. :arg xml.etree.ElementTree.Element value_element: an element that describes a value :arg str parent_tag: the name of the *value_element* parent element :return: a Python object that is the value of *value_element* *value_element* must be a ``<False />``, ``<True />``, ``<None />``, ``<bytes>``, ``<str>``, ``<unicode>``, ``<int>``, ``<float>``, ``<tuple>``, ``<list>``, ``<dict>``, or ``<set>`` element. This method will return one of the following types, dependent upon the element: * an object of a Python built-in type * a Python built-in constant * an Aglyph :class:`Reference` * an Aglyph :class:`Evaluator` """ parse_value = getattr(self, "_parse_%s" % value_element.tag, None) if parse_value is None: raise AglyphError("unexpected element: %s/%s" % (parent_tag, value_element.tag)) return parse_value(value_element)
def _unserialize_element_value(self, valuecontainer_element): """Return the appropriate object, value, Aglyph reference, or Aglyph evaluator for *element*. :arg xml.etree.ElementTree.Element valuecontainer_element: an element with a single child element that describes a value :return: the runtime object that is the result of processing *valuecontainer_element* :rtype: an object of a Python built-in type, a Python built-in constant, a :class:`Reference`, or an :class:`Evaluator` *valuecontainer_element* must be an ``<arg>``, ``<attribute>``, ``<key>``, or ``<value>`` element. """ component_id = valuecontainer_element.get("reference") if component_id is not None: return ref(component_id) children = list(valuecontainer_element) if len(children) != 1: vtag = valuecontainer_element.tag raise AglyphError( "<%s> must contain exactly one child element; found %s" % (vtag, ", ".join( "%s/%s" % (vtag, c.tag) for c in children) if children else "no children")) return self._process_value_element(children[0], valuecontainer_element.tag)
def test_register_fails_on_nonimportable_object(self): builder = self._builder_type( _MockContext(), dummy.ModuleClass.NestedClass) e_expected = AglyphError( "%r does not have an importable dotted name" % dummy.ModuleClass.NestedClass) assertRaisesWithMessage(self, e_expected, builder.register)
def test_factory_name_and_member_name_are_mutually_exclusive(self): e_expected = AglyphError( "only one of factory_name or member_name may be specified") assertRaisesWithMessage(self, e_expected, Component, "test", factory_name="factory", member_name="member")
def _process_attributes(self, attributes_element): """Yield attributes (fields, setter methods, or properties) parsed from *attributes_element*. :arg xml.etree.ElementTree.Element attributes_element: an ``<attributes>`` element :return: an iterator that yields the 2-tuple ``(name, value)`` """ for element in list(attributes_element): if element.tag != "attribute": raise AglyphError("unexpected element: attributes/%s" % element.tag) name = element.get("name") if not name: raise AglyphError( "attribute/@name is required and cannot be empty") value = self._unserialize_element_value(element) yield (name, value)
def _process_init(self, init_element): """Yield initialization arguments (positional and keyword) parsed from *init_element*. :arg xml.etree.ElementTree.Element init_element: an ``<init>`` element :return: an iterator that yields the 2-tuple ``(keyword, value)`` .. note:: Both positional and keyword arguments are yielded by this method as a 2-tuple ``(keyword, value)``. For positional arguments, ``keyword`` will be ``None``. """ for element in list(init_element): if element.tag != "arg": raise AglyphError("unexpected element: init/%s" % element.tag) keyword = element.get("keyword") if keyword == "": raise AglyphError("arg/@keyword cannot be empty") value = self._unserialize_element_value(element) yield (keyword, value)
def _process_dependencies(self, depsupport, depsupport_element): """Parse the child elements of *depsupport_element* to populate the *depsupport* initialization arguments and attributess. :arg depsupport: a :class:`Template` or :class:`Component` :arg xml.etree.ElementTree.Element depsupport_element: the ``<template>`` or ``<component>`` that was parsed to create *depsupport* """ children = list(depsupport_element) child_tags = [elem.tag for elem in children] if child_tags == ["init"]: init_element = children[0] attributes_element = None elif child_tags == ["init", "attributes"]: init_element, attributes_element = children elif child_tags == ["attributes"]: init_element = None attributes_element = children[0] elif not child_tags: init_element = None attributes_element = None else: dtag = depsupport_element.tag raise AglyphError("unexpected element: %s/%s" % (depsupport_element.tag, child_tags[0])) if init_element is not None: for (keyword, value) in self._process_init(init_element): if keyword is None: depsupport.args.append(value) else: depsupport.keywords[keyword] = value if attributes_element is not None: for (name, value) in self._process_attributes(attributes_element): depsupport.attributes[name] = value self.__log.debug("%r has args=%r, keywords=%r, attributess=%r", depsupport, depsupport.args, depsupport.keywords, depsupport.attributes)
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 _parse_eval(self, eval_element): """Return a partial object that will evaluate an expression parsed from *eval_element*. :arg xml.etree.ElementTree.Element eval_element:\ an ``<eval>`` element :rtype: :obj:`functools.partial` ..versionadded:: 3.0.0 The partial object will use Python's :func:`ast.literal_eval` function to evaluate the expression when it is called. (Prior versions of Aglyph used the builtin :obj:`eval` function.) .. seealso:: `Eval really is dangerous <http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html>`_ Ned Batchelder's insanely thorough discussion of :obj:`eval` """ if eval_element.text is None: raise AglyphError("<eval> cannot be an empty element") return partial(literal_eval, eval_element.text)
def _initialize(self, component): """Create a new *component* object initialized with its dependencies. :arg aglyph.component.Component component: a component definition :return: an initialized object of *component* This method performs **type 3 (constructor)** dependency injection. .. versionchanged:: 2.1.0 If *component* specifies a :attr:`Component.member_name` **and** either :attr:`Component.args` or :attr:`Component.keywords`, then a :class:`RuntimeWarning` is issued. """ initializer = self._resolve_initializer(component) if component.member_name is None: (args, keywords) = self._resolve_args_and_keywords(component) try: # issues/2: always use the __call__ protocol to initialize obj = initializer(*args, **keywords) except Exception as e: raise AglyphError( "failed to initialize object of component %r" % component.unique_id, e) else: obj = initializer if component.args or component.keywords: msg = ("ignoring args and keywords for component %r " "(uses member_name assembly)") self.__log.warning(msg, component.unique_id) warnings.warn(msg % component.unique_id, RuntimeWarning) return obj
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 test_nonimportable_class(self): e_expected = AglyphError("%r does not have an importable dotted name" % dummy.ModuleClass.NestedClass) assertRaisesWithMessage(self, e_expected, Reference, dummy.ModuleClass.NestedClass)
def test_bound_method(self): bound_method = dummy.ModuleClass(None).method e_expected = AglyphError("%r does not have an importable dotted name" % bound_method) assertRaisesWithMessage(self, e_expected, Reference, bound_method)
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_nonimportable_function(self): nested_function = dummy.outer_function() e_expected = AglyphError("%r does not have an importable dotted name" % nested_function) assertRaisesWithMessage(self, e_expected, Reference, nested_function)
def test_fails_to_identify_instance(self): # instance doesn't have __qualname__ or __name__ instance = dummy.ModuleClass(None) e_expected = AglyphError("%r does not have an importable dotted name" % instance) assertRaisesWithMessage(self, e_expected, _identify, instance)
def test_fails_to_identify_classmethod(self): e_expected = AglyphError("%r does not have an importable dotted name" % dummy.ModuleClass.classmethod_factory) assertRaisesWithMessage(self, e_expected, _identify, dummy.ModuleClass.classmethod_factory)
def test_fails_to_identify_property(self): # property doesn't have __module__, __qualname__, or __name__ e_expected = AglyphError("%r does not have an importable dotted name" % dummy.ModuleClass.prop) assertRaisesWithMessage(self, e_expected, _identify, dummy.ModuleClass.prop)
def test_builtin_object(self): e_expected = AglyphError("79 does not have an importable dotted name") assertRaisesWithMessage(self, e_expected, Reference, 79)
def test_user_object(self): obj = dummy.ModuleClass(None) e_expected = AglyphError("%r does not have an importable dotted name" % obj) assertRaisesWithMessage(self, e_expected, Reference, obj)
def test_process_attributes_unexpected_children(self): stream = bytebuf(self._uresource.encode("utf-8")) e_expected = AglyphError("unexpected element: attributes/unexpected") assertRaisesWithMessage(self, e_expected, XMLContext, stream)
def test_staticmethod(self): e_expected = AglyphError("%r does not have an importable dotted name" % dummy.ModuleClass.staticmethod_factory) assertRaisesWithMessage(self, e_expected, Reference, dummy.ModuleClass.staticmethod_factory)
def test_attribute_name_cannot_be_empty(self): stream = bytebuf(self._uresource.encode("utf-8")) e_expected = AglyphError( "attribute/@name is required and cannot be empty") assertRaisesWithMessage(self, e_expected, XMLContext, stream)
def test_fails_to_identify_none(self): # None doesn't have __module__, __qualname__, or __name__ e_expected = AglyphError("%r does not have an importable dotted name" % None) assertRaisesWithMessage(self, e_expected, _identify, None)
def test_attribute_cannot_be_empty(self): stream = bytebuf(self._uresource.encode("utf-8")) e_expected = AglyphError( "<attribute> must contain exactly one child element; " "found no children") assertRaisesWithMessage(self, e_expected, XMLContext, stream)
def test_fails_to_identify_nested_function(self): nested_function = dummy.outer_function() e_expected = AglyphError("%r does not have an importable dotted name" % nested_function) assertRaisesWithMessage(self, e_expected, _identify, nested_function)
def test_attribute_rejects_multiple_children(self): stream = bytebuf(self._uresource.encode("utf-8")) e_expected = AglyphError( "<attribute> must contain exactly one child element; " "found attribute/str, attribute/int") assertRaisesWithMessage(self, e_expected, XMLContext, stream)
def test_fails_to_identify_bound_function(self): bound_function = dummy.MODULE_MEMBER.method e_expected = AglyphError("%r does not have an importable dotted name" % bound_function) assertRaisesWithMessage(self, e_expected, _identify, bound_function)
def test_context_id_cannot_be_empty(self): stream = bytebuf(self._uresource.encode("utf-8")) e_expected = AglyphError("XMLContext ID must not be None or empty") assertRaisesWithMessage(self, e_expected, XMLContext, stream)