# This module defines sub-classes of C extension classes
# which add functionality that is better expressed in python.
# These types are then pushed (by _magic) down into extension
# code where they will be used as the types passed to callbacks.
from . import _p4p
__all__ = (
'Type',
'Value',
'Struct',
'StructArray',
'Union',
'UnionArray',
)
def Struct(spec=None, id=None):
return ('S', id, spec)
def Union(spec=None, id=None):
return ('U', id, spec)
def StructArray(spec=None, id=None):
return ('aS', id, spec)
def UnionArray(spec=None, id=None):
return ('aU', id, spec)
[docs]class Type(_p4p._Type):
"""Type(fields, id=None, base=None)
:param list fields: A list of tuples describing members of this data structure.
:param str id: Type label string.
:param Type base: Copy the fields of `Type`, then amend with the provided fields.
A definition of a data structure consisting of a list of field names and types,
as well as an optional type name string (id="").
Field type specifications are either a string eg. "d" (double precision float),
or a tuple ("S", None, [fields...) defining a sub-structure. ::
T = Type([
('value', 'I'),
])
Defines a structure with a single field named 'value' with type u32 (unsigned integer width 32-bit).
An example of defining a sub-structure. ::
T = Type([
('value', ('S', None, [
('index', 'i'),
])),
])
Type specifier codes:
==== =======
Code Type
==== =======
? bool
s unicode
b s8
B u8
h s16
H u16
i i32
I u32
l i64
L u64
f f32
d f64
v variant
U union
S struct
==== =======
"""
__slots__ = [] # we don't allow custom attributes for now
__contains__ = _p4p._Type.has
def __call__(self, initial=None):
return Value(self, initial)
[docs] def __iter__(self):
for k in self.keys():
yield k
[docs] def items(self):
return [(k, self[k]) for k in self.keys()]
[docs] def values(self):
return [self[k] for k in self.keys()]
def __repr__(self):
S, id, fields = self.aspy()
return 'Type(%s, id="%s")' % (fields, id)
__str__ = __repr__
_p4p.Type = Type
[docs]class Value(_p4p._Value):
"""Value(type[, initial])
:param Type type: The `Type` describing the structure to be instanciated
:param dict initial: A dictionary (or any mapping) which gives initial values for fields.
Representation of a data structure of a particular :py:class:`Type`.
Can be created using a :py:class:`Type`, with an optional dict containing initial values. ::
A = Value(Type([
('value', 'I'),
]), {
'value': 42,
})
Defines a structure with a single field named 'value' with type u32 (unsigned integer width 32-bit).
An example of defining a sub-structure. ::
A = Value(Type([
('value', ('S', None, [
('index', 'i'),
])),
]), {
'value': {'index', 5},
# 'value.index': 5, # equivalent
})
Defines a structure containing a sub-structure 'value' which has a single field 'index' which is
a signed 32-bit integer.
"""
__slots__ = [] # prevent attribute access to invalid fields
__contains__ = _p4p._Value.has
def keys(self):
"""keys() -> Iterable[str]
"""
return self.type().keys()
def __iter__(self):
return iter(self.type())
[docs] def changed(self, *fields):
"""changed(*fields) -> bool
Test if one or more named fields have changed.
A field is considered to have changed if it is marked as changed,
or if its parent, or any child, field is marked as changed.
"""
S = super(Value, self).changed
for fld in fields or (None,): # no args tests for any change
if S(fld):
return True
return False
[docs] def changedSet(self, expand=False, parents=False):
"""changedSet(expand=False, parents=False) -> set
:param bool expand: Whether to expand when entire sub-structures are marked as changed.
If True, then sub-structures are expanded and only leaf fields will be included.
If False, then a direct translation is made, which may include both leaf and sub-structure fields.
:param bool parents: If True, include fake entries for parent sub-structures with leaf fields marked as changed.
:returns: A :py:class:`set` of names of those fields marked as changed.
Return a :py:class:`set` containing the names of all changed fields. ::
A = Value(Type([
('x', 'i'),
('z', ('S', None, [
('a', 'i'),
('b', 'i'),
])),
]), {
})
A.mark('z')
assert A.changedSet(expand=False) == {'z'} # only shows fields explicitly marked
assert A.changedSet(expand=True) == {'z.a', 'z.b'} # actually used during network transmission
A.mark('z.a') # redundant
assert A.changedSet(expand=False) == {'z', 'z.a'}
assert A.changedSet(expand=True) == {'z.a', 'z.b'}
A.unmark('z')
assert A.changedSet(expand=False) == {'z.a'}
assert A.changedSet(expand=True) == {'z.a'}
assert A.changedSet(expand=False, parents=True) == {'z', 'z.a'}
assert A.changedSet(expand=True, parents=True) == {'z', 'z.a'}
* expand=False, parents=False gives a direct mapping of the underlying BitSet as it would (get/monitor),
or have been (put/rpc), moved over the network.
* expand=True, parents=False gives the effective set of leaf fields which will be moved over the network.
taking into account the use of whole sub-structure compress/shorthand bits.
* expand=False, parents=True gives a way of testing if anything changed within a set of interesting fields
(cf. set.intersect).
"""
return _p4p._Value.changedSet(self, expand, parents)
# TODO: deprecate
asSet = changedSet
def clear(self):
self.mark(None, False)
__str__ = _p4p._Value.tostr
def __repr__(self):
parts = []
ID = self.getID()
if ID!='structure':
parts.append('id:'+ID)
try:
parts.append(repr(self.value))
except AttributeError: # no .value
try:
parts.append(repr(self.get(self.type().keys()[0])))
except IndexError: # empty Structure
pass
return 'Value(%s)'%', '.join(parts)
_p4p.Value = Value