Bases: ABC
Base class for all domain value objects.
Value objects are immutable objects defined entirely by their attributes
rather than by an identity. Two value objects with the same attributes
are considered equal.
Concrete subclasses are automatically frozen, hashable, and structurally
comparable via auto_freeze, auto_hash, and
auto_eq. The three decorators are independent — each applies
exactly one concern:
@auto_freeze enforces immutability.
@auto_hash generates __hash__ from class fields.
@auto_eq generates __eq__ from class fields.
Intermediate abstract classes are skipped so leaf subclasses finish
their own __init__ without restriction.
Example
class Email(ValueObject[str]):
__slots__ = ("_value",)
def __init__(self, value: str) -> None:
super().__init__()
if "@" not in value:
raise ValueError("Invalid email format")
self._value = value
@property
def value(self) -> str:
return self._value
Source code in src/forging_blocks/domain/value_object.py
| class ValueObject[RawValueType](ABC):
"""Base class for all domain value objects.
Value objects are immutable objects defined entirely by their attributes
rather than by an identity. Two value objects with the same attributes
are considered equal.
Concrete subclasses are automatically frozen, hashable, and structurally
comparable via `auto_freeze`, `auto_hash`, and
`auto_eq`. The three decorators are independent — each applies
exactly one concern:
- `@auto_freeze` enforces immutability.
- `@auto_hash` generates `__hash__` from class fields.
- `@auto_eq` generates `__eq__` from class fields.
Intermediate abstract classes are skipped so leaf subclasses finish
their own `__init__` without restriction.
Example:
```python
class Email(ValueObject[str]):
__slots__ = ("_value",)
def __init__(self, value: str) -> None:
super().__init__()
if "@" not in value:
raise ValueError("Invalid email format")
self._value = value
@property
def value(self) -> str:
return self._value
```
"""
def __init_subclass__(cls, **kwargs: Any) -> None:
"""Apply `auto_freeze`, `auto_hash`, and `auto_eq` to concrete subclasses.
`auto_freeze` enforces immutability; `auto_hash` generates
`__hash__` from class fields; `auto_eq` generates `__eq__`
from class fields. All three are independent decorators — none
composes the others.
"""
super().__init_subclass__(**kwargs)
if not inspect.isabstract(cls):
auto_freeze(cls)
auto_hash(cls)
auto_eq(cls)
def __str__(self) -> str:
field_names = getattr(self, "__auto_hash_fields__", ())
components = tuple(getattr(self, name) for name in field_names)
if len(components) == 1:
return f"{self.__class__.__name__}({components[0]!r})"
return f"{self.__class__.__name__}{components!r}"
def __repr__(self) -> str:
return self.__str__()
@property
@abstractmethod
def value(self) -> RawValueType:
"""Return the primary raw value encapsulated by the ValueObject."""
|
value: RawValueType
abstractmethod
property
Return the primary raw value encapsulated by the ValueObject.
__init_subclass__(**kwargs: Any) -> None
Apply auto_freeze, auto_hash, and auto_eq to concrete subclasses.
auto_freeze enforces immutability; auto_hash generates
__hash__ from class fields; auto_eq generates __eq__
from class fields. All three are independent decorators — none
composes the others.
Source code in src/forging_blocks/domain/value_object.py
| def __init_subclass__(cls, **kwargs: Any) -> None:
"""Apply `auto_freeze`, `auto_hash`, and `auto_eq` to concrete subclasses.
`auto_freeze` enforces immutability; `auto_hash` generates
`__hash__` from class fields; `auto_eq` generates `__eq__`
from class fields. All three are independent decorators — none
composes the others.
"""
super().__init_subclass__(**kwargs)
if not inspect.isabstract(cls):
auto_freeze(cls)
auto_hash(cls)
auto_eq(cls)
|