1
votes

I need a Property type that can set and get True, False and None values to NDB using the Python interface. For single Property attributes, this is fairly easy to do.

For repeated Property attributes (repeated=True), however, it seems impossible to set a list item to None. Whichever approach I've tried, the NDB library raises exceptions.

One attempt looks like this:

class LOGICAL(ndb.GenericProperty):

  def _set_value(self, entity, value):
    if isinstance(value, (list, tuple, set, frozenset)):
        for i, val in enumerate(value):
            if isinstance(val, str):
                if val.lower() in ('unknown', 'none'):
                    value[i] = None
    if isinstance(value, str):
        if value.lower() in ('unknown', 'none'):
            value = None
    ndb.Property._set_value(self, entity, value)

  def _validate(self, value):
    if isinstance(value, str):
        if value.lower() == 'true':
            return True
        if value.lower() == 'false':
            return False
        raise AssertionError('LOGICAL must be one of "true","false","unknown","none" or'
                             'True, False, None.')
    if value is not None:
        assert isinstance(value, bool)

This code runs fine for single LOGICAL properties, but as soon as I want to assign a list such as [True,'false',None], None will be rejected. I get the following warnings from the 'machine' in the background and a long, long stack trace.

WARNING:root:initial generator _put_tasklet(context.py:270) raised AssertionError()
WARNING:root:suspended generator put(context.py:748) raised AssertionError()

Please let me know if my task is an impossible one, or which approach to take.

1
You need to look at the full stacktrace, the bit you supplied isn't useful. We can then look at the lower levels of code that is raising the error. My gut feeling is you just can't do it, but need to see the specific code in ndb or lower down that is causing the error. - Tim Hoffman
I think I solved this one, but its not pretty. In my class LOGICAL, which is a subclass of ndb.Property, I overloaded both Property._set_value and Property._apply_to_values to simply replace any string 'unknown' that comes up from NDBs _from_base_value methods with None. - Arne Wolframm

1 Answers

0
votes

_validate does not correctly handle the cases for "unknown" and "none":

        if value.lower() in ('unknown', 'none'):
            return None

Then, at the very end of the function:

    return value

So the resulting function is:

def _validate(self, value):
    if isinstance(value, str):
        if value.lower() == 'true':
            return True
        if value.lower() == 'false':
            return False
        if value.lower() in ('unknown', 'none'):    # Add these two lines
            return None
        raise AssertionError('LOGICAL must be one of "true","false","unknown","none" or'
                             'True, False, None.')
    if value is not None:
        assert isinstance(value, bool)
    return value