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

26 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-09 20:07 +0100

1""" 

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

3""" 

4 

5import functools 

6import typing 

7 

8from .dump import asdict, asjson 

9 

10T = typing.TypeVar("T") 

11 

12 

13def 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 

27def 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 

60def 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 

71def 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 

81def 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)