Coverage for pyTooling/DataModel/__init__.py: 100%
36 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-25 12:33 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-25 12:33 +0000
1# ==================================================================================================================== #
2# _____ _ _ ____ _ __ __ _ _ #
3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ | _ \ __ _| |_ __ _| \/ | ___ __| | ___| | #
4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | | | | |/ _` | __/ _` | |\/| |/ _ \ / _` |/ _ \ | #
5# | |_) | |_| || | (_) | (_) | | | | | | (_| |_| |_| | (_| | || (_| | | | | (_) | (_| | __/ | #
6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____/ \__,_|\__\__,_|_| |_|\___/ \__,_|\___|_| #
7# |_| |___/ |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2026-2026 Patrick Lehmann - Bötzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32Foundations of data models: building blocks the classes of a data model share.
34A :class:`KeyValuePairsMixin` attaches arbitrary key-value-pairs to an element of a data model, accessed like a
35dictionary: :pycode:`element["runner.os"] = "Linux"`. :data:`Predicate` is the type of a function selecting
36elements, e.g. in an ``Iterate***`` method.
38.. hint::
40 See :ref:`high-level help <STRUCT/DataModel>` for explanations and usage examples.
41"""
42from __future__ import annotations
44from typing import Any, Callable, Hashable, Iterator, Mapping, Optional as Nullable, TypeVar
46from pyTooling.Common import getFullyQualifiedName
47from pyTooling.Decorators import export
48from pyTooling.MetaClasses import ExtendedType
51__all__ = ["ElementType", "Predicate"]
54# WORKAROUND: Python <3.12
55# Obsolete with the 'type' statement for 'Predicate' below, which declares its type parameter itself.
56ElementType = TypeVar("ElementType")
57"""A type variable for the element a :data:`Predicate` decides on."""
59# WORKAROUND: Python <3.12
60# The 'type' statement (PEP 695) isn't supported by Python 3.11. Use it when Python 3.11 support gets dropped:
61# type Predicate[ElementType] = Callable[[ElementType], bool]
62Predicate = Callable[[ElementType], bool]
63"""
64A function deciding whether an element is selected.
66``Predicate[Vertex]`` is ``Callable[[Vertex], bool]``. Methods filtering what they iterate, reverse or remove take a
67predicate, e.g. :meth:`pyTooling.Graph.BaseGraph.IterateVertices`.
68"""
71@export
72class KeyValuePairsMixin(metaclass=ExtendedType, mixin=True):
73 """
74 Mixin-class adding arbitrary key-value-pairs to an element of a data model, accessed like a dictionary.
76 * :pycode:`element[key]` reads a value.
77 * :pycode:`element.get(key, default)` reads a value, or a default value, if the key doesn't exist.
78 * :pycode:`element[key] = value` creates or updates a pair.
79 * :pycode:`del element[key]` removes a pair.
80 * :pycode:`key in element` checks for a key.
81 * :pycode:`len(element)` counts the pairs.
82 * :pycode:`for key in element:` iterates the keys.
83 """
85 _dict: dict[Hashable, Any] #: A dictionary to store arbitrary key-value-pairs.
87 def __init__(self, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None) -> None:
88 """
89 Initializes the key-value-pairs.
91 :param keyValuePairs: Optional, mapping (dictionary) of key-value-pairs. Default: ``None``.
92 :raises TypeError: If parameter 'keyValuePairs' is not a mapping.
93 """
94 if keyValuePairs is None:
95 self._dict = {}
96 return
97 elif not isinstance(keyValuePairs, Mapping):
98 ex = TypeError("Parameter 'keyValuePairs' is not a mapping ('dict', ...).")
99 ex.add_note(f"Got type '{getFullyQualifiedName(keyValuePairs)}'.")
100 raise ex
102 self._dict = dict(keyValuePairs)
104 def get(self, key: Hashable, default: Any = None) -> Any:
105 """
106 Read an attached attribute (key-value-pair) by key, or a default value, if the key doesn't exist.
108 This method is spelled the way :meth:`dict.get` is, because that is the behaviour it offers.
110 :param key: The key to look for.
111 :param default: Optional, the value returned if the key isn't an attached attribute. Default: ``None``.
112 :returns: The value associated to the given key, otherwise the default value.
113 """
114 return self._dict.get(key, default)
116 def __getitem__(self, key: Hashable) -> Any:
117 """
118 Read an attached attribute (key-value-pair) by key.
120 :param key: The key to look for.
121 :returns: The value associated to the given key.
122 :raises KeyError: If key doesn't exist in the attached attributes.
123 """
124 return self._dict[key]
126 def __setitem__(self, key: Hashable, value: Any) -> None:
127 """
128 Create or update an attached attribute (key-value-pair) by key.
130 If a key doesn't exist yet, a new key-value-pair is created.
132 :param key: The key to create or update.
133 :param value: The value to associate to the given key.
134 """
135 self._dict[key] = value
137 def __delitem__(self, key: Hashable) -> None:
138 """
139 Remove an attached attribute (key-value-pair) by key.
141 :param key: The key to remove.
142 :raises KeyError: If key doesn't exist in the attached attributes.
143 """
144 del self._dict[key]
146 def __contains__(self, key: Hashable) -> bool:
147 """
148 Check if the key is an attached attribute (key-value-pair).
150 :param key: The key to check.
151 :returns: ``True``, if the key is an attached attribute.
152 """
153 return key in self._dict
155 def __len__(self) -> int:
156 """
157 Return the number of attached attributes (key-value-pairs).
159 :returns: Number of attached attributes.
160 """
161 return len(self._dict)
163 def __iter__(self) -> Iterator[Hashable]:
164 """
165 Iterate the keys of the attached attributes (key-value-pairs).
167 :returns: An iterator over the keys, in the order they were added.
168 """
169 return iter(self._dict)