summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxmo-odoo <xmo@odoo.com>2022-05-17 10:22:31 +0200
committerGitHub <noreply@github.com>2022-05-17 10:22:31 +0200
commita90d0ee11685fef61e61c2de01a417a0e26eba50 (patch)
tree3d4e2d56c9ec1b8d636d8d9054d4e1c3ef249d73
parent58c10b06e5239a68a1a0c7cb311402581b4e20d1 (diff)
downloadpython-lxml-a90d0ee11685fef61e61c2de01a417a0e26eba50.tar.gz
Fix inheritance order of mixin classes in lxml.html (GH-340)
As the old FIXME comment from https://github.com/lxml/lxml/commit/8132c755adad4a75ba855d985dd257493bccc7fd notes, the mixin should come first for the inheritance to be correct (the left-most class is the first in the MRO, at least if no diamond inheritance is involved). Also fix the odd `super` call in `HtmlMixin`, likely stemming from the incorrect MRO. Fixes the inheritance order of all `HTML*` base classes though it probably doesn't matter for other than `HtmlElement`.
-rw-r--r--src/lxml/html/__init__.py14
-rw-r--r--src/lxml/html/tests/test_basic.py44
-rw-r--r--tox.ini1
3 files changed, 49 insertions, 10 deletions
diff --git a/src/lxml/html/__init__.py b/src/lxml/html/__init__.py
index 2139c75a..ef06a40b 100644
--- a/src/lxml/html/__init__.py
+++ b/src/lxml/html/__init__.py
@@ -245,7 +245,7 @@ class HtmlMixin(object):
creates a 'boolean' attribute without value, e.g. "<form novalidate></form>"
for ``form.set('novalidate')``.
"""
- super(HtmlElement, self).set(key, value)
+ super(HtmlMixin, self).set(key, value)
@property
def classes(self):
@@ -685,21 +685,19 @@ iterlinks = _MethodFunc('iterlinks', copy=False)
rewrite_links = _MethodFunc('rewrite_links', copy=True)
-class HtmlComment(etree.CommentBase, HtmlMixin):
+class HtmlComment(HtmlMixin, etree.CommentBase):
pass
-class HtmlElement(etree.ElementBase, HtmlMixin):
- # Override etree.ElementBase.cssselect() and set(), despite the MRO (FIXME: change base order?)
- cssselect = HtmlMixin.cssselect
- set = HtmlMixin.set
+class HtmlElement(HtmlMixin, etree.ElementBase):
+ pass
-class HtmlProcessingInstruction(etree.PIBase, HtmlMixin):
+class HtmlProcessingInstruction(HtmlMixin, etree.PIBase):
pass
-class HtmlEntity(etree.EntityBase, HtmlMixin):
+class HtmlEntity(HtmlMixin, etree.EntityBase):
pass
diff --git a/src/lxml/html/tests/test_basic.py b/src/lxml/html/tests/test_basic.py
index 6e35c274..464d4747 100644
--- a/src/lxml/html/tests/test_basic.py
+++ b/src/lxml/html/tests/test_basic.py
@@ -1,11 +1,51 @@
+import sys
import unittest
from lxml.tests.common_imports import make_doctest, doctest
-import lxml.html
+from lxml import html
+
+class TestBasicFeatures(unittest.TestCase):
+ def test_various_mixins(self):
+ base_url = "http://example.org"
+ doc = html.fromstring("""
+ <root>
+ <!-- comment -->
+ <?pi contents ?>
+ &entity;
+ <el/>
+ </root>
+ """, base_url=base_url)
+ self.assertEqual(doc.getroottree().docinfo.URL, base_url)
+ self.assertEqual(len(doc), 3)
+ self.assertIsInstance(doc[0], html.HtmlComment)
+ self.assertIsInstance(doc[1], html.HtmlProcessingInstruction)
+ self.assertIsInstance(doc[2], html.HtmlElement)
+ for child in doc:
+ # base_url makes sense on all nodes (kinda) whereas `classes` or
+ # `get_rel_links` not really
+ self.assertEqual(child.base_url, base_url)
+
+ def test_set_empty_attribute(self):
+ e = html.Element('e')
+ e.set('a')
+ e.set('b', None)
+ e.set('c', '')
+ self.assertEqual(
+ html.tostring(e),
+ b'<e a b c=""></e>',
+ "Attributes set to `None` should yield empty attributes"
+ )
+ self.assertEqual(e.get('a'), '', "getting the empty attribute results in an empty string")
+ self.assertEqual(e.attrib, {
+ 'a': '',
+ 'b': '',
+ 'c': '',
+ })
def test_suite():
suite = unittest.TestSuite()
suite.addTests([make_doctest('test_basic.txt')])
- suite.addTests([doctest.DocTestSuite(lxml.html)])
+ suite.addTests([doctest.DocTestSuite(html)])
+ suite.addTest(unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]))
return suite
if __name__ == '__main__':
diff --git a/tox.ini b/tox.ini
index 3906b1de..063a6804 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,6 +7,7 @@
envlist = py27, py35, py36, py37, py38, py39, py310
[testenv]
+allowlist_externals = make
setenv =
CFLAGS = -g -O0
commands =