:date: 2018-03-08 ======================= Thursday, March 8, 2018 ======================= Today I worked for :ticket:`2330` (which is part of our porting to Python 3 project). It all started with this error message `TypeError: append() argument must be xml.etree.ElementTree.Element, not lxml.etree._Element `__. Until now we have been working with two different implementations of ElementTree: my self-made implementation based on :class:`etgen.utils.Namespace`, and the lxml implementation. In Python 3 this now causes above problem, probably because there is some type checking somewhere. I think the best solution is to completely move over to the lxml implementation. Though not the easy way. It has a few consequences. We cannot use `E.tostring()` any more because the ``E`` defined in :mod:`lxml.etree` doesn't have that method. My extended version of :func:`tostring` is now as a global function in :mod:`etgen.html`. Old code:: from etgen.html import E ... E.tostring() New code:: from etgen.html import E, tostring ... tostring() Same problem for `E.iselement` and `E.to_rst` and `E.raw`. Note that there was no direct error message because expressions `E.tostring`, `E.iselement` and `E.to_rst` are valid: they return an element with that tag. Which is of course not at all our expected behaviour. One possible consequence was:: FutureWarning: The behavior of this method will change in future versions. Use specific 'len(elem)' or 'elem is not None' test instead. elif isinstance(text, six.string_types) or E.iselement(text): In lxml we don't have the hack of adding an underscore to attributes like `class` which are a reserved in Python. We must convert these cases. Before:: return E.li(a, class_="active") After:: return E.li(a, **{'class': "active"}) There is also the trick described in the lxml tutorial:: def CLASS(*args): # class is a reserved word in Python return {"class":' '.join(args)} But I don't like it because it adds unnecessary complexity. There were lots of failures saying `TypeError: bad argument type: __proxy__(u' by ')`. These are because lxml elements don't like Django translatable strings. Old code:: return E.div(E.h2(self.actor.label), e) New code:: return E.div(E.h2(str(self.actor.label)), e) I tried to add the :func:`str` in the core, i.e. to have as least impact as possible to application code. Another failure was :message:`TypeError: update() takes no keyword arguments`. Old code:: e.attrib.update(align='right') New code:: e.set('align', 'right') I also changed the message: Your printable document (filename sales.VatProductInvoice-135.pdf) should now open in a new browser window. If it doesn't, please consult the documentation or ask your system administrator. to: Your printable document (sales.VatProductInvoice-135.pdf) should now open in a new browser window. If it doesn't, please ask your system administrator. Because the ``a`` tag with two attributes (href and target) caused doctest failures in Python 3 where the order of these attribues is arbitrary.