A class describing a typed, persisted attribute of a datastore entity.
Not to be confused with Python's 'property' built-in.
This is just a base class; there are specific subclasses that
describe Properties of various types (and GenericProperty which
describes a dynamically typed Property).
All special Property attributes, even those considered 'public',
have names starting with an underscore, because StructuredProperty
uses the non-underscore attribute namespace to refer to nested
Property names; this is essential for specifying queries on
subproperties (see the module docstring).
The Property class and its predefined subclasses allow easy
subclassing using composable (or stackable) validation and
conversion APIs. These require some terminology definitions:
- A 'user value' is a value such as would be set and accessed by the
application code using standard attributes on the entity.
- A 'base value' is a value such as would be serialized to
and deserialized from the datastore.
The values stored in ent._values[name] and accessed by
_store_value() and _retrieve_value() can be either user values or
base values. To retrieve user values, use
_get_user_value(). To retrieve base values, use
_get_base_value(). In particular, _get_value() calls
_get_user_value(), and _serialize() effectively calls
_get_base_value().
To store a user value, just call _store_value(). To store a
base value, wrap the value in a _BaseValue() and then
call _store_value().
A Property subclass that wants to implement a specific
transformation between user values and serialiazble values should
implement two methods, _to_base_type() and _from_base_type().
These should *NOT* call their super() method; super calls are taken
care of by _call_to_base_type() and _call_from_base_type().
This is what is meant by composable (or stackable) APIs.
The API supports 'stacking' classes with ever more sophisticated
user<-->base conversions: the user-->base conversion
goes from more sophisticated to less sophisticated, while the
base-->user conversion goes from less sophisticated to more
sophisticated. For example, see the relationship between
BlobProperty, TextProperty and StringProperty.
In addition to _to_base_type() and _from_base_type(), the
_validate() method is also a composable API.
The validation API distinguishes between 'lax' and 'strict' user
values. The set of lax values is a superset of the set of strict
values. The _validate() method takes a lax value and if necessary
converts it to a strict value. This means that when setting the
property value, lax values are accepted, while when getting the
property value, only strict values will be returned. If no
conversion is needed, _validate() may return None. If the argument
is outside the set of accepted lax values, _validate() should raise
an exception, preferably TypeError or
datastore_errors.BadValueError.
Example/boilerplate:
def _validate(self, value):
'Lax user value to strict user value.'
if not isinstance(value, <top type>):
raise TypeError(...) # Or datastore_errors.BadValueError(...).
def _to_base_type(self, value):
'(Strict) user value to base value.'
if isinstance(value, <user type>):
return <base type>(value)
def _from_base_type(self, value):
'base value to (strict) user value.'
if not isinstance(value, <base type>):
return <user type>(value)
Things that _validate(), _to_base_type() and _from_base_type()
do *not* need to handle:
- None: They will not be called with None (and if they return None,
this means that the value does not need conversion).
- Repeated values: The infrastructure (_get_user_value() and
_get_base_value()) takes care of calling
_from_base_type() or _to_base_type() for each list item in a
repeated value.
- Wrapping values in _BaseValue(): The wrapping and unwrapping is
taken care of by the infrastructure that calls the composable APIs.
- Comparisons: The comparison operations call _to_base_type() on
their operand.
- Distinguishing between user and base values: the
infrastructure guarantees that _from_base_type() will be called
with an (unwrapped) base value, and that
_to_base_type() will be called with a user value.
- Returning the original value: if any of these return None, the
original value is kept. (Returning a differen value not equal to
None will substitute the different value.)