Skip to content

Entity

Base Entity class for domain layer.

entity

Base Entity class for domain layer.

Entity

Bases: Generic[TId], ABC

Base class for all domain entities.

An entity is defined by its identity rather than its attributes. Two entities with the same
identifier are considered equal, regardless of their other attributes.

Source code in src/forging_blocks/domain/entity.py
class Entity(Generic[TId], ABC):
    """Base class for all domain entities.

    An entity is defined by its identity rather than its attributes. Two entities with the same
    identifier are considered equal, regardless of their other attributes.
    """

    _id: TId | None
    __is_frozen: bool

    def __init__(self, entity_id: TId | None) -> None:
        object.__setattr__(self, "_id", entity_id)
        object.__setattr__(self, "_Entity__is_frozen", True)

    def __setattr__(self, name: str, value: Any) -> None:
        """Prevent modification of '_id' once set."""
        if (
            getattr(self, "_Entity__is_frozen", False)
            and name == "_id"
            and getattr(self, "_id", None) is not None
            and value != self._id
        ):
            raise AttributeError(
                f"{self.__class__.__name__}: cannot modify '{name}' once set "
                f"(current value={self._id!r})."
            )
        object.__setattr__(self, name, value)

    def __delattr__(self, name: str) -> None:
        """Prevent deletion of '_id'."""
        if name == "_id":
            raise AttributeError(f"{self.__class__.__name__}: cannot delete 'id'.")
        object.__delattr__(self, name)

    def __eq__(self, other: object) -> bool:
        """Check equality based on type and identifier."""
        if type(self) is not type(other):
            return False

        other_entty = cast(Entity[TId], other)

        if self._id is None or other_entty._id is None:
            return self is other_entty

        return self._id == other_entty._id

    def __hash__(self) -> int:
        """Return the hash based on the entity's identifier.

        Raises:
            DraftEntityIsNotHashableError: If the entity is a draft (id is None).
        """
        if self._id is None:
            raise DraftEntityIsNotHashableError.from_class_name(self.__class__.__name__)
        return hash(self._id)

    def __str__(self) -> str:
        """Return a user-friendly string representation of the entity."""
        return f"{self.__class__.__name__}(id={self._id})"

    def __repr__(self) -> str:
        """Return a developer-friendly string representation of the entity."""
        return str(self)

    @property
    def id(self) -> TId | None:
        """Return the entity's identifier, or None if it's a draft entity."""
        return self._id

    def is_persisted(self) -> bool:
        """Return True if the entity has a defined identifier (i.e., is persisted)."""
        return self._id is not None

id: TId | None property

Return the entity's identifier, or None if it's a draft entity.

__setattr__(name: str, value: Any) -> None

Prevent modification of '_id' once set.

Source code in src/forging_blocks/domain/entity.py
def __setattr__(self, name: str, value: Any) -> None:
    """Prevent modification of '_id' once set."""
    if (
        getattr(self, "_Entity__is_frozen", False)
        and name == "_id"
        and getattr(self, "_id", None) is not None
        and value != self._id
    ):
        raise AttributeError(
            f"{self.__class__.__name__}: cannot modify '{name}' once set "
            f"(current value={self._id!r})."
        )
    object.__setattr__(self, name, value)

__delattr__(name: str) -> None

Prevent deletion of '_id'.

Source code in src/forging_blocks/domain/entity.py
def __delattr__(self, name: str) -> None:
    """Prevent deletion of '_id'."""
    if name == "_id":
        raise AttributeError(f"{self.__class__.__name__}: cannot delete 'id'.")
    object.__delattr__(self, name)

__eq__(other: object) -> bool

Check equality based on type and identifier.

Source code in src/forging_blocks/domain/entity.py
def __eq__(self, other: object) -> bool:
    """Check equality based on type and identifier."""
    if type(self) is not type(other):
        return False

    other_entty = cast(Entity[TId], other)

    if self._id is None or other_entty._id is None:
        return self is other_entty

    return self._id == other_entty._id

__hash__() -> int

Return the hash based on the entity's identifier.

Raises:

Type Description
DraftEntityIsNotHashableError

If the entity is a draft (id is None).

Source code in src/forging_blocks/domain/entity.py
def __hash__(self) -> int:
    """Return the hash based on the entity's identifier.

    Raises:
        DraftEntityIsNotHashableError: If the entity is a draft (id is None).
    """
    if self._id is None:
        raise DraftEntityIsNotHashableError.from_class_name(self.__class__.__name__)
    return hash(self._id)

__str__() -> str

Return a user-friendly string representation of the entity.

Source code in src/forging_blocks/domain/entity.py
def __str__(self) -> str:
    """Return a user-friendly string representation of the entity."""
    return f"{self.__class__.__name__}(id={self._id})"

__repr__() -> str

Return a developer-friendly string representation of the entity.

Source code in src/forging_blocks/domain/entity.py
def __repr__(self) -> str:
    """Return a developer-friendly string representation of the entity."""
    return str(self)

is_persisted() -> bool

Return True if the entity has a defined identifier (i.e., is persisted).

Source code in src/forging_blocks/domain/entity.py
def is_persisted(self) -> bool:
    """Return True if the entity has a defined identifier (i.e., is persisted)."""
    return self._id is not None