Thursday, March 24, 2016

super() in the __str__() of a python_2_unicode_compatible

Here is now how to reproduce #844.

The following code snippet was used to reproduce #844:

>>> from lino import startup
>>> startup('lino_welfare.projects.std.settings.doctests')
>>> from lino.api.doctest import *
>>> obj = households.Member()
>>> print(obj)
Member object

Explanation starts in lino_xl.lib.households.models which defines:

@python_2_unicode_compatible
class Member(mixins.DatePeriod):
    def __str__(self):
        if self.person_id is None:
            return super(Member, self).__str__()
        if self.role is None:
            return unicode(self.person)
        return u"%s (%s)" % (self.person, self.role)

This code, on its own, is not problematic. The problem comes only when Lino Welfare extends the Member model. In lino_welfare.modlib.households.models it says:

class Member(Member, mixins.Human, mixins.Born):
    ...

And in lino.mixins.human we have:

from lino_xl.lib.households.models import *

@python_2_unicode_compatible
class Human(model.Model):

    def __str__(self):
        return self.get_full_name(nominative=True)

The rule of thumb is: Don’t use :func:`super` in the :meth:`__str__` method of a `python_2_unicode_compatible` model.

My explanation is that python_2_unicode_compatible causes something to get messed up with the mro for the __str__() method, but I wont’t dive deeper into this right now because my problem was fixed by changing the relevant line:

return super(Member, self).__str__()

into an explicit copy of the code which I want to run there (defined in the super() of Django’s Model class):

return str('%s object' % self.__class__.__name__)

I added a section about “Member objects” in welfare.specs.households to cover it.

Moving from fabric to invoke

I wanted to get the Lino test suite pass on Travis CI. It took me almost the whole day. Lots of subtle changes in atelier.

The main problem was to understand how invoke does configuration and to decide how to store current_project and how to define configration variables. I added an invoke.yaml to most projects.

Many more inv commands now work.

fab bd was the easiest: it simply wasn’t yet imported into the atelier.tasks module.

There was a bug in atelier.utils.confirm() which made it fail under Python 2 when the prompt was a unicode string with non-ascii characters.

# -*- coding: UTF-8 -*-

from __future__ import unicode_literals
from atelier.utils import confirm

confirm("Ein schöner Tag?")

Above script gives:

Traceback (most recent call last):
  File "0324.py", line 4, in <module>
    confirm("Ein schöner Tag?")
  File "/atelier/atelier/utils.py", line 213, in confirm
    ln = input(prompt)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 7: ordinal not in range(128)

A simplified version:

# -*- coding: UTF-8 -*-

from __future__ import unicode_literals
from atelier.utils import confirm

confirm("Ein schöner Tag?")

But finally I can not do:

$ pp inv clean
$ pp inv prep test bd pd

I released Atelier 0.0.20 to PyPI (needed because Travis CI uses the released version)

And the reward: the Lino test suite now passes on Travis CI using invoke instead of fabric! At least for Python 2.