Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace factory functions with classes. #405

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/lxml/etree.pyx
Expand Up @@ -59,6 +59,8 @@ from libc cimport limits, stdio, stdlib
from libc cimport string as cstring_h # not to be confused with stdlib 'string'
from libc.string cimport const_char

from abc import ABC

cdef object os_path_abspath
from os.path import abspath as os_path_abspath

Expand Down Expand Up @@ -3058,18 +3060,31 @@ cdef xmlNode* _createEntity(xmlDoc* c_doc, const_xmlChar* name) noexcept:

# module-level API for ElementTree

def Element(_tag, attrib=None, nsmap=None, **_extra):

class Element(ABC):
"""Element(_tag, attrib=None, nsmap=None, **_extra)

Element factory. This function returns an object implementing the
Element class. An instance of this class is an object implementing the
Element interface.

>>> element = Element("test")
>>> type(element)
<class 'lxml.etree._Element'>
>>> isinstance(element, Element)
True
>>> issubclass(_Element, Element)
True

Also look at the `_Element.makeelement()` and
`_BaseParser.makeelement()` methods, which provide a faster way to
create an Element within a specific document or parser context.
"""
return _makeElement(_tag, NULL, None, None, None, None,
attrib, nsmap, _extra)
def __new__(cls, _tag, attrib=None, nsmap=None, **_extra):
return _makeElement(_tag, NULL, None, None, None, None,
attrib, nsmap, _extra)

# Register _Element as a virtual subclass of Element
Element.register(_Element)


def Comment(text=None):
Expand Down Expand Up @@ -3185,7 +3200,8 @@ def SubElement(_Element _parent not None, _tag,
return _makeSubElement(_parent, _tag, None, None, attrib, nsmap, _extra)


def ElementTree(_Element element=None, *, file=None, _BaseParser parser=None):
class ElementTree(ABC):
def __new__(cls, _Element element=None, *, file=None, _BaseParser parser=None):
"""ElementTree(element=None, file=None, parser=None)

ElementTree wrapper class.
Expand All @@ -3210,6 +3226,9 @@ def ElementTree(_Element element=None, *, file=None, _BaseParser parser=None):

return _elementTreeFactory(doc, element)

# Register _ElementTree as a virtual subclass of ElementTree
ElementTree.register(_ElementTree)


def HTML(text, _BaseParser parser=None, *, base_url=None):
"""HTML(text, parser=None, base_url=None)
Expand Down
74 changes: 74 additions & 0 deletions src/lxml/tests/test_api.py
@@ -0,0 +1,74 @@
"""
Test module level API for etree.
"""


import unittest
import sys
from io import BytesIO

from .common_imports import etree
from .common_imports import HelperTestCase
from lxml import builder, sax


class APITestCase(HelperTestCase):
"""Test cases of the etree module API.
"""

def test_class_hierarchy(self):

element = etree.Element("test")
# The Element class constructs an _Element instance
self.assertIs(type(element), etree._Element)
# _Element is a subclass implementation of Element
self.assertTrue(issubclass(etree._Element, etree.Element))
# Therefore, element is an instance of Element
self.assertIsInstance(element, etree.Element)

comment = etree.Comment("text")
self.assertIs(type(comment), etree._Comment)
self.assertIsInstance(comment, etree._Element)
self.assertIsInstance(comment, etree.Element)

pi = etree.ProcessingInstruction("target", "text")
self.assertIs(type(pi), etree._ProcessingInstruction)
self.assertIsInstance(pi, etree._Element)
self.assertIsInstance(pi, etree.Element)

entity = etree.Entity("text")
self.assertIs(type(entity), etree._Entity)
self.assertIsInstance(entity, etree._Element)
self.assertIsInstance(entity, etree.Element)

sub_element = etree.SubElement(element, "child")
self.assertIs(type(sub_element), etree._Element)
self.assertIsInstance(sub_element, etree.Element)

tree = etree.ElementTree(element)
self.assertIs(type(tree), etree._ElementTree)
self.assertIsInstance(tree, etree.ElementTree)
self.assertNotIsInstance(tree, etree._Element)

# XML is a factory function and not a class.
xml = etree.XML("<root><test/></root>")
self.assertIs(type(xml), etree._Element)
self.assertIsInstance(xml, etree._Element)
self.assertIsInstance(xml, etree.Element)

self.assertNotIsInstance(element, etree.ElementBase)
self.assertIs(type(element), etree._Element)
self.assertTrue(issubclass(etree.ElementBase, etree._Element))

self.assertTrue(callable(etree.Element))
self.assertTrue(callable(etree.ElementTree))


def test_suite():
suite = unittest.TestSuite()
suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(APITestCase)])
return suite


if __name__ == '__main__':
print('to test use test.py %s' % __file__)