Source code for valguard.constrained_dict
"""Define a dictionary-like container that enforces value constraints.
This module introduces `ConstrainedValueDict`, a mutable mapping that enforces
validation on all inserted values. Each instance is associated with a `Constraint` that
governs both the expected `Value` type and the allowable values.
Validation is performed on assignment and during bulk updates. If a value fails
validation, a `ValidationError` is raised.
"""
from collections.abc import Iterator, Mapping, MutableMapping
from typing import Any
from .constraints import AnyConstraint, Constraint
from .core import TypedValue
from .exceptions import ConfigurationError
# -------------------------------------------------------------------------------------
# Constrained Value Dict
# -------------------------------------------------------------------------------------
Value = TypedValue[Any]
[docs]
class ConstrainedValueDict[K, V: Value](MutableMapping[K, V]):
"""A dictionary that stores validated `Value` instances.
Each value inserted must satisfy the provided `Constraint` instance, which is
responsible for checking both type and content validity.
A mismatch between `value_type` and `constraint` may not be detected until the first
value is inserted and validated.
Arguments:
constraint: The constraint that governs type and value validity.
data: An optional initial mapping of key-value pairs to populate the dictionary.
Raises:
TypeError: If `data` is not a valid mapping or iterable of key-value pairs.
ValueError: If `data` contains non-pair elements.
ValidationError: If any value fails validation by the constraint.
ConfigurationError: If the constraint is misconfigured or invalid.
"""
ANY_CONSTRAINT = AnyConstraint()
def __init__(
self,
constraint: Constraint = ANY_CONSTRAINT,
data: Mapping[K, V] | None = None,
) -> None:
# constraint must be an instance of Constraint
if not isinstance(constraint, Constraint):
msg = (
f"Invalid constraint: expected instance of Constraint, "
f"got {type(constraint)}"
)
raise ConfigurationError(msg)
self._constraint = constraint
self._data: dict[K, V] = {}
if data is not None:
self.update(data) # Triggers validation via __setitem__
def __setitem__(self, key: K, value: V) -> None:
"""Assign a value to the given key after validation.
Raises:
ValidationError: If the value fails constraint validation.
"""
self.constraint.validate(value)
self._data[key] = value
def __getitem__(self, key: K) -> V:
return self._data[key]
def __delitem__(self, key: K) -> None:
del self._data[key]
def __iter__(self) -> Iterator[K]:
return iter(self._data)
def __len__(self) -> int:
return len(self._data)
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"constraint={self.constraint!r}, "
f"data={self._data!r})"
)
@property
def constraint(self) -> Constraint:
return self._constraint