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

1"""

2 Logic for the TypedConfig inheritable class.

3"""

4

5 import typing

6

7 from .core import T_data, all_annotations, check_type, load_into

8 from .errors import ConfigErrorExtraKey, ConfigErrorInvalidType

9

10 C = typing.TypeVar("C", bound=typing.Any)

11

12

13 class TypedConfig:

14 """

15 Can be used instead of load_into.

16 """

17

18 @classmethod

19 def load(

20 cls: typing.Type[C], data: T_data, key: str = None, init: dict[str, typing.Any] = None, strict: bool = True

21 ) -> C:

22 """

23 Load a class' config values from the config file.

24

25 SomeClass.load(data, ...) = load_into(SomeClass, data, ...).

26 """

27 return load_into(cls, data, key=key, init=init, strict=strict)

28

29 def _update(self, _strict: bool = True, _allow_none: bool = False, **values: typing.Any) -> None:

30 """

31 Can be used if .update is overwritten with another value in the config.

32 """

33 annotations = all_annotations(self.__class__)

34

35 for key, value in values.items():

36 if value is None and not _allow_none:

37 continue

38

39 if _strict and key not in annotations:

40 raise ConfigErrorExtraKey(cls=self.__class__, key=key, value=value)

41

42 if _strict and not check_type(value, annotations[key]) and not (value is None and _allow_none):

43 raise ConfigErrorInvalidType(expected_type=annotations[key], key=key, value=value)

44

45 setattr(self, key, value)

46

47 def update(self, _strict: bool = True, _allow_none: bool = False, **values: typing.Any) -> None:

48 """

49 Update values on this config.

50

51 Args:

52 _strict: allow wrong types?

53 _allow_none: allow None or skip those entries?

54 **values: key: value pairs in the right types to update.

55 """

56 return self._update(_strict, _allow_none, **values)

57

58 def _format(self, string: str) -> str:

59 """

60 Format the config data into a string template.

61

62 Replacement for string.format(**config), which is only possible for MutableMappings.

63 MutableMapping does not work well with our Singleton Metaclass.

64 """

65 return string.format(**self.__dict__)

66

67

68# also expose as separate function:

69 def update(instance: typing.Any, _strict: bool = True, _allow_none: bool = False, **values: typing.Any) -> None:

70 """

71 Update values on a config.

72

73 Args:

74 instance: config instance to update

75 _strict: allow wrong types?

76 _allow_none: allow None or skip those entries?

77 **values: key: value pairs in the right types to update.

78 """

79 return TypedConfig._update(instance, _strict, _allow_none, **values)