Coverage for src/configuraptor/loaders/register.py: 100%
40 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-01-22 21:21 +0100
« prev ^ index » next coverage.py v7.2.7, created at 2024-01-22 21:21 +0100
1"""
2Exposes `register_loader` to define loader for specific file types.
3"""
5import typing
6from pathlib import Path
8from ._types import T_config
10T_loader = typing.Callable[[typing.BinaryIO, Path], T_config]
11T_WrappedLoader = typing.Callable[[T_loader], T_loader]
13T_dumper = typing.Callable[..., str | dict[str, typing.Any]]
14T_WrappedDumper = typing.Callable[[T_dumper], T_dumper]
16LOADERS: dict[str, T_loader] = {}
17DUMPERS: dict[str, T_dumper] = {}
19R = typing.TypeVar("R")
20AnyCallable = typing.Callable[..., typing.Any]
23def register_something(storage: dict[str, typing.Any], *extension_args: typing.Any) -> AnyCallable:
24 f_outer = None
25 extension_set = set()
27 for extension in extension_args:
28 if not isinstance(extension, str):
29 f_outer = extension
30 extension = extension.__name__
32 elif extension.startswith("."):
33 extension = extension.removeprefix(".")
35 extension_set.add(extension)
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
41 if f_outer:
42 return wrapper(f_outer) # -> T_Loader
43 else:
44 return wrapper # -> T_WrappedLoader
47@typing.overload
48def register_loader(*extension_args: str) -> T_WrappedLoader:
49 """
50 Overload for case with parens.
52 @register_loader("yaml", ".yml")
53 def load_yaml(...):
54 ...
56 # extension_args is a tuple of strings
57 # this will return a wrapper which takes `load_yaml` as input and output.
58 """
61@typing.overload
62def register_loader(*extension_args: T_loader) -> T_loader:
63 """
64 Overload for case without parens.
66 @register_loader
67 def json(...):
68 ...
70 # extension_args is a tuple of 1: `def json`
71 # this will simply return the `json` method itself.
72 """
75def register_loader(*extension_args: str | T_loader) -> T_loader | T_WrappedLoader:
76 """
77 Register a data loader for a new filetype.
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)
88@typing.overload
89def register_dumper(*extension_args: str) -> typing.Callable[[typing.Callable[..., R]], typing.Callable[..., R]]:
90 """
91 Overload for case with parens.
93 @register_dumper("yaml", ".yml")
94 def dump_yaml(...):
95 ...
97 # extension_args is a tuple of strings
98 # this will return a wrapper which takes `dump_yaml` as input and output.
99 """
102@typing.overload
103def register_dumper(*extension_args: typing.Callable[..., R]) -> typing.Callable[..., R]:
104 """
105 Overload for case without parens.
107 @register_dumper
108 def json(...):
109 ...
111 # extension_args is a tuple of 1: `def json`
112 # this will simply return the `json` method itself.
113 """
116def register_dumper(
117 *extension_args: str | typing.Callable[..., R]
118) -> typing.Callable[[typing.Callable[..., R]], typing.Callable[..., R]] | typing.Callable[..., R]:
119 """
120 Register a data dumper for a new filetype.
122 Not everything that can be loaded, can also be dumped (currently).
123 """
124 return register_something(DUMPERS, *extension_args)
127__all__ = ["register_loader", "register_dumper", "LOADERS", "DUMPERS"]