Plain Old Data.
plainbox.impl.pod
¶
This module contains the POD
and Field
classes that simplify
creation of declarative struct-like data holding classes. POD classes get a
useful repr() method, useful initializer and accessors for each of the fields
defined inside. POD classes can be inherited (properly detecting any field
clashes)
Defining POD classes:
>>> class Person(POD):
... name = Field("name of the person", str, MANDATORY)
... age = Field("age of the person", int)
Creating POD instances, positional arguments match field definition order:
>>> joe = Person("joe", age=42)
Full-blown comparison (not only equality):
>>> joe == Person("joe", 42)
True
Reading and writing attributes also works (obviously):
>>> joe.name
'joe'
>>> joe.age
42
>>> joe.age = 24
>>> joe.age
24
For a full description check out the documentation of the POD
and
Field
.
-
class
plainbox.impl.pod.
POD
(*args, **kwargs)[source]¶ Bases:
plainbox.impl.pod.PODBase
Base class that removes boilerplate from plain-old-data classes.
Use POD as your base class and define
Field
objects inside. Don’t define any __init__() (unless you really, really have to have one) and instead set appropriate attributes on the initializer of a particular field object.What you get for free is, all the properties (for each field), documentation, initializer, comparison methods (PODs have total ordering) and the __repr__() method.
There are some additional methods, such as
as_tuple()
andas_dict()
that may be of use in some circumstances.All fields in a single POD subclass are collected (including all of the fields in the parent classes) and arranged in a list. That list is available as
POD.field_list
.In addition each POD class has an unique named tuple that corresponds to each field stored inside the POD, the named tuple is available as
POD.namedtuple_cls
. The return value ofas_tuple()
actually uses that type.-
as_dict
() → dict¶ Return the data in this POD as a dictionary.
Note
UNSET values are not added to the dictionary.
-
as_tuple
() → tuple¶ Return the data in this POD as a tuple.
Order of elements in the tuple corresponds to the order of field declarations.
-
field_list
= []¶
-
-
class
plainbox.impl.pod.
PODBase
(*args, **kwargs)[source]¶ Bases:
object
Base class for POD-like classes.
-
as_dict
() → dict[source]¶ Return the data in this POD as a dictionary.
Note
UNSET values are not added to the dictionary.
-
as_tuple
() → tuple[source]¶ Return the data in this POD as a tuple.
Order of elements in the tuple corresponds to the order of field declarations.
-
field_list
= []¶
-
-
plainbox.impl.pod.
podify
(cls)[source]¶ Decorator for POD classes.
The decorator offers an alternative from using the POD class (with the PODMeta meta-class). Instead of using that, one can use the
@podify
decorator on a PODBase-derived class.
-
class
plainbox.impl.pod.
Field
(doc=None, type=None, initial=None, initial_fn=None, notify=False, notify_fn=None, assign_filter_list=None)[source]¶ Bases:
object
A field in a plain-old-data class.
Each field declares one attribute that can be read and written to. Just like a C structure. Attributes are readable _and_ writable but there is a lot of flexibility in what happens.
Attr name: Name of the field (this is how this field can be accessed on the class or instance that contains it). This gets set by _FieldCollection.inspect_namespace()
Attr instance_attr: Name of the POD dictionary entry used as backing store. This is set the same as name
above. By default that’s just name prepended with the'_'
character.Attr type: An optional type hit. This is not used by default but assign filters can inspect and use this for type checking. It can also be used for documenting the intent of the field. Attr __doc__: The docstring of the field, as initialized by the caller. Attr initial: Initial value of this field, can be changed by passing arguments to POD.__init__()
. May be set toMANDATORY
for a special meaning (see below).Attr initial_fn: If not None this is a callable that produces the initial
value for each new POD object.Attr notify: If True, a on_{name}_changed A flag controlling if notification events are sent for each modification of POD data through field. Attr notify_fn: An (optional) function to use as the first responder to the change notification signal. This field is only used if the notify
attribute is set toTrue
.Attr assign_filter_list: An (optional) list of assignment filter functions. A field is initialized based on the arguments passed to the POD initializer. If no argument is passed that would correspond to a given field the initial value is used. The initial value is either a constant (reference) stored in the
initial
property of the field or the return value of the callable ininitial_fn
. Please make sure to useinitial_fn
if the value is not immutable as otherwise the produced value may be unintentionally shared by multiple objects.If the
initial
value is the special constantMANDATORY
then the corresponding field must be explicitly initialized by the POD initializer argument list or a TypeError is raised.The
notify
flag controls the existence of theon_{name}_changed(old, new)
signal on the class that includes the field. Applications can connect to that signal to observe changes. The signal is fired whenever the newly-assigned value compares unequal to the value currently stored in the POD.The
notify_fn
is an optional function that is used instead of the default (internal)on_changed()
method of the Field class itself. If specified it must have the same three-argument signature. It will be called whenever the value of the field changes. Note that it will also be called on the initial assignment, when theold
argument it receives will be set to the specialUNSET
object.Lastly a docstring and type hint can be provided for documentation. The type check is not enforced.
Assignment filters are used to inspect and optionally modify a value during assignment (including the assignment done on object initialization) and can be used for various operations (including type conversions and validation). Assignment filters are called whenever a field is used to write to a POD.
Since assignment filters are arranged in a list and executed in-order, they can also be used to modify the value as it gets propagated through the list of filters.
The signature of each filter is
fn(pod, field, old_value, new_value)
. The return value is the value shown to the subsequent filter or finally assigned to the POD.-
alter_cls
(cls: type) → None[source]¶ Modify class definition this field belongs to.
This method is called during class construction. It allows the field to alter the class and add the on_{field.name}_changed signal. The signal is only added if notification is enabled and if there is no such signal in the first place (this allows inheritance not to create separate but identically-named signals and allows signal handlers connected via the base class to work on child classes.
-
change_notifier
¶ Decorator for changing the change notification function.
This decorator can be used to define all the fields in one block and all the notification function in another block. It helps to make the code easier to read.
Example:
>>> class Person(POD): ... name = Field() ... ... @name.change_notifier ... def _name_changed(self, old, new): ... print("changed from {!r} to {!r}".format(old, new)) >>> person = Person() changed from UNSET to None >>> person.name = "bob" changed from None to 'bob'
Note
Keep in mind that the decorated function is converted to a signal automatically. The name of the function is also irrelevant, the POD core automatically creates signals that have consistent names of
on_{field}_changed()
.
-
gain_name
(name: str) → None[source]¶ Set field name.
Parameters: name – Name of the field as it appears in a class definition Method called at most once on each Field instance embedded in a
POD
subclass. This method informs the field of the name it was assigned to in the class.
-
is_mandatory
¶ Flag indicating if the field needs a mandatory initializer.
-
-
plainbox.impl.pod.
read_only_assign_filter
(instance: plainbox.impl.pod.POD, field: plainbox.impl.pod.Field, old: 'Any', new: 'Any') → 'Any'[source]¶ An assign filter that makes a field read-only.
The field can be only assigned if the old value is
UNSET
, that is, during the initial construction of a POD object.Parameters: Returns: new
, as-isRaises: AttributeError – if
old
is anything but the special objectUNSET
-
plainbox.impl.pod.
type_convert_assign_filter
(instance: plainbox.impl.pod.POD, field: plainbox.impl.pod.Field, old: 'Any', new: 'Any') → 'Any'[source]¶ An assign filter that converts the value to the field type.
The field must have a valid python type object stored in the .type field.
Parameters: Returns: new
type-converted tofield.type
.Raises: ValueError – if
new
cannot be converted tofield.type
-
plainbox.impl.pod.
type_check_assign_filter
(instance: plainbox.impl.pod.POD, field: plainbox.impl.pod.Field, old: 'Any', new: 'Any') → 'Any'[source]¶ An assign filter that type-checks the value according to the field type.
The field must have a valid python type object stored in the .type field.
Parameters: Returns: new
, as-isRaises: TypeError – if
new
is not an instance offield.type
-
plainbox.impl.pod.
modify_field_docstring
(field_docstring_ext: str)[source]¶ Decorator for altering field docstrings via assign filter functions.
A decorator for assign filter functions that allows them to declaratively modify the docstring of the field they are used on.
Parameters: field_docstring_ext – A string compatible with python’s str.format() method. The string should be one line long (newlines will look odd) and may reference any of the field attributes, as exposed by the {field} named format attribute. Example:
>>> @modify_field_docstring("not even") ... def not_even(instance, field, old, new): ... if new % 2 == 0: ... raise ValueError("value cannot be even") ... return new