Developer reference¶
The structure NineML Python library aims to closely match the
NineML specification, with each NineML “layer” represented by a
sub-package (i.e. nineml.abstraction
and nineml.user
) and each NineML
type mapping to a separate Python class, with the exception of some
simple types that just contain a single element (e.g. Size) or are used just to
provide a name to a singleton child class (e.g. Pre, Post, etc…).
Base classes¶
There are number of base classes that should be derived from when designing NineML classes, which one(s) depend on the structure of the type, e.g. whether the contain annotations, child elements, or can be placed at the top-level of a NineML document.
BaseNineMLObject¶
All classes that represent objects in the “NineML object model” should derive
from BaseNineMLObject
.
BaseNineMLObject
defines a number of common methods such as clone
,
equals
, write
, etc… (see NineML Types). As well as default
values for class attributes that are required for all NineML classes,
nineml_type
, nineml_attr
, nineml_child
, nineml_children
.
These class attributes match the structure of the NineML specification and
are used extensively within the visitor architecture (including
serialization).
nineml_type¶
nineml_type
should be a string containing the name of the
corresponding NineML type in the NineML specification.
nineml_type_v1¶
If the nineml_type differs between v1 and v2 of the specification,
nineml_type_v1
should also be defined to hold the name of the type
in the v1 syntax.
nineml_attr¶
nineml_attr
should be a tuple of strings, listing the
attributes of the given NineML class that are part of the
NineML specification and are not NineML types themselves, such as str
,
int
and float
fields.
nineml_child¶
nineml_child
should be a dictionary, which lists the names of singleton
NineML child attributes in the class along with a mapping to their
expected class. If the the child attribute can be one of several NineML
classes then the attribute should map to None.
nineml_children¶
nineml_children
should be a tuple listing the NineML classes that
are contained within the object as children sets (e.g. (Property, Initial)
for the DynamicsProperties
class). Note that if a class has
non-empty nineml_children
it should derive from ContainerObject
.
temporary¶
“Temporary” NineML objects are created when calling iterator properties and
accessor methods of the MultiDynamics
class that override corresponding in
the Dynamics
class, allowing MultiDynamics
objects to duck-type (i.e.
pretend to be) Dynamics
objects. Such classes should override the
temporary
class attribute and set it to True
. This prevents their
address in memory being used to identify the object (e.g. in the cloner “memo”
dictionary) as it since they are generated on the fly, this address will change
between accesses.
Note
The id
property in BaseNineMLObject should always be used to check
whether two Python objects are the representing the same NineML object for
this reason.
AnnotatedObject¶
The NineML specification states that all NineML objects can be annotated
except Annotations
objects themselves. Therefore, all bar Annotations
NineML classes should derive from AnnotatedObject
, which itself derives
from BaseNineMLObject
. This provides the annotations
attribute, which
can provides access to any annotations associated with the object.
ContainerObject¶
“Container classes” are classes that contain sets of children, such as
Dynamics` (contains parameters, regimes, state-variables) or
OnCondition (contains state assignments and output events), as opposed
to classes that have nested singleton objects such as Dimension objects
in Parameter objects. Such classes should derive from ContainerObject
.
ContainerObject
adds a number of convenient methods, including add
,
remove
, and general iterators used to traverse the object hierarchy.
The ContainerObject.__init__
method creates an OrderedDict
for each
child set with the name supplied by the child class’ _children_dict_name
method (which is _<pluralized-lowercase-child-type>
by default).
Iterators and accessors¶
Container classes need to define three iterator properties and one
accessor method for each children-set, corresponding to the method names
supplied by the class methods in the child class, _children_iter_name
,
_num_children_name
, _children_keys_name
and _child_accessor_name
.
By default the method names returned by these class methods will be
<pluralized-lowercase-nineml_type>, num_<pluralized-lowercase-nineml_type>,
<pluralized-lowercase-nineml_type>_names, and <lowercase-nineml_type>
respectively. These properties/method should return:
- children_iter:
- A property that returns an iterator over all children in the dictionary
- num_children :
- A property that returns the number of children in the dictionary:
- children_keys:
- A property that returns an iterator over the keys of the dictionary.
If the child type doesn’t have a
name
attribute then the iterator should be named <pluralized-lowercase-nineml-type>_keys instead. - child_accessor:
- An accessor that takes the name (or key) of a child and returns the child.
Note
It would be possible to implement these properties/methods in the
ContainerObject
base class using __getattr__
but since they are
part of the public API that could be confusing to the user.
DocumentLevelObject¶
All NineML classes that are permitted at the top level in NineML documents
(see Document-level types) need to derive from DocumentLevelObject
,
this provides document
and url
attribute properties and is also used
in checks at various points in the code.
Visitors¶
Visitor patterns are used extensively within the NineML Python to find, validate, modify and analyze NineML structures, including their serialization.
Base Visitors¶
Visitor base classes are found in the nineml.visitors.base
module,
which search the object hierarchy and perform an “action” each object. These
visitors use the nineml_*
class attributes (see BaseNineMLObject) to
navigate the object hierarchy and therefore can be used search to any NineML
object.
If not overridden, the action method applied to each object will first check
whether a specialized method for that type of object called
action_<lowercase-nineml_type>
has been implemented and call it if it
has, otherwise call default_action
method. Note that if specialized methods
are not required then the visitor can just override the action
method
directly.
There are a number of different base visitor classes to derive from depending on the requirements of the visitor pattern in question.
BaseVisitor¶
If no contextual information or results of child objects are required then a
visitor can derive directly from the BaseVisitor
class. The action method
will be called before child objects are actioned.
BaseVisitorWithContext¶
If contextual information is required, such as the parent container (and its
parent, etc…) then the BaseVisitorWithContext
can be derived instead. The
immediate context is available via the context
property and the context
of all parent containers via the contexts
attribute.
BaseChildResultsVisitor¶
For visitors that require the results of child objects (e.g. Cloner
) to
in their action methods. The child/children results can be accessed via the
child_result
and children_result
dictionaries. If context information
is also required use the BaseChildResultsVisitorWithContext
visitor.
BasePreAndPostVisitor¶
For visitors the need to perform and action before and after the child results
are actioned. The “pre” action methods are the same as in the BaseVisitor
class and the “post” action method is called post_action
, which by
default will call the post_action_<lowercase-nineml_type>
or
default_post_action
methods. If context information
is also required use the BasePreAndPostVisitorWithContext
visitor.
BaseDualVisitor¶
This visitor visits two objects side by side, raising exceptions if their
structure doesn’t match. As such it is probably only useful for equality
checking (and is derived by the EqualityChecker
and MismatchFinder
visitors). A BaseDualVisitorWithContext
visitor is also available.
Validation¶
Validation is currently only performed on component classes (i.e. Dynamics
,
ConnectionRule
, and RandomDistribution
). A separate visitor is
implemented for every aspect of the component classes that need to be validated
(e.g. name-conflicts, mismatching-dimensions).
Base validators are implemented in the
nineml.abstraction.componentclassvisitors.validators
package with
specializations for each component class type in the corresponding
nineml.abstraction.<componentclass-type>.visitors.validators
packages (at
this stage only the Dynamics
component class has specialised validators).
Serialization¶
For serialization visitors to be able to serialize a NineML object it needs to
define both serialize_node
and unserialize_node
methods.
serialize_node
/unserialize_node
¶
Both serialize_node
and unserialize_node
take a single argument, a
NodeToSerialize
or NodeToUnSerialize
instance respectively. These
node objects wrap a serial element of the given serialization format (e.g.
lxml.etree._Element
for the XMLSerializer
) and provide convenient
methods for adding, or accessing, children, attributes and body elements to the
node.
The node method calls then call format-specific method of the serialization
visitor to un/serialize the NineML objects. However, in some cases
(particularly in some awkward v1.0 syntax), the serialization visitor may need
to be accessed directly, which is available at node.visitor
.
Both serialize_node
and unserialize_node
should accept arbitrary
keyword arguments and pass them on to all calls made to methods of the nodes
and the visitor directly. However, these arguments are not currently used by
any of the current serializers.
has_serial_body
¶
NineML classes that contain “body” text when serialized (to a supporting
serial format) should override the class attribute has_serial_body
to set
it to True
. If the class has a body only in NineML v1.0 syntax but not v2.0
then it should be set to 'v1'
.
NineML classes that just contain a single body element (e.g.
SingleValue
) should set has_serial_body to 'only'
, to allow them to be
collapsed into an attribute in formats that don’t support body text (i.e. YAML,
JSON).