Coverage for src/configuraptor/loaders/register.py: 100%

40 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-11-07 15:36 +0100

1""" 

2Exposes `register_loader` to define loader for specific file types. 

3""" 

4 

5import typing 

6from pathlib import Path 

7 

8from ._types import T_config 

9 

10T_loader = typing.Callable[[typing.BinaryIO, Path], T_config] 

11T_WrappedLoader = typing.Callable[[T_loader], T_loader] 

12 

13T_dumper = typing.Callable[..., str | dict[str, typing.Any]] 

14T_WrappedDumper = typing.Callable[[T_dumper], T_dumper] 

15 

16LOADERS: dict[str, T_loader] = {} 

17DUMPERS: dict[str, T_dumper] = {} 

18 

19R = typing.TypeVar("R") 

20AnyCallable = typing.Callable[..., typing.Any] 

21 

22 

23def register_something(storage: dict[str, typing.Any], *extension_args: typing.Any) -> AnyCallable: 

24 f_outer = None 

25 extension_set = set() 

26 

27 for extension in extension_args: 

28 if not isinstance(extension, str): 

29 f_outer = extension 

30 extension = extension.__name__ 

31 

32 elif extension.startswith("."): 

33 extension = extension.removeprefix(".") 

34 

35 extension_set.add(extension) 

36 

37 def wrapper(f_inner: typing.Callable[..., R]) -> typing.Callable[..., R]: 

38 storage.update({ext: f_inner for ext in extension_set}) 

39 return f_inner 

40 

41 if f_outer: 

42 return wrapper(f_outer) # -> T_Loader 

43 else: 

44 return wrapper # -> T_WrappedLoader 

45 

46 

47@typing.overload 

48def register_loader(*extension_args: str) -> T_WrappedLoader: 

49 """ 

50 Overload for case with parens. 

51 

52 @register_loader("yaml", ".yml") 

53 def load_yaml(...): 

54 ... 

55 

56 # extension_args is a tuple of strings 

57 # this will return a wrapper which takes `load_yaml` as input and output. 

58 """ 

59 

60 

61@typing.overload 

62def register_loader(*extension_args: T_loader) -> T_loader: 

63 """ 

64 Overload for case without parens. 

65 

66 @register_loader 

67 def json(...): 

68 ... 

69 

70 # extension_args is a tuple of 1: `def json` 

71 # this will simply return the `json` method itself. 

72 """ 

73 

74 

75def register_loader(*extension_args: str | T_loader) -> T_loader | T_WrappedLoader: 

76 """ 

77 Register a data loader for a new filetype. 

78 

79 Used as a decorator on a method that takes two arguments: 

80 (BinaryIO, Path) - an open binary file stream to the config file and the pathlib.Path to the config file. 

81 By default, the open file handler can be used. 

82 However, some loaders (such as .ini) don't support binary file streams. 

83 These can use the Path to open and read the file themselves however they please. 

84 """ 

85 return register_something(LOADERS, *extension_args) 

86 

87 

88@typing.overload 

89def register_dumper(*extension_args: str) -> AnyCallable: 

90 """ 

91 Overload for case with parens. 

92 

93 @register_dumper("yaml", ".yml") 

94 def dump_yaml(...): 

95 ... 

96 

97 # extension_args is a tuple of strings 

98 # this will return a wrapper which takes `dump_yaml` as input and output. 

99 """ 

100 

101 

102@typing.overload 

103def register_dumper(*extension_args: typing.Callable[..., R]) -> typing.Callable[..., R]: 

104 """ 

105 Overload for case without parens. 

106 

107 @register_dumper 

108 def json(...): 

109 ... 

110 

111 # extension_args is a tuple of 1: `def json` 

112 # this will simply return the `json` method itself. 

113 """ 

114 

115 

116def register_dumper(*extension_args: str | typing.Callable[..., R]) -> AnyCallable | typing.Callable[..., R]: 

117 """ 

118 Register a data dumper for a new filetype. 

119 

120 Not everything that can be loaded, can also be dumped (currently). 

121 """ 

122 return register_something(DUMPERS, *extension_args) 

123 

124 

125__all__ = ["register_loader", "register_dumper", "LOADERS", "DUMPERS"]