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() and as_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 of as_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 = []
namedtuple_cls

alias of POD

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 = []
namedtuple_cls

alias of PODBase

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 to MANDATORY 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 to True.
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 in initial_fn. Please make sure to use initial_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 constant MANDATORY 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 the on_{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 the old argument it receives will be set to the special UNSET 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.

on_changed()[source]

The first responder of the per-field modification signal.

Parameters:
  • pod – The object that contains the modified values
  • old – The old value of the field
  • new – The new value of the field
plainbox.impl.pod.read_only_assign_filter()[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:
  • instance – A subclass of POD that contains field
  • field – The Field being assigned to
  • old – The current value of the field
  • new – The proposed value of the field
Returns:

new, as-is

Raises:

AttributeError – if old is anything but the special object UNSET

plainbox.impl.pod.type_convert_assign_filter()[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:
  • instance – A subclass of POD that contains field
  • field – The Field being assigned to
  • old – The current value of the field
  • new – The proposed value of the field
Returns:

new type-converted to field.type.

Raises:

ValueError – if new cannot be converted to field.type

plainbox.impl.pod.type_check_assign_filter()[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:
  • instance – A subclass of POD that contains field
  • field – The Field being assigned to
  • old – The current value of the field
  • new – The proposed value of the field
Returns:

new, as-is

Raises:

TypeError – if new is not an instance of field.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
comments powered by Disqus