Data Model Foundations

The pyTooling.DataModel package holds building blocks the classes of a data model share.

Key-Value-Pairs

An element of a data model often carries data its model doesn’t know: the results of an analysis, or what only one service reports. KeyValuePairsMixin attaches arbitrary key-value-pairs to an element, accessed like a dictionary. A key is any Hashable object.

element["runner.os"] = "Linux"   # create or update a pair
os = element["runner.os"]        # read a value; KeyError if the key doesn't exist
os = element.get("runner.os")    # read a value; None (or a given default) if the key doesn't exist
del element["runner.os"]         # remove a pair; KeyError if the key doesn't exist
if "runner.os" in element: ...   # check for a key
count = len(element)             # number of pairs
for key in element: ...          # the keys, in the order they were added

The constructor takes the initial pairs as a mapping and copies them. Anything but a mapping - e.g. a list of pairs - is rejected with a TypeError.

The mixin’s field becomes a slot of the first slotted class deriving from it. It is used in one of two ways:

  • The slotted root class of a data model, whose elements all carry key-value-pairs, derives from the mixin alone.

    from pyTooling.DataModel   import KeyValuePairsMixin
    from pyTooling.MetaClasses import ExtendedType
    
    class Element(KeyValuePairsMixin, metaclass=ExtendedType, slots=True):
      _name: str
    
      def __init__(self, name: str, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None) -> None:
        super().__init__(keyValuePairs)
        self._name = name
    
  • A class with a base-class of its own adds the KeyValuePairsMixin as a further base-class and calls its initializer.

    from pyTooling.DataModel import KeyValuePairsMixin
    
    class Node(Base, KeyValuePairsMixin):
      def __init__(self, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None) -> None:
        super().__init__()
        KeyValuePairsMixin.__init__(self, keyValuePairs)
    

The elements of pyTooling.CI, pyTooling.ControlFlow and pyTooling.Graph derive from KeyValuePairsMixin by their root class Base, a Node directly, and those of pyTooling.Tracing by TraceElement.

Predicate

A method selecting elements - iterating, reversing or removing those a function decides on - takes a predicate: a function receiving an element and returning a bool. Predicate is its type, generic in the element type.

from pyTooling.DataModel import Predicate

def IterateVertices(self, predicate: Nullable[Predicate[Vertex]] = None) -> Generator[Vertex, None, None]:
  ...

Predicate[Vertex] is Callable[[Vertex], bool]. It is written with a type variable, not with the type statement of Python 3.12, because pyTooling supports Python 3.11.