自定义元素 class 查找未加载自定义元素

Custom element class lookup not loading custom element

我正在对某些 lxml 自定义元素(例如 ParentElementChildElement)实施测试,这些元素是通过自定义查找 class 中的装饰器注册的(ModelLookup).

pytest 用于 运行 测试,我正在使用 conftest.py 中定义的固定装置。

问题是,当自定义元素的对象作为夹具的一部分创建时,测试函数中的输入丢失,我收到以下错误:

>   assert ['eclios', 'ruby'] == sorted([e.name for e in simple_tidy_family.child_element])
E   AttributeError: 'lxml.etree._Element' object has no attribute 'name'

另一方面,如果对象是作为测试函数的一部分创建的,那么所有 运行 都可以。

理想情况下,我想在其他测试中重新使用这些装置,因此首先在 conftest 中定义它。

模块代码:

from __future__ import unicode_literals
from lxml import etree


class ModelLookup(etree.PythonElementClassLookup):

    _lookup = {}

    @classmethod
    def register_node_handler_class(cls, handler_cls):
        if handler_cls.cls_tag not in cls._lookup.keys():
            cls._lookup[handler_cls.cls_tag] = handler_cls
        return handler_cls

    def lookup(self, doc, node):
        if node.tag in self._lookup.keys():
            print(node.tag)
            return self._lookup[node.tag]
        return etree.ElementBase


@ModelLookup.register_node_handler_class
class ParentElement(etree.ElementBase):

    cls_tag = 'ParentElement'

    @staticmethod
    def tada():
        return 'tada'

    @property
    def child_element(self):
        return self.xpath('./ChildElement')

    @child_element.setter
    def child_element(self, value):
        self.append(value)

    @property
    def name(self):
        return self.get('name')

    @name.setter
    def name(self, value):
        self.set('name', value)


@ModelLookup.register_node_handler_class
class ChildElement(etree.ElementBase):

    cls_tag = 'ChildElement'

    @property
    def name(self):
        return self.get('name')

    @name.setter
    def name(self, value):
        self.set('name', value)

conftest.py

from __future__ import unicode_literals
import pytest
import xmlpal.xmlpal as xpal
import logging


@pytest.fixture()
def simple_family():

    ruby = xpal.ChildElement(**{'name': 'ruby'})
    eclios = xpal.ChildElement(**{'name': 'eclios'})

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    logging.info(f'my name is {ruby.name}')

    return adam

测试码:

from __future__ import unicode_literals
from lxml import etree
import xmlpal.xmlpal as xpal


def test_fixture_family(simple_family):

    assert 'tada' == simple_family.tada()
    assert isinstance(simple_family, xpal.ParentElement)
    assert 2 == len(simple_family.child_element)
    assert 2 == len([e for e in simple_family.child_element if isinstance(e, etree._Element)])
    assert ['eclios', 'ruby'] == sorted([e.name for e in simple_family.child_element])
    assert 2 == len([e for e in simple_family.child_element if isinstance(e, xpal.ChildElement)])


def test_local_family():

    ruby = xpal.ChildElement(**{'name': 'ruby'})
    eclios = xpal.ChildElement(**{'name': 'eclios'})

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    assert 'tada' == adam.tada()
    assert isinstance(adam, xpal.ParentElement)
    assert 2 == len(adam.child_element)
    assert 2 == len([e for e in adam.child_element if isinstance(e, etree._Element)])
    assert ['eclios', 'ruby'] == sorted([e.name for e in adam.child_element])
    assert 2 == len([e for e in adam.child_element if isinstance(e, xpal.ChildElement)])

感谢任何帮助!

这不是 pytest fixtures 的问题。当 simple_family 是一个普通函数时,也会发生同样的错误。

哇,看起来你有内存管理问题:O。如果您 return 来自 simple_family return adam, eclios, ruby 的所有对象或使用 yield 一切正常。如果你不使用 yield:

ruby is not simple_family.child_element[0]

看起来库中有一些严重的错误。

编辑:如果 ruby 和 eclios 是全局变量,它也可以正常工作:D

我找到了一个有效的解决方案,但不太明白它为什么有效。

如果我用夹具中的 yield 替换 return,一切都会按预期工作。

@pytest.fixture()
def simple_tidy_family():

    ruby = xpal.ChildElement()
    ruby.name = 'ruby'
    eclios = xpal.ChildElement()
    eclios.name = 'eclios'

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    yield adam

您不能使用那样的自定义查找。仅当从源解析 XML 文档树时才应用查找。因此,您根本不会在测试中调用自定义查找,也不会注册您的自定义元素类型。您遇到的其余问题只是两个函数调用之间的垃圾收集(因为您没有退出 fixture 函数的范围,所以产生有效)。但是,当在解析文档时应用查找时,一切都按预期工作:

@pytest.fixture
def source():
    """Construct the tree and serialize it to string."""
    ruby = xpal.ChildElement(**{'name': 'ruby'})
    eclios = xpal.ChildElement(**{'name': 'eclios'})

    adam = xpal.ParentElement()
    adam.append(ruby)
    adam.append(eclios)

    return etree.tostring(adam)


@pytest.fixture
def tree(source):
    """Parse the tree from a string using a parser with custom lookup registered."""
    parser = etree.XMLParser()
    parser.set_element_class_lookup(xpal.ModelLookup())
    return etree.fromstring(source, parser)


def test_fixture_family(tree):
    adam = tree
    assert 'tada' == adam.tada()
    ...