Coverage for src/configuraptor/beautify.py: 100%

1"""

2 Add @beautify behavior to enhance configuraptor/TypedConfig classes automagically!

3"""

4

5 import functools

6 import typing

7

8 from .dump import asdict, asjson

9

10 T = typing.TypeVar("T")

11

12

13 def is_default(obj: typing.Any, prop: str) -> bool:

14 """

15 Check if the property of an object is set to its default value.

16

17 Args:

18 obj (typing.Any): The object to check.

19 prop (str): The property to check.

20

21 Returns:

22 bool: True if the property is set to its default value, False otherwise.

23 """

24 return getattr(obj, prop) is getattr(object, prop)

25

26

27 def patch(cls: typing.Type[T], patch_repr: bool, patch_str: bool) -> None:

28 """

29 Patch the __str__ and __repr__ methods of a class if they are set to their default values.

30

31 Args:

32 cls (typing.Type[typing.Any]): The class to patch.

33 patch_repr: patch __repr__? (if no custom one set yet)

34 patch_str: patch __str__? (if no custom one set yet)

35 """

36

37 def _repr(self: T) -> str:

38 """

39 Custom __repr__ by configuraptor @beautify.

40 """

41 clsname = type(self).__name__

42 data = asdict(self, with_top_level_key=False, exclude_internals=2)

43 return f"<{clsname} {data}>"

44

45 def _str(self: T) -> str:

46 """

47 Custom __str__ by configuraptor @beautify.

48 """

49 return asjson(self, with_top_level_key=False, exclude_internals=2)

50

51 # if magic method is already set, don't overwrite it!

52 if patch_str and is_default(cls, "__str__"):

53 cls.__str__ = _str # type: ignore

54

55 if patch_repr and is_default(cls, "__repr__"):

56 cls.__repr__ = _repr # type: ignore

57

58

59@typing.overload

60 def beautify(

61 maybe_cls: typing.Type[T],

62 repr: bool = True, # noqa A002

63 str: bool = True, # noqa A002

64) -> typing.Type[T]:

65 """

66 Overload function for the beautify decorator when used without parentheses.

67 """

68

69

70@typing.overload

71 def beautify(

72 maybe_cls: None = None,

73 repr: bool = True, # noqa A002

74 str: bool = True, # noqa A002

75) -> typing.Callable[[typing.Type[T]], typing.Type[T]]:

76 """

77 Overload function for the beautify decorator when used with parentheses.

78 """

79

80

81 def beautify(

82 maybe_cls: typing.Type[T] | None = None,

83 repr: bool = True, # noqa A002

84 str: bool = True, # noqa A002

85) -> typing.Type[T] | typing.Callable[[typing.Type[T]], typing.Type[T]]:

86 """

87 The beautify decorator. Enhances a class by patching its __str__ and __repr__ methods.

88

89 Args:

90 maybe_cls (typing.Type[T] | None, optional): The class to beautify. None when used with parentheses.

91 repr: patch __repr__? (if no custom one set yet)

92 str: patch __str__? (if no custom one set yet)

93

94 Returns:

95 The beautified class or the beautify decorator.

96 """

97 if maybe_cls:

98 patch(maybe_cls, patch_repr=repr, patch_str=str)

99 return maybe_cls

100 else:

101 return functools.partial(beautify, repr=repr, str=str)