Coverage for kwai/core/domain/entity.py: 90%
29 statements
« prev ^ index » next coverage.py v7.3.0, created at 2023-09-05 17:55 +0000
« prev ^ index » next coverage.py v7.3.0, created at 2023-09-05 17:55 +0000
1"""Module that defines a generic entity."""
2import inspect
3from typing import Any, Generic, TypeVar
5from kwai.core.domain.value_objects.identifier import Identifier
7T = TypeVar("T", bound=Identifier)
10class Entity(Generic[T]):
11 """A base class for an entity."""
13 def __init__(self, id_: T):
14 self._id = id_
16 @property
17 def id(self) -> T:
18 """Return the id of the entity."""
19 return self._id
21 def has_id(self) -> bool:
22 """Check if this entity has a valid id.
24 Returns:
25 bool: True when the id is not empty.
26 """
27 return not self._id.is_empty()
29 @classmethod
30 def replace(cls, entity: "Entity[T]", **changes) -> Any:
31 """Return a new entity from the existing entity.
33 Args:
34 entity(Entity[T]): The entity to copy the values from
35 changes: the values to override when creating the new entity.
37 Use the same keyword arguments as used on the class constructor (__init__) to
38 replace the existing value of an attribute.
39 The class constructor will be called to create this new entity.
40 The arguments of the constructor that are not passed in "changes", will get
41 the value from the current entity.
43 Note:
44 To make it clear that the attributes of an entity are private, they are
45 prefixed with an underscore. The name of the keyword argument does not
46 contain this underscore. This method will try to find the attribute first
47 without underscore. When no attribute exists with that name, it will try
48 to find it with an underscore.
49 When an argument of the constructor contains an underscore as suffix (to
50 avoid naming conflicts for example), the underscore will be removed to find
51 the attribute of the class.
52 """
53 ctor_arguments = inspect.signature(entity.__class__.__init__).parameters
54 for argument in ctor_arguments:
55 # self is not needed
56 if argument == "self":
57 continue
59 # We already have the value when the argument is passed
60 if argument in changes:
61 continue
63 # The attribute is not passed, so we need to use the original value.
65 # Try to get the value of a public attribute
66 attribute_name = argument.removesuffix("_")
67 if attribute_name in entity.__dict__:
68 changes[argument] = entity.__dict__[attribute_name]
69 continue
71 # Try to get the value of the private attribute
72 private_attribute_name = "_" + attribute_name
73 if private_attribute_name in entity.__dict__:
74 changes[argument] = entity.__dict__[private_attribute_name]
75 continue
77 return entity.__class__(**changes)