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

1"""Module that defines a generic entity.""" 

2import inspect 

3from typing import Any, Generic, TypeVar 

4 

5from kwai.core.domain.value_objects.identifier import Identifier 

6 

7T = TypeVar("T", bound=Identifier) 

8 

9 

10class Entity(Generic[T]): 

11 """A base class for an entity.""" 

12 

13 def __init__(self, id_: T): 

14 self._id = id_ 

15 

16 @property 

17 def id(self) -> T: 

18 """Return the id of the entity.""" 

19 return self._id 

20 

21 def has_id(self) -> bool: 

22 """Check if this entity has a valid id. 

23 

24 Returns: 

25 bool: True when the id is not empty. 

26 """ 

27 return not self._id.is_empty() 

28 

29 @classmethod 

30 def replace(cls, entity: "Entity[T]", **changes) -> Any: 

31 """Return a new entity from the existing entity. 

32 

33 Args: 

34 entity(Entity[T]): The entity to copy the values from 

35 changes: the values to override when creating the new entity. 

36 

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. 

42 

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 

58 

59 # We already have the value when the argument is passed 

60 if argument in changes: 

61 continue 

62 

63 # The attribute is not passed, so we need to use the original value. 

64 

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 

70 

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 

76 

77 return entity.__class__(**changes)