Property class is designed to be subclassed.
However, it is normally easier to subclass an existing
even those considered 'public',
have names starting with an underscore.
This is because
uses the non-underscore attribute namespace to refer to nested
Property names; this is essential for specifying queries on
Property class and its predefined subclasses allow
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.
Property subclass that implements a specific
transformation between user values and serializable values should
implement two methods,
These should not call their
This is what is meant by composable (or stackable) APIs.
The API supports stacking classes with ever more sophisticated
user-base conversions: the user-to-base conversion
goes from more sophisticated to less sophisticated, while the
base-to-user conversion goes from less sophisticated to more
sophisticated. For example, see the relationship between
TextProperty inherits from
BlobProperty; its code is pretty simple because it
inherits most of the behavior it needs.
In addition to
_validate() method is also a composable API.
The validation API distinguishes between lax and
values. The set of lax values is a superset of the set of strict
_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
_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 takes care of calling
_to_base_type()for each list item in a repeated value.
- Distinguishing user values from base values: The infrastructure handles this by calling 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.
For example, suppose you need to store really long integers.
IntegerProperty only supports (signed)
Your property might store a longer integer as a string; it would be
good to have the property class handle the conversion.
An application using your property class might look something like
This looks simple and straightforward. It also demonstrates the
use of some standard property options (default, repeated); as
the author of
LongIntegerProperty, you will be glad
to hear you don't have to write any "boilerplate" to get those
working. It's easier to define a subclass of another property, for
When you set a property value on an entity, e.g.
ent.abc = 42, your
method is called, and (if it doesn't raise an exception) the value
is stored on the entity. When you write the entity to the Datastore,
_to_base_type() method is called, converting the
value to the string. Then that value is serialized by the base class,
The inverse chain of events happens when the entity is read back from
the Datastore. The
classes together take care of the other details, such as serializing
the and deserializing the string, setting the default, and handling
repeated property values.
In this example, supporting inequalities (i.e. queries using <, <=, >, >=) requires more work. The following example implementation imposes a maximum size of integer and stores values as fixed-length strings:
This can be used in the same way as
except that you must pass the number of bits to the property constructor,
You can subclass other property types in similar ways.
This approach also works for storing structured data.
Suppose you have a
FuzzyDate Python class that represents a
date range; it uses fields
to store the date range's beginning and end:
You can create a
FuzzyDateProperty that derives from
StructuredProperty. Unfortunately, the latter doesn't
work with plain old Python classes; it needs a
So define a Model subclass as an intermediate representation;
Next, construct a subclass of
that hardcodes the modelclass argument to be
methods to convert between
An application might use this class like so:
Suppose you want to accept plain
date objects in
FuzzyDate objects as the values for
FuzzyDateProperty. To do this, modify the
method as follows:
You could instead subclass
FuzzyDateProperty as follows
is as shown above).
When you assign a value to a
FuzzyDateProperty._validate() are invoked, in that order.
The same applies to
_from_base_type(): the methods in in
superclass and subclass are implicitly combined.
super to control inherited behavior for this.
For these three methods,
the interaction is subtle and
super doesn't do what you want.)