omdev 0.0.0.dev416__py3-none-any.whl → 0.0.0.dev500__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of omdev might be problematic. Click here for more details.

Files changed (211) hide show
  1. omdev/{.manifests.json → .omlish-manifests.json} +23 -47
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +12 -8
  4. omdev/amalg/cli/main.py +1 -2
  5. omdev/amalg/gen/gen.py +49 -6
  6. omdev/amalg/gen/imports.py +1 -1
  7. omdev/amalg/gen/manifests.py +1 -1
  8. omdev/amalg/gen/resources.py +1 -1
  9. omdev/amalg/gen/srcfiles.py +26 -3
  10. omdev/amalg/gen/strip.py +1 -1
  11. omdev/amalg/gen/types.py +1 -1
  12. omdev/amalg/gen/typing.py +1 -1
  13. omdev/amalg/info.py +32 -0
  14. omdev/cache/compute/storage.py +3 -1
  15. omdev/cache/data/actions.py +1 -1
  16. omdev/cache/data/cache.py +2 -2
  17. omdev/cache/data/specs.py +1 -1
  18. omdev/cexts/_boilerplate.cc +2 -3
  19. omdev/cexts/_distutils/build_ext.py +5 -2
  20. omdev/cexts/_distutils/compilers/ccompiler.py +5 -2
  21. omdev/cexts/_distutils/compilers/options.py +3 -0
  22. omdev/cexts/_distutils/compilers/unixccompiler.py +6 -2
  23. omdev/cexts/_distutils/dir_util.py +6 -2
  24. omdev/cexts/_distutils/errors.py +3 -0
  25. omdev/cexts/_distutils/extension.py +3 -0
  26. omdev/cexts/_distutils/file_util.py +6 -2
  27. omdev/cexts/_distutils/modified.py +3 -0
  28. omdev/cexts/_distutils/spawn.py +6 -2
  29. omdev/cexts/_distutils/sysconfig.py +3 -0
  30. omdev/cexts/_distutils/util.py +6 -2
  31. omdev/cexts/_distutils/version.py +3 -0
  32. omdev/cexts/cmake.py +5 -3
  33. omdev/cexts/scan.py +1 -2
  34. omdev/ci/cache.py +7 -3
  35. omdev/ci/cli.py +6 -4
  36. omdev/ci/docker/buildcaching.py +3 -1
  37. omdev/ci/docker/cache.py +2 -1
  38. omdev/ci/docker/cacheserved/cache.py +4 -1
  39. omdev/ci/docker/cacheserved/manifests.py +2 -2
  40. omdev/ci/docker/dataserver.py +2 -2
  41. omdev/ci/docker/imagepulling.py +2 -1
  42. omdev/ci/docker/packing.py +1 -1
  43. omdev/ci/docker/repositories.py +2 -1
  44. omdev/ci/github/api/clients.py +8 -4
  45. omdev/ci/github/api/v1/client.py +4 -1
  46. omdev/ci/github/api/v2/api.py +2 -0
  47. omdev/ci/github/api/v2/azure.py +4 -1
  48. omdev/ci/github/api/v2/client.py +4 -1
  49. omdev/cli/clicli.py +37 -7
  50. omdev/clipboard/clipboard.py +1 -1
  51. omdev/cmake.py +2 -1
  52. omdev/cmdlog/cli.py +1 -2
  53. omdev/dataclasses/_dumping.py +1960 -0
  54. omdev/dataclasses/_template.py +22 -0
  55. omdev/dataclasses/cli.py +7 -2
  56. omdev/dataclasses/codegen.py +342 -62
  57. omdev/dataclasses/dumping.py +200 -0
  58. omdev/dataserver/handlers.py +3 -2
  59. omdev/dataserver/targets.py +2 -2
  60. omdev/imgur.py +2 -2
  61. omdev/interp/cli.py +1 -1
  62. omdev/interp/inspect.py +2 -1
  63. omdev/interp/providers/base.py +3 -2
  64. omdev/interp/providers/standalone.py +4 -1
  65. omdev/interp/providers/system.py +2 -2
  66. omdev/interp/pyenv/install.py +2 -1
  67. omdev/interp/pyenv/provider.py +2 -2
  68. omdev/interp/types.py +3 -2
  69. omdev/interp/uv/provider.py +40 -2
  70. omdev/interp/uv/uv.py +2 -2
  71. omdev/interp/venvs.py +3 -2
  72. omdev/irc/messages/base.py +50 -0
  73. omdev/irc/messages/formats.py +92 -0
  74. omdev/irc/messages/messages.py +775 -0
  75. omdev/irc/messages/parsing.py +99 -0
  76. omdev/irc/numerics/formats.py +97 -0
  77. omdev/irc/numerics/numerics.py +865 -0
  78. omdev/irc/numerics/types.py +59 -0
  79. omdev/irc/protocol/LICENSE +11 -0
  80. omdev/irc/protocol/__init__.py +61 -0
  81. omdev/irc/protocol/consts.py +6 -0
  82. omdev/irc/protocol/errors.py +30 -0
  83. omdev/irc/protocol/message.py +21 -0
  84. omdev/irc/protocol/nuh.py +55 -0
  85. omdev/irc/protocol/parsing.py +158 -0
  86. omdev/irc/protocol/rendering.py +153 -0
  87. omdev/irc/protocol/tags.py +102 -0
  88. omdev/irc/protocol/utils.py +30 -0
  89. omdev/manifests/_dumping.py +529 -136
  90. omdev/manifests/building.py +6 -3
  91. omdev/manifests/main.py +1 -1
  92. omdev/markdown/__init__.py +0 -0
  93. omdev/markdown/incparse.py +116 -0
  94. omdev/markdown/tokens.py +51 -0
  95. omdev/oci/data.py +2 -2
  96. omdev/oci/datarefs.py +2 -2
  97. omdev/oci/media.py +2 -2
  98. omdev/oci/repositories.py +3 -2
  99. omdev/packaging/marshal.py +9 -9
  100. omdev/packaging/requires.py +6 -6
  101. omdev/packaging/revisions.py +5 -2
  102. omdev/packaging/specifiers.py +41 -42
  103. omdev/packaging/versions.py +10 -10
  104. omdev/packaging/wheelfile.py +4 -2
  105. omdev/precheck/blanklines.py +66 -0
  106. omdev/precheck/caches.py +1 -1
  107. omdev/precheck/imports.py +14 -1
  108. omdev/precheck/lite.py +2 -2
  109. omdev/precheck/main.py +5 -5
  110. omdev/precheck/unicode.py +39 -15
  111. omdev/py/asts/__init__.py +0 -0
  112. omdev/py/asts/parents.py +28 -0
  113. omdev/py/asts/toplevel.py +123 -0
  114. omdev/py/asts/visitors.py +18 -0
  115. omdev/py/attrdocs.py +6 -7
  116. omdev/py/bracepy.py +12 -4
  117. omdev/py/docstrings/numpydoc.py +4 -4
  118. omdev/py/reprs.py +32 -0
  119. omdev/py/scripts/execstat.py +31 -26
  120. omdev/py/srcheaders.py +1 -1
  121. omdev/py/tokens/__init__.py +0 -0
  122. omdev/{tokens → py/tokens}/utils.py +2 -1
  123. omdev/py/tools/importscan.py +2 -2
  124. omdev/py/tools/mkrelimp.py +3 -4
  125. omdev/py/tools/pipdepup.py +686 -0
  126. omdev/pyproject/cli.py +1 -1
  127. omdev/pyproject/pkg.py +197 -48
  128. omdev/pyproject/reqs.py +36 -10
  129. omdev/pyproject/tools/__init__.py +0 -0
  130. omdev/pyproject/tools/aboutdeps.py +60 -0
  131. omdev/pyproject/venvs.py +12 -2
  132. omdev/rs/__init__.py +0 -0
  133. omdev/scripts/ci.py +9551 -6982
  134. omdev/scripts/interp.py +1323 -892
  135. omdev/scripts/lib/__init__.py +0 -0
  136. omdev/scripts/lib/inject.py +2086 -0
  137. omdev/scripts/lib/logs.py +2175 -0
  138. omdev/scripts/lib/marshal.py +1731 -0
  139. omdev/scripts/pyproject.py +4979 -1874
  140. omdev/tools/docker.py +19 -7
  141. omdev/tools/git/cli.py +56 -16
  142. omdev/tools/git/messages.py +2 -2
  143. omdev/tools/json/cli.py +6 -6
  144. omdev/tools/json/formats.py +2 -0
  145. omdev/tools/json/parsing.py +5 -5
  146. omdev/tools/json/processing.py +6 -3
  147. omdev/tools/json/rendering.py +2 -2
  148. omdev/tools/jsonview/cli.py +49 -65
  149. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  150. omdev/tools/pawk/README.md +195 -0
  151. omdev/tools/pawk/pawk.py +2 -2
  152. omdev/tools/pip.py +8 -0
  153. omdev/tui/__init__.py +0 -0
  154. omdev/tui/apps/__init__.py +0 -0
  155. omdev/tui/apps/edit/__init__.py +0 -0
  156. omdev/tui/apps/edit/main.py +167 -0
  157. omdev/tui/apps/irc/__init__.py +0 -0
  158. omdev/tui/apps/irc/__main__.py +4 -0
  159. omdev/tui/apps/irc/app.py +286 -0
  160. omdev/tui/apps/irc/client.py +187 -0
  161. omdev/tui/apps/irc/commands.py +175 -0
  162. omdev/tui/apps/irc/main.py +26 -0
  163. omdev/tui/apps/markdown/__init__.py +0 -0
  164. omdev/tui/apps/markdown/__main__.py +11 -0
  165. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  166. omdev/tui/rich/__init__.py +46 -0
  167. omdev/tui/rich/console2.py +20 -0
  168. omdev/tui/rich/markdown2.py +186 -0
  169. omdev/tui/textual/__init__.py +265 -0
  170. omdev/tui/textual/app2.py +16 -0
  171. omdev/tui/textual/autocomplete/LICENSE +21 -0
  172. omdev/tui/textual/autocomplete/__init__.py +33 -0
  173. omdev/tui/textual/autocomplete/matching.py +226 -0
  174. omdev/tui/textual/autocomplete/paths.py +202 -0
  175. omdev/tui/textual/autocomplete/widget.py +612 -0
  176. omdev/tui/textual/debug/__init__.py +10 -0
  177. omdev/tui/textual/debug/dominfo.py +151 -0
  178. omdev/tui/textual/debug/screen.py +24 -0
  179. omdev/tui/textual/devtools.py +187 -0
  180. omdev/tui/textual/drivers2.py +55 -0
  181. omdev/tui/textual/logging2.py +20 -0
  182. omdev/tui/textual/types.py +45 -0
  183. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/METADATA +18 -12
  184. omdev-0.0.0.dev500.dist-info/RECORD +386 -0
  185. omdev/ptk/__init__.py +0 -103
  186. omdev/ptk/apps/ncdu.py +0 -167
  187. omdev/ptk/confirm.py +0 -60
  188. omdev/ptk/markdown/LICENSE +0 -22
  189. omdev/ptk/markdown/__init__.py +0 -10
  190. omdev/ptk/markdown/__main__.py +0 -11
  191. omdev/ptk/markdown/border.py +0 -94
  192. omdev/ptk/markdown/markdown.py +0 -390
  193. omdev/ptk/markdown/parser.py +0 -42
  194. omdev/ptk/markdown/styles.py +0 -29
  195. omdev/ptk/markdown/tags.py +0 -299
  196. omdev/ptk/markdown/utils.py +0 -366
  197. omdev/pyproject/cexts.py +0 -110
  198. omdev/tools/antlr/__main__.py +0 -11
  199. omdev/tools/antlr/cli.py +0 -62
  200. omdev/tools/antlr/consts.py +0 -7
  201. omdev/tools/antlr/gen.py +0 -188
  202. omdev-0.0.0.dev416.dist-info/RECORD +0 -332
  203. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  204. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  205. /omdev/{tools/antlr → irc/numerics}/__init__.py +0 -0
  206. /omdev/{tokens → py/tokens}/all.py +0 -0
  207. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  208. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/WHEEL +0 -0
  209. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/entry_points.txt +0 -0
  210. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/licenses/LICENSE +0 -0
  211. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2086 @@
1
+ #!/usr/bin/env python3
2
+ # noinspection DuplicatedCode
3
+ # @omlish-lite
4
+ # @omlish-script
5
+ # @omlish-generated
6
+ # @omlish-amalg-output ../../../omlish/lite/inject.py
7
+ # @omlish-git-diff-omit
8
+ # ruff: noqa: UP006 UP007 UP036 UP043 UP045
9
+ import abc
10
+ import collections
11
+ import contextlib
12
+ import contextvars
13
+ import dataclasses as dc
14
+ import functools
15
+ import inspect
16
+ import operator
17
+ import sys
18
+ import threading
19
+ import types
20
+ import typing as ta
21
+ import weakref
22
+
23
+
24
+ ########################################
25
+
26
+
27
+ if sys.version_info < (3, 8):
28
+ raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
29
+
30
+
31
+ def __omlish_amalg__(): # noqa
32
+ return dict(
33
+ src_files=[
34
+ dict(path='abstract.py', sha1='a2fc3f3697fa8de5247761e9d554e70176f37aac'),
35
+ dict(path='check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
36
+ dict(path='reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
37
+ dict(path='maybes.py', sha1='bdf5136654ccd14b6a072588cad228925bdfbabd'),
38
+ dict(path='inject.py', sha1='6f097e3170019a34ff6834d36fcc9cbeed3a7ab4'),
39
+ ],
40
+ )
41
+
42
+
43
+ ########################################
44
+
45
+
46
+ # abstract.py
47
+ T = ta.TypeVar('T')
48
+
49
+ # check.py
50
+ SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
51
+ CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
52
+ CheckLateConfigureFn = ta.Callable[['Checks'], None] # ta.TypeAlias
53
+ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
54
+ CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
55
+ CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
56
+
57
+ # maybes.py
58
+ U = ta.TypeVar('U')
59
+
60
+ # inject.py
61
+ InjectorKeyCls = ta.Union[type, ta.NewType] # ta.TypeAlias
62
+ InjectorProviderFn = ta.Callable[['Injector'], ta.Any] # ta.TypeAlias
63
+ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn'] # ta.TypeAlias
64
+ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings'] # ta.TypeAlias
65
+
66
+
67
+ ########################################
68
+ # ../abstract.py
69
+
70
+
71
+ ##
72
+
73
+
74
+ _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
75
+ _IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
76
+
77
+
78
+ def is_abstract_method(obj: ta.Any) -> bool:
79
+ return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
80
+
81
+
82
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
83
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
84
+
85
+ # Stage 1: direct abstract methods
86
+
87
+ abstracts = {
88
+ a
89
+ # Get items as a list to avoid mutation issues during iteration
90
+ for a, v in list(cls.__dict__.items())
91
+ if is_abstract_method(v)
92
+ }
93
+
94
+ # Stage 2: inherited abstract methods
95
+
96
+ for base in cls.__bases__:
97
+ # Get __abstractmethods__ from base if it exists
98
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
99
+ continue
100
+
101
+ # Iterate over abstract methods in base
102
+ for key in base_abstracts:
103
+ # Check if this class has an attribute with this name
104
+ try:
105
+ value = getattr(cls, key)
106
+ except AttributeError:
107
+ # Attribute not found in this class, skip
108
+ continue
109
+
110
+ # Check if it's still abstract
111
+ if is_abstract_method(value):
112
+ abstracts.add(key)
113
+
114
+ return frozenset(abstracts)
115
+
116
+
117
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
118
+ if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
119
+ # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
120
+ # implementation (especially during testing), and we want to handle both cases.
121
+ return cls
122
+
123
+ abstracts = compute_abstract_methods(cls)
124
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
125
+ return cls
126
+
127
+
128
+ #
129
+
130
+
131
+ class AbstractTypeError(TypeError):
132
+ pass
133
+
134
+
135
+ _FORCE_ABSTRACT_ATTR = '__forceabstract__'
136
+
137
+
138
+ class Abstract:
139
+ """
140
+ Different from, but interoperable with, abc.ABC / abc.ABCMeta:
141
+
142
+ - This raises AbstractTypeError during class creation, not instance instantiation - unless Abstract or abc.ABC are
143
+ explicitly present in the class's direct bases.
144
+ - This will forbid instantiation of classes with Abstract in their direct bases even if there are no
145
+ abstractmethods left on the class.
146
+ - This is a mixin, not a metaclass.
147
+ - As it is not an ABCMeta, this does not support virtual base classes. As a result, operations like `isinstance`
148
+ and `issubclass` are ~7x faster.
149
+ - It additionally enforces a base class order of (Abstract, abc.ABC) to preemptively prevent common mro conflicts.
150
+
151
+ If not mixed-in with an ABCMeta, it will update __abstractmethods__ itself.
152
+ """
153
+
154
+ __slots__ = ()
155
+
156
+ __abstractmethods__: ta.ClassVar[ta.FrozenSet[str]] = frozenset()
157
+
158
+ #
159
+
160
+ def __forceabstract__(self):
161
+ raise TypeError
162
+
163
+ # This is done manually, rather than through @abc.abstractmethod, to mask it from static analysis.
164
+ setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
165
+
166
+ #
167
+
168
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
169
+ setattr(
170
+ cls,
171
+ _FORCE_ABSTRACT_ATTR,
172
+ getattr(Abstract, _FORCE_ABSTRACT_ATTR) if Abstract in cls.__bases__ else False,
173
+ )
174
+
175
+ super().__init_subclass__(**kwargs)
176
+
177
+ if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
178
+ if ams := compute_abstract_methods(cls):
179
+ amd = {
180
+ a: mcls
181
+ for mcls in cls.__mro__[::-1]
182
+ for a in ams
183
+ if a in mcls.__dict__
184
+ }
185
+
186
+ raise AbstractTypeError(
187
+ f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
188
+ ', '.join(sorted([
189
+ '.'.join([
190
+ *([
191
+ *([m] if (m := getattr(c, '__module__')) else []),
192
+ getattr(c, '__qualname__', getattr(c, '__name__')),
193
+ ] if c is not None else '?'),
194
+ a,
195
+ ])
196
+ for a in ams
197
+ for c in [amd.get(a)]
198
+ ])),
199
+ )
200
+
201
+ xbi = (Abstract, abc.ABC) # , ta.Generic ?
202
+ bis = [(cls.__bases__.index(b), b) for b in xbi if b in cls.__bases__]
203
+ if bis != sorted(bis):
204
+ raise TypeError(
205
+ f'Abstract subclass {cls.__name__} must have proper base class order of '
206
+ f'({", ".join(getattr(b, "__name__") for b in xbi)}), got: '
207
+ f'({", ".join(getattr(b, "__name__") for _, b in sorted(bis))})',
208
+ )
209
+
210
+ if not isinstance(cls, abc.ABCMeta):
211
+ update_abstracts(cls, force=True)
212
+
213
+
214
+ ########################################
215
+ # ../check.py
216
+ """
217
+ TODO:
218
+ - def maybe(v: lang.Maybe[T])
219
+ - def not_ ?
220
+ - ** class @dataclass Raise - user message should be able to be an exception type or instance or factory
221
+ """
222
+
223
+
224
+ ##
225
+
226
+
227
+ class Checks:
228
+ def __init__(self) -> None:
229
+ super().__init__()
230
+
231
+ self._config_lock = threading.RLock()
232
+ self._on_raise_fns: ta.Sequence[CheckOnRaiseFn] = []
233
+ self._exception_factory: CheckExceptionFactory = Checks.default_exception_factory
234
+ self._args_renderer: ta.Optional[CheckArgsRenderer] = None
235
+ self._late_configure_fns: ta.Sequence[CheckLateConfigureFn] = []
236
+
237
+ @staticmethod
238
+ def default_exception_factory(exc_cls: ta.Type[Exception], *args, **kwargs) -> Exception:
239
+ return exc_cls(*args, **kwargs) # noqa
240
+
241
+ #
242
+
243
+ def register_on_raise(self, fn: CheckOnRaiseFn) -> None:
244
+ with self._config_lock:
245
+ self._on_raise_fns = [*self._on_raise_fns, fn]
246
+
247
+ def unregister_on_raise(self, fn: CheckOnRaiseFn) -> None:
248
+ with self._config_lock:
249
+ self._on_raise_fns = [e for e in self._on_raise_fns if e != fn]
250
+
251
+ #
252
+
253
+ def register_on_raise_breakpoint_if_env_var_set(self, key: str) -> None:
254
+ import os
255
+
256
+ def on_raise(exc: Exception) -> None: # noqa
257
+ if key in os.environ:
258
+ breakpoint() # noqa
259
+
260
+ self.register_on_raise(on_raise)
261
+
262
+ #
263
+
264
+ def set_exception_factory(self, factory: CheckExceptionFactory) -> None:
265
+ self._exception_factory = factory
266
+
267
+ def set_args_renderer(self, renderer: ta.Optional[CheckArgsRenderer]) -> None:
268
+ self._args_renderer = renderer
269
+
270
+ #
271
+
272
+ def register_late_configure(self, fn: CheckLateConfigureFn) -> None:
273
+ with self._config_lock:
274
+ self._late_configure_fns = [*self._late_configure_fns, fn]
275
+
276
+ def _late_configure(self) -> None:
277
+ if not self._late_configure_fns:
278
+ return
279
+
280
+ with self._config_lock:
281
+ if not (lc := self._late_configure_fns):
282
+ return
283
+
284
+ for fn in lc:
285
+ fn(self)
286
+
287
+ self._late_configure_fns = []
288
+
289
+ #
290
+
291
+ class _ArgsKwargs:
292
+ def __init__(self, *args, **kwargs):
293
+ self.args = args
294
+ self.kwargs = kwargs
295
+
296
+ def _raise(
297
+ self,
298
+ exception_type: ta.Type[Exception],
299
+ default_message: str,
300
+ message: CheckMessage,
301
+ ak: _ArgsKwargs = _ArgsKwargs(),
302
+ *,
303
+ render_fmt: ta.Optional[str] = None,
304
+ ) -> ta.NoReturn:
305
+ exc_args = ()
306
+ if callable(message):
307
+ message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
308
+ if isinstance(message, tuple):
309
+ message, *exc_args = message # type: ignore
310
+
311
+ if message is None:
312
+ message = default_message
313
+
314
+ self._late_configure()
315
+
316
+ if render_fmt is not None and (af := self._args_renderer) is not None:
317
+ rendered_args = af(render_fmt, *ak.args)
318
+ if rendered_args is not None:
319
+ message = f'{message} : {rendered_args}'
320
+
321
+ exc = self._exception_factory(
322
+ exception_type,
323
+ message,
324
+ *exc_args,
325
+ *ak.args,
326
+ **ak.kwargs,
327
+ )
328
+
329
+ for fn in self._on_raise_fns:
330
+ fn(exc)
331
+
332
+ raise exc
333
+
334
+ #
335
+
336
+ def _unpack_isinstance_spec(self, spec: ta.Any) -> tuple:
337
+ if isinstance(spec, type):
338
+ return (spec,)
339
+ if not isinstance(spec, tuple):
340
+ spec = (spec,)
341
+ if None in spec:
342
+ spec = tuple(filter(None, spec)) + (None.__class__,) # noqa
343
+ if ta.Any in spec:
344
+ spec = (object,)
345
+ return spec
346
+
347
+ @ta.overload
348
+ def isinstance(self, v: ta.Any, spec: ta.Type[T], msg: CheckMessage = None) -> T:
349
+ ...
350
+
351
+ @ta.overload
352
+ def isinstance(self, v: ta.Any, spec: ta.Any, msg: CheckMessage = None) -> ta.Any:
353
+ ...
354
+
355
+ def isinstance(self, v, spec, msg=None):
356
+ if not isinstance(v, self._unpack_isinstance_spec(spec)):
357
+ self._raise(
358
+ TypeError,
359
+ 'Must be instance',
360
+ msg,
361
+ Checks._ArgsKwargs(v, spec),
362
+ render_fmt='not isinstance(%s, %s)',
363
+ )
364
+
365
+ return v
366
+
367
+ @ta.overload
368
+ def of_isinstance(self, spec: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[ta.Any], T]:
369
+ ...
370
+
371
+ @ta.overload
372
+ def of_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[ta.Any], ta.Any]:
373
+ ...
374
+
375
+ def of_isinstance(self, spec, msg=None):
376
+ def inner(v):
377
+ return self.isinstance(v, self._unpack_isinstance_spec(spec), msg)
378
+
379
+ return inner
380
+
381
+ def cast(self, v: ta.Any, cls: ta.Type[T], msg: CheckMessage = None) -> T:
382
+ if not isinstance(v, cls):
383
+ self._raise(
384
+ TypeError,
385
+ 'Must be instance',
386
+ msg,
387
+ Checks._ArgsKwargs(v, cls),
388
+ )
389
+
390
+ return v
391
+
392
+ def of_cast(self, cls: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[T], T]:
393
+ def inner(v):
394
+ return self.cast(v, cls, msg)
395
+
396
+ return inner
397
+
398
+ def not_isinstance(self, v: T, spec: ta.Any, msg: CheckMessage = None) -> T: # noqa
399
+ if isinstance(v, self._unpack_isinstance_spec(spec)):
400
+ self._raise(
401
+ TypeError,
402
+ 'Must not be instance',
403
+ msg,
404
+ Checks._ArgsKwargs(v, spec),
405
+ render_fmt='isinstance(%s, %s)',
406
+ )
407
+
408
+ return v
409
+
410
+ def of_not_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[T], T]:
411
+ def inner(v):
412
+ return self.not_isinstance(v, self._unpack_isinstance_spec(spec), msg)
413
+
414
+ return inner
415
+
416
+ ##
417
+
418
+ def issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
419
+ if not issubclass(v, spec):
420
+ self._raise(
421
+ TypeError,
422
+ 'Must be subclass',
423
+ msg,
424
+ Checks._ArgsKwargs(v, spec),
425
+ render_fmt='not issubclass(%s, %s)',
426
+ )
427
+
428
+ return v
429
+
430
+ def not_issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]:
431
+ if issubclass(v, spec):
432
+ self._raise(
433
+ TypeError,
434
+ 'Must not be subclass',
435
+ msg,
436
+ Checks._ArgsKwargs(v, spec),
437
+ render_fmt='issubclass(%s, %s)',
438
+ )
439
+
440
+ return v
441
+
442
+ #
443
+
444
+ def in_(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
445
+ if v not in c:
446
+ self._raise(
447
+ ValueError,
448
+ 'Must be in',
449
+ msg,
450
+ Checks._ArgsKwargs(v, c),
451
+ render_fmt='%s not in %s',
452
+ )
453
+
454
+ return v
455
+
456
+ def not_in(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
457
+ if v in c:
458
+ self._raise(
459
+ ValueError,
460
+ 'Must not be in',
461
+ msg,
462
+ Checks._ArgsKwargs(v, c),
463
+ render_fmt='%s in %s',
464
+ )
465
+
466
+ return v
467
+
468
+ def empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
469
+ if len(v) != 0:
470
+ self._raise(
471
+ ValueError,
472
+ 'Must be empty',
473
+ msg,
474
+ Checks._ArgsKwargs(v),
475
+ render_fmt='%s',
476
+ )
477
+
478
+ return v
479
+
480
+ def iterempty(self, v: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
481
+ it = iter(v)
482
+ try:
483
+ next(it)
484
+ except StopIteration:
485
+ pass
486
+ else:
487
+ self._raise(
488
+ ValueError,
489
+ 'Must be empty',
490
+ msg,
491
+ Checks._ArgsKwargs(v),
492
+ render_fmt='%s',
493
+ )
494
+
495
+ return v
496
+
497
+ def not_empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
498
+ if len(v) == 0:
499
+ self._raise(
500
+ ValueError,
501
+ 'Must not be empty',
502
+ msg,
503
+ Checks._ArgsKwargs(v),
504
+ render_fmt='%s',
505
+ )
506
+
507
+ return v
508
+
509
+ def unique(self, it: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
510
+ dupes = [e for e, c in collections.Counter(it).items() if c > 1]
511
+ if dupes:
512
+ self._raise(
513
+ ValueError,
514
+ 'Must be unique',
515
+ msg,
516
+ Checks._ArgsKwargs(it, dupes),
517
+ )
518
+
519
+ return it
520
+
521
+ def single(self, obj: ta.Iterable[T], msg: CheckMessage = None) -> T:
522
+ try:
523
+ [value] = obj
524
+ except ValueError:
525
+ self._raise(
526
+ ValueError,
527
+ 'Must be single',
528
+ msg,
529
+ Checks._ArgsKwargs(obj),
530
+ render_fmt='%s',
531
+ )
532
+
533
+ return value
534
+
535
+ def opt_single(self, obj: ta.Iterable[T], msg: CheckMessage = None) -> ta.Optional[T]:
536
+ it = iter(obj)
537
+ try:
538
+ value = next(it)
539
+ except StopIteration:
540
+ return None
541
+
542
+ try:
543
+ next(it)
544
+ except StopIteration:
545
+ return value # noqa
546
+
547
+ self._raise(
548
+ ValueError,
549
+ 'Must be empty or single',
550
+ msg,
551
+ Checks._ArgsKwargs(obj),
552
+ render_fmt='%s',
553
+ )
554
+
555
+ raise RuntimeError # noqa
556
+
557
+ #
558
+
559
+ def none(self, v: ta.Any, msg: CheckMessage = None) -> None:
560
+ if v is not None:
561
+ self._raise(
562
+ ValueError,
563
+ 'Must be None',
564
+ msg,
565
+ Checks._ArgsKwargs(v),
566
+ render_fmt='%s',
567
+ )
568
+
569
+ def not_none(self, v: ta.Optional[T], msg: CheckMessage = None) -> T:
570
+ if v is None:
571
+ self._raise(
572
+ ValueError,
573
+ 'Must not be None',
574
+ msg,
575
+ Checks._ArgsKwargs(v),
576
+ render_fmt='%s',
577
+ )
578
+
579
+ return v
580
+
581
+ #
582
+
583
+ def equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
584
+ if o != v:
585
+ self._raise(
586
+ ValueError,
587
+ 'Must be equal',
588
+ msg,
589
+ Checks._ArgsKwargs(v, o),
590
+ render_fmt='%s != %s',
591
+ )
592
+
593
+ return v
594
+
595
+ def not_equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
596
+ if o == v:
597
+ self._raise(
598
+ ValueError,
599
+ 'Must not be equal',
600
+ msg,
601
+ Checks._ArgsKwargs(v, o),
602
+ render_fmt='%s == %s',
603
+ )
604
+
605
+ return v
606
+
607
+ def is_(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
608
+ if o is not v:
609
+ self._raise(
610
+ ValueError,
611
+ 'Must be the same',
612
+ msg,
613
+ Checks._ArgsKwargs(v, o),
614
+ render_fmt='%s is not %s',
615
+ )
616
+
617
+ return v
618
+
619
+ def is_not(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
620
+ if o is v:
621
+ self._raise(
622
+ ValueError,
623
+ 'Must not be the same',
624
+ msg,
625
+ Checks._ArgsKwargs(v, o),
626
+ render_fmt='%s is %s',
627
+ )
628
+
629
+ return v
630
+
631
+ def callable(self, v: T, msg: CheckMessage = None) -> T: # noqa
632
+ if not callable(v):
633
+ self._raise(
634
+ TypeError,
635
+ 'Must be callable',
636
+ msg,
637
+ Checks._ArgsKwargs(v),
638
+ render_fmt='%s',
639
+ )
640
+
641
+ return v
642
+
643
+ def non_empty_str(self, v: ta.Optional[str], msg: CheckMessage = None) -> str:
644
+ if not isinstance(v, str) or not v:
645
+ self._raise(
646
+ ValueError,
647
+ 'Must be non-empty str',
648
+ msg,
649
+ Checks._ArgsKwargs(v),
650
+ render_fmt='%s',
651
+ )
652
+
653
+ return v
654
+
655
+ def replacing(self, expected: ta.Any, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
656
+ if old != expected:
657
+ self._raise(
658
+ ValueError,
659
+ 'Must be replacing',
660
+ msg,
661
+ Checks._ArgsKwargs(expected, old, new),
662
+ render_fmt='%s -> %s -> %s',
663
+ )
664
+
665
+ return new
666
+
667
+ def replacing_none(self, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
668
+ if old is not None:
669
+ self._raise(
670
+ ValueError,
671
+ 'Must be replacing None',
672
+ msg,
673
+ Checks._ArgsKwargs(old, new),
674
+ render_fmt='%s -> %s',
675
+ )
676
+
677
+ return new
678
+
679
+ #
680
+
681
+ def arg(self, v: bool, msg: CheckMessage = None) -> None:
682
+ if not v:
683
+ self._raise(
684
+ RuntimeError,
685
+ 'Argument condition not met',
686
+ msg,
687
+ Checks._ArgsKwargs(v),
688
+ render_fmt='%s',
689
+ )
690
+
691
+ def state(self, v: bool, msg: CheckMessage = None) -> None:
692
+ if not v:
693
+ self._raise(
694
+ RuntimeError,
695
+ 'State condition not met',
696
+ msg,
697
+ Checks._ArgsKwargs(v),
698
+ render_fmt='%s',
699
+ )
700
+
701
+
702
+ check = Checks()
703
+
704
+
705
+ ########################################
706
+ # ../reflect.py
707
+
708
+
709
+ ##
710
+
711
+
712
+ _GENERIC_ALIAS_TYPES = (
713
+ ta._GenericAlias, # type: ignore # noqa
714
+ *([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
715
+ )
716
+
717
+
718
+ def is_generic_alias(obj: ta.Any, *, origin: ta.Any = None) -> bool:
719
+ return (
720
+ isinstance(obj, _GENERIC_ALIAS_TYPES) and
721
+ (origin is None or ta.get_origin(obj) is origin)
722
+ )
723
+
724
+
725
+ is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
726
+
727
+
728
+ ##
729
+
730
+
731
+ _UNION_ALIAS_ORIGINS = frozenset([
732
+ ta.get_origin(ta.Optional[int]),
733
+ *(
734
+ [
735
+ ta.get_origin(int | None),
736
+ ta.get_origin(getattr(ta, 'TypeVar')('_T') | None),
737
+ ] if sys.version_info >= (3, 10) else ()
738
+ ),
739
+ ])
740
+
741
+
742
+ def is_union_alias(obj: ta.Any) -> bool:
743
+ return ta.get_origin(obj) in _UNION_ALIAS_ORIGINS
744
+
745
+
746
+ #
747
+
748
+
749
+ def is_optional_alias(spec: ta.Any) -> bool:
750
+ return (
751
+ is_union_alias(spec) and
752
+ len(ta.get_args(spec)) == 2 and
753
+ any(a in (None, type(None)) for a in ta.get_args(spec))
754
+ )
755
+
756
+
757
+ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
758
+ [it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
759
+ return it
760
+
761
+
762
+ ##
763
+
764
+
765
+ def is_new_type(spec: ta.Any) -> bool:
766
+ if isinstance(ta.NewType, type):
767
+ return isinstance(spec, ta.NewType)
768
+ else:
769
+ # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
770
+ return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
771
+
772
+
773
+ def get_new_type_supertype(spec: ta.Any) -> ta.Any:
774
+ return spec.__supertype__
775
+
776
+
777
+ ##
778
+
779
+
780
+ def is_literal_type(spec: ta.Any) -> bool:
781
+ if hasattr(ta, '_LiteralGenericAlias'):
782
+ return isinstance(spec, ta._LiteralGenericAlias) # noqa
783
+ else:
784
+ return (
785
+ isinstance(spec, ta._GenericAlias) and # type: ignore # noqa
786
+ spec.__origin__ is ta.Literal
787
+ )
788
+
789
+
790
+ def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
791
+ return spec.__args__
792
+
793
+
794
+ ########################################
795
+ # ../maybes.py
796
+
797
+
798
+ ##
799
+
800
+
801
+ @functools.total_ordering
802
+ class Maybe(ta.Generic[T]):
803
+ class ValueNotPresentError(BaseException):
804
+ pass
805
+
806
+ #
807
+
808
+ @property
809
+ @abc.abstractmethod
810
+ def present(self) -> bool:
811
+ raise NotImplementedError
812
+
813
+ @abc.abstractmethod
814
+ def must(self) -> T:
815
+ raise NotImplementedError
816
+
817
+ #
818
+
819
+ @abc.abstractmethod
820
+ def __repr__(self) -> str:
821
+ raise NotImplementedError
822
+
823
+ @abc.abstractmethod
824
+ def __hash__(self) -> int:
825
+ raise NotImplementedError
826
+
827
+ @abc.abstractmethod
828
+ def __eq__(self, other) -> bool:
829
+ raise NotImplementedError
830
+
831
+ @abc.abstractmethod
832
+ def __lt__(self, other) -> bool:
833
+ raise NotImplementedError
834
+
835
+ #
836
+
837
+ @ta.final
838
+ def __ne__(self, other):
839
+ return not (self == other)
840
+
841
+ @ta.final
842
+ def __iter__(self) -> ta.Iterator[T]:
843
+ if self.present:
844
+ yield self.must()
845
+
846
+ @ta.final
847
+ def __bool__(self) -> ta.NoReturn:
848
+ raise TypeError
849
+
850
+ #
851
+
852
+ @ta.final
853
+ def if_present(self, consumer: ta.Callable[[T], None]) -> None:
854
+ if self.present:
855
+ consumer(self.must())
856
+
857
+ @ta.final
858
+ def filter(self, predicate: ta.Callable[[T], bool]) -> 'Maybe[T]':
859
+ if self.present and predicate(self.must()):
860
+ return self
861
+ else:
862
+ return Maybe.empty()
863
+
864
+ @ta.final
865
+ def map(self, mapper: ta.Callable[[T], U]) -> 'Maybe[U]':
866
+ if self.present:
867
+ return Maybe.just(mapper(self.must()))
868
+ else:
869
+ return Maybe.empty()
870
+
871
+ @ta.final
872
+ def flat_map(self, mapper: ta.Callable[[T], 'Maybe[U]']) -> 'Maybe[U]':
873
+ if self.present:
874
+ if not isinstance(v := mapper(self.must()), Maybe):
875
+ raise TypeError(v)
876
+ return v
877
+ else:
878
+ return Maybe.empty()
879
+
880
+ @ta.final
881
+ def or_else(self, other: ta.Union[T, U]) -> ta.Union[T, U]:
882
+ if self.present:
883
+ return self.must()
884
+ else:
885
+ return other
886
+
887
+ @ta.final
888
+ def or_else_get(self, supplier: ta.Callable[[], ta.Union[T, U]]) -> ta.Union[T, U]:
889
+ if self.present:
890
+ return self.must()
891
+ else:
892
+ return supplier()
893
+
894
+ @ta.final
895
+ def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
896
+ if self.present:
897
+ return self.must()
898
+ else:
899
+ raise exception_supplier()
900
+
901
+ #
902
+
903
+ @classmethod
904
+ def of_optional(cls, v: ta.Optional[T]) -> 'Maybe[T]':
905
+ if v is not None:
906
+ return cls.just(v)
907
+ else:
908
+ return cls.empty()
909
+
910
+ @classmethod
911
+ def just(cls, v: T) -> 'Maybe[T]':
912
+ return _JustMaybe(v)
913
+
914
+ _empty: ta.ClassVar['Maybe']
915
+
916
+ @classmethod
917
+ def empty(cls) -> 'Maybe[T]':
918
+ return Maybe._empty
919
+
920
+
921
+ ##
922
+
923
+
924
+ class _Maybe(Maybe[T], Abstract):
925
+ def __lt__(self, other):
926
+ if not isinstance(other, _Maybe):
927
+ return NotImplemented
928
+ sp = self.present
929
+ op = other.present
930
+ if self.present and other.present:
931
+ return self.must() < other.must()
932
+ else:
933
+ return op and not sp
934
+
935
+
936
+ @ta.final
937
+ class _JustMaybe(_Maybe[T]):
938
+ __slots__ = ('_v', '_hash')
939
+
940
+ def __init__(self, v: T) -> None:
941
+ self._v = v
942
+
943
+ @property
944
+ def present(self) -> bool:
945
+ return True
946
+
947
+ def must(self) -> T:
948
+ return self._v
949
+
950
+ #
951
+
952
+ def __repr__(self) -> str:
953
+ return f'just({self._v!r})'
954
+
955
+ _hash: int
956
+
957
+ def __hash__(self) -> int:
958
+ try:
959
+ return self._hash
960
+ except AttributeError:
961
+ pass
962
+ h = self._hash = hash((_JustMaybe, self._v))
963
+ return h
964
+
965
+ def __eq__(self, other):
966
+ return (
967
+ self.__class__ is other.__class__ and
968
+ self._v == other._v # noqa
969
+ )
970
+
971
+
972
+ @ta.final
973
+ class _EmptyMaybe(_Maybe[T]):
974
+ __slots__ = ()
975
+
976
+ @property
977
+ def present(self) -> bool:
978
+ return False
979
+
980
+ def must(self) -> T:
981
+ raise Maybe.ValueNotPresentError
982
+
983
+ #
984
+
985
+ def __repr__(self) -> str:
986
+ return 'empty()'
987
+
988
+ def __hash__(self) -> int:
989
+ return hash(_EmptyMaybe)
990
+
991
+ def __eq__(self, other):
992
+ return self.__class__ is other.__class__
993
+
994
+
995
+ Maybe._empty = _EmptyMaybe() # noqa
996
+
997
+
998
+ ##
999
+
1000
+
1001
+ setattr(Maybe, 'just', _JustMaybe) # noqa
1002
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
1003
+
1004
+
1005
+ ########################################
1006
+ # inject.py
1007
+
1008
+
1009
+ ###
1010
+ # types
1011
+
1012
+
1013
+ @dc.dataclass(frozen=True)
1014
+ class InjectorKey(ta.Generic[T]):
1015
+ # Before PEP-560 typing.Generic was a metaclass with a __new__ that takes a 'cls' arg, so instantiating a dataclass
1016
+ # with kwargs (such as through dc.replace) causes `TypeError: __new__() got multiple values for argument 'cls'`.
1017
+ # See:
1018
+ # - https://github.com/python/cpython/commit/d911e40e788fb679723d78b6ea11cabf46caed5a
1019
+ # - https://gist.github.com/wrmsr/4468b86efe9f373b6b114bfe85b98fd3
1020
+ cls_: InjectorKeyCls
1021
+
1022
+ tag: ta.Any = None
1023
+ array: bool = False
1024
+
1025
+
1026
+ def is_valid_injector_key_cls(cls: ta.Any) -> bool:
1027
+ return isinstance(cls, type) or is_new_type(cls)
1028
+
1029
+
1030
+ def check_valid_injector_key_cls(cls: T) -> T:
1031
+ if not is_valid_injector_key_cls(cls):
1032
+ raise TypeError(cls)
1033
+ return cls
1034
+
1035
+
1036
+ ##
1037
+
1038
+
1039
+ class InjectorProvider(Abstract):
1040
+ @abc.abstractmethod
1041
+ def provider_fn(self) -> InjectorProviderFn:
1042
+ raise NotImplementedError
1043
+
1044
+
1045
+ ##
1046
+
1047
+
1048
+ @dc.dataclass(frozen=True)
1049
+ class InjectorBinding:
1050
+ key: InjectorKey
1051
+ provider: InjectorProvider
1052
+
1053
+ def __post_init__(self) -> None:
1054
+ check.isinstance(self.key, InjectorKey)
1055
+ check.isinstance(self.provider, InjectorProvider)
1056
+
1057
+
1058
+ class InjectorBindings(Abstract):
1059
+ @abc.abstractmethod
1060
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
1061
+ raise NotImplementedError
1062
+
1063
+ ##
1064
+
1065
+
1066
+ class Injector(Abstract):
1067
+ @abc.abstractmethod
1068
+ def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
1069
+ raise NotImplementedError
1070
+
1071
+ @abc.abstractmethod
1072
+ def provide(self, key: ta.Any) -> ta.Any:
1073
+ raise NotImplementedError
1074
+
1075
+ @abc.abstractmethod
1076
+ def provide_kwargs(
1077
+ self,
1078
+ obj: ta.Any,
1079
+ *,
1080
+ skip_args: int = 0,
1081
+ skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
1082
+ ) -> ta.Mapping[str, ta.Any]:
1083
+ raise NotImplementedError
1084
+
1085
+ @abc.abstractmethod
1086
+ def inject(
1087
+ self,
1088
+ obj: ta.Any,
1089
+ *,
1090
+ args: ta.Optional[ta.Sequence[ta.Any]] = None,
1091
+ kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None,
1092
+ ) -> ta.Any:
1093
+ raise NotImplementedError
1094
+
1095
+ def __getitem__(
1096
+ self,
1097
+ target: ta.Union[InjectorKey[T], ta.Type[T]],
1098
+ ) -> T:
1099
+ return self.provide(target)
1100
+
1101
+
1102
+ ###
1103
+ # exceptions
1104
+
1105
+
1106
+ class InjectorError(Exception):
1107
+ pass
1108
+
1109
+
1110
+ @dc.dataclass()
1111
+ class InjectorKeyError(InjectorError):
1112
+ key: InjectorKey
1113
+
1114
+ source: ta.Any = None
1115
+ name: ta.Optional[str] = None
1116
+
1117
+
1118
+ class UnboundInjectorKeyError(InjectorKeyError):
1119
+ pass
1120
+
1121
+
1122
+ class DuplicateInjectorKeyError(InjectorKeyError):
1123
+ pass
1124
+
1125
+
1126
+ class CyclicDependencyInjectorKeyError(InjectorKeyError):
1127
+ pass
1128
+
1129
+
1130
+ ###
1131
+ # keys
1132
+
1133
+
1134
+ def as_injector_key(o: ta.Any) -> InjectorKey:
1135
+ if o is inspect.Parameter.empty:
1136
+ raise TypeError(o)
1137
+ if isinstance(o, InjectorKey):
1138
+ return o
1139
+ if is_valid_injector_key_cls(o):
1140
+ return InjectorKey(o)
1141
+ raise TypeError(o)
1142
+
1143
+
1144
+ ###
1145
+ # providers
1146
+
1147
+
1148
+ @dc.dataclass(frozen=True)
1149
+ class FnInjectorProvider(InjectorProvider):
1150
+ fn: ta.Any
1151
+
1152
+ def __post_init__(self) -> None:
1153
+ check.not_isinstance(self.fn, type)
1154
+
1155
+ def provider_fn(self) -> InjectorProviderFn:
1156
+ def pfn(i: Injector) -> ta.Any:
1157
+ return i.inject(self.fn)
1158
+
1159
+ return pfn
1160
+
1161
+
1162
+ @dc.dataclass(frozen=True)
1163
+ class CtorInjectorProvider(InjectorProvider):
1164
+ cls_: type
1165
+
1166
+ def __post_init__(self) -> None:
1167
+ check.isinstance(self.cls_, type)
1168
+
1169
+ def provider_fn(self) -> InjectorProviderFn:
1170
+ def pfn(i: Injector) -> ta.Any:
1171
+ return i.inject(self.cls_)
1172
+
1173
+ return pfn
1174
+
1175
+
1176
+ @dc.dataclass(frozen=True)
1177
+ class ConstInjectorProvider(InjectorProvider):
1178
+ v: ta.Any
1179
+
1180
+ def provider_fn(self) -> InjectorProviderFn:
1181
+ return lambda _: self.v
1182
+
1183
+
1184
+ @dc.dataclass(frozen=True)
1185
+ class SingletonInjectorProvider(InjectorProvider):
1186
+ p: InjectorProvider
1187
+
1188
+ def __post_init__(self) -> None:
1189
+ check.isinstance(self.p, InjectorProvider)
1190
+
1191
+ def provider_fn(self) -> InjectorProviderFn:
1192
+ v = not_set = object()
1193
+
1194
+ def pfn(i: Injector) -> ta.Any:
1195
+ nonlocal v
1196
+ if v is not_set:
1197
+ v = ufn(i)
1198
+ return v
1199
+
1200
+ ufn = self.p.provider_fn()
1201
+ return pfn
1202
+
1203
+
1204
+ @dc.dataclass(frozen=True)
1205
+ class LinkInjectorProvider(InjectorProvider):
1206
+ k: InjectorKey
1207
+
1208
+ def __post_init__(self) -> None:
1209
+ check.isinstance(self.k, InjectorKey)
1210
+
1211
+ def provider_fn(self) -> InjectorProviderFn:
1212
+ def pfn(i: Injector) -> ta.Any:
1213
+ return i.provide(self.k)
1214
+
1215
+ return pfn
1216
+
1217
+
1218
+ @dc.dataclass(frozen=True)
1219
+ class ArrayInjectorProvider(InjectorProvider):
1220
+ ps: ta.Sequence[InjectorProvider]
1221
+
1222
+ def provider_fn(self) -> InjectorProviderFn:
1223
+ ps = [p.provider_fn() for p in self.ps]
1224
+
1225
+ def pfn(i: Injector) -> ta.Any:
1226
+ rv = []
1227
+ for ep in ps:
1228
+ o = ep(i)
1229
+ rv.append(o)
1230
+ return rv
1231
+
1232
+ return pfn
1233
+
1234
+
1235
+ ###
1236
+ # bindings
1237
+
1238
+
1239
+ @dc.dataclass(frozen=True)
1240
+ class _InjectorBindings(InjectorBindings):
1241
+ bs: ta.Optional[ta.Sequence[InjectorBinding]] = None
1242
+ ps: ta.Optional[ta.Sequence[InjectorBindings]] = None
1243
+
1244
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
1245
+ if self.bs is not None:
1246
+ yield from self.bs
1247
+ if self.ps is not None:
1248
+ for p in self.ps:
1249
+ yield from p.bindings()
1250
+
1251
+
1252
+ def as_injector_bindings(*args: InjectorBindingOrBindings) -> InjectorBindings:
1253
+ bs: ta.List[InjectorBinding] = []
1254
+ ps: ta.List[InjectorBindings] = []
1255
+
1256
+ for a in args:
1257
+ if isinstance(a, InjectorBindings):
1258
+ ps.append(a)
1259
+ elif isinstance(a, InjectorBinding):
1260
+ bs.append(a)
1261
+ else:
1262
+ raise TypeError(a)
1263
+
1264
+ return _InjectorBindings(
1265
+ bs or None,
1266
+ ps or None,
1267
+ )
1268
+
1269
+
1270
+ ##
1271
+
1272
+
1273
+ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
1274
+ pm: ta.Dict[InjectorKey, InjectorProvider] = {}
1275
+ am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
1276
+
1277
+ for b in bs.bindings():
1278
+ if b.key.array:
1279
+ al = am.setdefault(b.key, [])
1280
+ if isinstance(b.provider, ArrayInjectorProvider):
1281
+ al.extend(b.provider.ps)
1282
+ else:
1283
+ al.append(b.provider)
1284
+ else:
1285
+ if b.key in pm:
1286
+ raise KeyError(b.key)
1287
+ pm[b.key] = b.provider
1288
+
1289
+ if am:
1290
+ for k, aps in am.items():
1291
+ pm[k] = ArrayInjectorProvider(aps)
1292
+
1293
+ return pm
1294
+
1295
+
1296
+ ###
1297
+ # overrides
1298
+
1299
+
1300
+ @dc.dataclass(frozen=True)
1301
+ class OverridesInjectorBindings(InjectorBindings):
1302
+ p: InjectorBindings
1303
+ m: ta.Mapping[InjectorKey, InjectorBinding]
1304
+
1305
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
1306
+ for b in self.p.bindings():
1307
+ yield self.m.get(b.key, b)
1308
+
1309
+
1310
+ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
1311
+ m: ta.Dict[InjectorKey, InjectorBinding] = {}
1312
+
1313
+ for b in as_injector_bindings(*args).bindings():
1314
+ if b.key in m:
1315
+ raise DuplicateInjectorKeyError(b.key)
1316
+ m[b.key] = b
1317
+
1318
+ return OverridesInjectorBindings(p, m)
1319
+
1320
+
1321
+ ###
1322
+ # scopes
1323
+
1324
+
1325
+ class InjectorScope(Abstract):
1326
+ def __init__(
1327
+ self,
1328
+ *,
1329
+ _i: Injector,
1330
+ ) -> None:
1331
+ super().__init__()
1332
+
1333
+ self._i = _i
1334
+
1335
+ all_seeds: ta.Iterable[_InjectorScopeSeed] = self._i.provide(InjectorKey(_InjectorScopeSeed, array=True))
1336
+ self._sks = {s.k for s in all_seeds if s.sc is type(self)}
1337
+
1338
+ #
1339
+
1340
+ @dc.dataclass(frozen=True)
1341
+ class State:
1342
+ seeds: ta.Dict[InjectorKey, ta.Any]
1343
+ provisions: ta.Dict[InjectorKey, ta.Any] = dc.field(default_factory=dict)
1344
+
1345
+ def new_state(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> State:
1346
+ vs = dict(vs)
1347
+ check.equal(set(vs.keys()), self._sks)
1348
+ return InjectorScope.State(vs)
1349
+
1350
+ #
1351
+
1352
+ @abc.abstractmethod
1353
+ def state(self) -> State:
1354
+ raise NotImplementedError
1355
+
1356
+ @abc.abstractmethod
1357
+ def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.ContextManager[None]:
1358
+ raise NotImplementedError
1359
+
1360
+
1361
+ class ExclusiveInjectorScope(InjectorScope, Abstract):
1362
+ _st: ta.Optional[InjectorScope.State] = None
1363
+
1364
+ def state(self) -> InjectorScope.State:
1365
+ return check.not_none(self._st)
1366
+
1367
+ @contextlib.contextmanager
1368
+ def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.Iterator[None]:
1369
+ check.none(self._st)
1370
+ self._st = self.new_state(vs)
1371
+ try:
1372
+ yield
1373
+ finally:
1374
+ self._st = None
1375
+
1376
+
1377
+ class ContextvarInjectorScope(InjectorScope, Abstract):
1378
+ _cv: contextvars.ContextVar
1379
+
1380
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
1381
+ super().__init_subclass__(**kwargs)
1382
+
1383
+ check.not_in(Abstract, cls.__bases__)
1384
+ check.not_in(abc.ABC, cls.__bases__)
1385
+ check.state(not hasattr(cls, '_cv'))
1386
+ cls._cv = contextvars.ContextVar(f'{cls.__name__}_cv')
1387
+
1388
+ def state(self) -> InjectorScope.State:
1389
+ return self._cv.get()
1390
+
1391
+ @contextlib.contextmanager
1392
+ def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.Iterator[None]:
1393
+ try:
1394
+ self._cv.get()
1395
+ except LookupError:
1396
+ pass
1397
+ else:
1398
+ raise RuntimeError(f'Scope already entered: {self}')
1399
+ st = self.new_state(vs)
1400
+ tok = self._cv.set(st)
1401
+ try:
1402
+ yield
1403
+ finally:
1404
+ self._cv.reset(tok)
1405
+
1406
+
1407
+ #
1408
+
1409
+
1410
+ @dc.dataclass(frozen=True)
1411
+ class ScopedInjectorProvider(InjectorProvider):
1412
+ p: InjectorProvider
1413
+ k: InjectorKey
1414
+ sc: ta.Type[InjectorScope]
1415
+
1416
+ def __post_init__(self) -> None:
1417
+ check.isinstance(self.p, InjectorProvider)
1418
+ check.isinstance(self.k, InjectorKey)
1419
+ check.issubclass(self.sc, InjectorScope)
1420
+
1421
+ def provider_fn(self) -> InjectorProviderFn:
1422
+ def pfn(i: Injector) -> ta.Any:
1423
+ st = i[self.sc].state()
1424
+ try:
1425
+ return st.provisions[self.k]
1426
+ except KeyError:
1427
+ pass
1428
+ v = ufn(i)
1429
+ st.provisions[self.k] = v
1430
+ return v
1431
+
1432
+ ufn = self.p.provider_fn()
1433
+ return pfn
1434
+
1435
+
1436
+ @dc.dataclass(frozen=True)
1437
+ class _ScopeSeedInjectorProvider(InjectorProvider):
1438
+ k: InjectorKey
1439
+ sc: ta.Type[InjectorScope]
1440
+
1441
+ def __post_init__(self) -> None:
1442
+ check.isinstance(self.k, InjectorKey)
1443
+ check.issubclass(self.sc, InjectorScope)
1444
+
1445
+ def provider_fn(self) -> InjectorProviderFn:
1446
+ def pfn(i: Injector) -> ta.Any:
1447
+ st = i[self.sc].state()
1448
+ return st.seeds[self.k]
1449
+ return pfn
1450
+
1451
+
1452
+ def bind_injector_scope(sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
1453
+ return InjectorBinder.bind(sc, singleton=True)
1454
+
1455
+
1456
+ #
1457
+
1458
+
1459
+ @dc.dataclass(frozen=True)
1460
+ class _InjectorScopeSeed:
1461
+ sc: ta.Type['InjectorScope']
1462
+ k: InjectorKey
1463
+
1464
+ def __post_init__(self) -> None:
1465
+ check.issubclass(self.sc, InjectorScope)
1466
+ check.isinstance(self.k, InjectorKey)
1467
+
1468
+
1469
+ def bind_injector_scope_seed(k: ta.Any, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
1470
+ kk = as_injector_key(k)
1471
+ return as_injector_bindings(
1472
+ InjectorBinding(kk, _ScopeSeedInjectorProvider(kk, sc)),
1473
+ InjectorBinder.bind(_InjectorScopeSeed(sc, kk), array=True),
1474
+ )
1475
+
1476
+
1477
+ ###
1478
+ # inspection
1479
+
1480
+
1481
+ class _InjectionInspection(ta.NamedTuple):
1482
+ signature: inspect.Signature
1483
+ type_hints: ta.Mapping[str, ta.Any]
1484
+ args_offset: int
1485
+
1486
+
1487
+ _INJECTION_INSPECTION_CACHE: ta.MutableMapping[ta.Any, _InjectionInspection] = weakref.WeakKeyDictionary()
1488
+
1489
+
1490
+ def _do_injection_inspect(obj: ta.Any) -> _InjectionInspection:
1491
+ tgt = obj
1492
+
1493
+ # inspect.signature(eval_str=True) was added in 3.10 and we have to support 3.8, so we have to get_type_hints to
1494
+ # eval str annotations *in addition to* getting the signature for parameter information.
1495
+ uw = tgt
1496
+ has_partial = False
1497
+ while True:
1498
+ if isinstance(uw, functools.partial):
1499
+ uw = uw.func
1500
+ has_partial = True
1501
+ else:
1502
+ if (uw2 := inspect.unwrap(uw)) is uw:
1503
+ break
1504
+ uw = uw2
1505
+
1506
+ has_args_offset = False
1507
+
1508
+ if isinstance(tgt, type) and tgt.__new__ is not object.__new__:
1509
+ # Python 3.8's inspect.signature can't handle subclasses overriding __new__, always generating *args/**kwargs.
1510
+ # - https://bugs.python.org/issue40897
1511
+ # - https://github.com/python/cpython/commit/df7c62980d15acd3125dfbd81546dad359f7add7
1512
+ tgt = tgt.__init__ # type: ignore[misc]
1513
+ has_args_offset = True
1514
+
1515
+ if tgt in (object.__init__, object.__new__):
1516
+ # inspect strips self for types but not the underlying methods.
1517
+ def dummy(self):
1518
+ pass
1519
+ tgt = dummy
1520
+ has_args_offset = True
1521
+
1522
+ if has_partial and has_args_offset:
1523
+ # TODO: unwrap partials masking parameters like modern python
1524
+ raise InjectorError(
1525
+ 'Injector inspection does not currently support both an args offset and a functools.partial: '
1526
+ f'{obj}',
1527
+ )
1528
+
1529
+ return _InjectionInspection(
1530
+ inspect.signature(tgt),
1531
+ ta.get_type_hints(uw),
1532
+ 1 if has_args_offset else 0,
1533
+ )
1534
+
1535
+
1536
+ def _injection_inspect(obj: ta.Any) -> _InjectionInspection:
1537
+ try:
1538
+ return _INJECTION_INSPECTION_CACHE[obj]
1539
+ except TypeError:
1540
+ return _do_injection_inspect(obj)
1541
+ except KeyError:
1542
+ pass
1543
+ insp = _do_injection_inspect(obj)
1544
+ _INJECTION_INSPECTION_CACHE[obj] = insp
1545
+ return insp
1546
+
1547
+
1548
+ class InjectionKwarg(ta.NamedTuple):
1549
+ name: str
1550
+ key: InjectorKey
1551
+ has_default: bool
1552
+
1553
+
1554
+ class InjectionKwargsTarget(ta.NamedTuple):
1555
+ obj: ta.Any
1556
+ kwargs: ta.Sequence[InjectionKwarg]
1557
+
1558
+
1559
+ def build_injection_kwargs_target(
1560
+ obj: ta.Any,
1561
+ *,
1562
+ skip_args: int = 0,
1563
+ skip_kwargs: ta.Optional[ta.Iterable[str]] = None,
1564
+ raw_optional: bool = False,
1565
+ ) -> InjectionKwargsTarget:
1566
+ insp = _injection_inspect(obj)
1567
+
1568
+ params = list(insp.signature.parameters.values())
1569
+
1570
+ skip_names: ta.Set[str] = set()
1571
+ if skip_kwargs is not None:
1572
+ skip_names.update(check.not_isinstance(skip_kwargs, str))
1573
+
1574
+ seen: ta.Set[InjectorKey] = set()
1575
+ kws: ta.List[InjectionKwarg] = []
1576
+ for p in params[insp.args_offset + skip_args:]:
1577
+ if p.name in skip_names:
1578
+ continue
1579
+
1580
+ if p.annotation is inspect.Signature.empty:
1581
+ if p.default is not inspect.Parameter.empty:
1582
+ raise KeyError(f'{obj}, {p.name}')
1583
+ continue
1584
+
1585
+ if p.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY):
1586
+ raise TypeError(insp)
1587
+
1588
+ # 3.8 inspect.signature doesn't eval_str but typing.get_type_hints does, so prefer that.
1589
+ ann = insp.type_hints.get(p.name, p.annotation)
1590
+ if (
1591
+ not raw_optional and
1592
+ is_optional_alias(ann)
1593
+ ):
1594
+ ann = get_optional_alias_arg(ann)
1595
+
1596
+ k = as_injector_key(ann)
1597
+
1598
+ if k in seen:
1599
+ raise DuplicateInjectorKeyError(k)
1600
+ seen.add(k)
1601
+
1602
+ kws.append(InjectionKwarg(
1603
+ p.name,
1604
+ k,
1605
+ p.default is not inspect.Parameter.empty,
1606
+ ))
1607
+
1608
+ return InjectionKwargsTarget(
1609
+ obj,
1610
+ kws,
1611
+ )
1612
+
1613
+
1614
+ ###
1615
+ # injector
1616
+
1617
+
1618
+ _INJECTOR_INJECTOR_KEY: InjectorKey[Injector] = InjectorKey(Injector)
1619
+
1620
+
1621
+ @dc.dataclass(frozen=True)
1622
+ class _InjectorEager:
1623
+ key: InjectorKey
1624
+
1625
+
1626
+ _INJECTOR_EAGER_ARRAY_KEY: InjectorKey[_InjectorEager] = InjectorKey(_InjectorEager, array=True)
1627
+
1628
+
1629
+ class _Injector(Injector):
1630
+ _DEFAULT_BINDINGS: ta.ClassVar[ta.List[InjectorBinding]] = []
1631
+
1632
+ def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
1633
+ super().__init__()
1634
+
1635
+ self._bs = check.isinstance(bs, InjectorBindings)
1636
+ self._p: ta.Optional[Injector] = check.isinstance(p, (Injector, type(None)))
1637
+
1638
+ self._pfm = {
1639
+ k: v.provider_fn()
1640
+ for k, v in build_injector_provider_map(as_injector_bindings(
1641
+ *self._DEFAULT_BINDINGS,
1642
+ bs,
1643
+ )).items()
1644
+ }
1645
+
1646
+ if _INJECTOR_INJECTOR_KEY in self._pfm:
1647
+ raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
1648
+
1649
+ self.__cur_req: ta.Optional[_Injector._Request] = None
1650
+
1651
+ if _INJECTOR_EAGER_ARRAY_KEY in self._pfm:
1652
+ for e in self.provide(_INJECTOR_EAGER_ARRAY_KEY):
1653
+ self.provide(e.key)
1654
+
1655
+ class _Request:
1656
+ def __init__(self, injector: '_Injector') -> None:
1657
+ super().__init__()
1658
+
1659
+ self._injector = injector
1660
+ self._provisions: ta.Dict[InjectorKey, Maybe] = {}
1661
+ self._seen_keys: ta.Set[InjectorKey] = set()
1662
+
1663
+ def handle_key(self, key: InjectorKey) -> Maybe[Maybe]:
1664
+ try:
1665
+ return Maybe.just(self._provisions[key])
1666
+ except KeyError:
1667
+ pass
1668
+ if key in self._seen_keys:
1669
+ raise CyclicDependencyInjectorKeyError(key)
1670
+ self._seen_keys.add(key)
1671
+ return Maybe.empty()
1672
+
1673
+ def handle_provision(self, key: InjectorKey, mv: Maybe) -> Maybe:
1674
+ check.in_(key, self._seen_keys)
1675
+ check.not_in(key, self._provisions)
1676
+ self._provisions[key] = mv
1677
+ return mv
1678
+
1679
+ @contextlib.contextmanager
1680
+ def _current_request(self) -> ta.Generator[_Request, None, None]:
1681
+ if (cr := self.__cur_req) is not None:
1682
+ yield cr
1683
+ return
1684
+
1685
+ cr = self._Request(self)
1686
+ try:
1687
+ self.__cur_req = cr
1688
+ yield cr
1689
+ finally:
1690
+ self.__cur_req = None
1691
+
1692
+ def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
1693
+ key = as_injector_key(key)
1694
+
1695
+ cr: _Injector._Request
1696
+ with self._current_request() as cr:
1697
+ if (rv := cr.handle_key(key)).present:
1698
+ return rv.must()
1699
+
1700
+ if key == _INJECTOR_INJECTOR_KEY:
1701
+ return cr.handle_provision(key, Maybe.just(self))
1702
+
1703
+ fn = self._pfm.get(key)
1704
+ if fn is not None:
1705
+ return cr.handle_provision(key, Maybe.just(fn(self)))
1706
+
1707
+ if self._p is not None:
1708
+ pv = self._p.try_provide(key)
1709
+ if pv is not None:
1710
+ return cr.handle_provision(key, Maybe.empty())
1711
+
1712
+ return cr.handle_provision(key, Maybe.empty())
1713
+
1714
+ def provide(self, key: ta.Any) -> ta.Any:
1715
+ v = self.try_provide(key)
1716
+ if v.present:
1717
+ return v.must()
1718
+ raise UnboundInjectorKeyError(key)
1719
+
1720
+ def provide_kwargs(
1721
+ self,
1722
+ obj: ta.Any,
1723
+ *,
1724
+ skip_args: int = 0,
1725
+ skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
1726
+ ) -> ta.Mapping[str, ta.Any]:
1727
+ kt = build_injection_kwargs_target(
1728
+ obj,
1729
+ skip_args=skip_args,
1730
+ skip_kwargs=skip_kwargs,
1731
+ )
1732
+
1733
+ ret: ta.Dict[str, ta.Any] = {}
1734
+ for kw in kt.kwargs:
1735
+ if kw.has_default:
1736
+ if not (mv := self.try_provide(kw.key)).present:
1737
+ continue
1738
+ v = mv.must()
1739
+ else:
1740
+ v = self.provide(kw.key)
1741
+ ret[kw.name] = v
1742
+ return ret
1743
+
1744
+ def inject(
1745
+ self,
1746
+ obj: ta.Any,
1747
+ *,
1748
+ args: ta.Optional[ta.Sequence[ta.Any]] = None,
1749
+ kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None,
1750
+ ) -> ta.Any:
1751
+ provided = self.provide_kwargs(
1752
+ obj,
1753
+ skip_args=len(args) if args is not None else 0,
1754
+ skip_kwargs=kwargs if kwargs is not None else None,
1755
+ )
1756
+
1757
+ return obj(
1758
+ *(args if args is not None else ()),
1759
+ **(kwargs if kwargs is not None else {}),
1760
+ **provided,
1761
+ )
1762
+
1763
+
1764
+ ###
1765
+ # binder
1766
+
1767
+
1768
+ class InjectorBinder:
1769
+ def __new__(cls, *args, **kwargs): # noqa
1770
+ raise TypeError
1771
+
1772
+ _FN_TYPES: ta.ClassVar[ta.Tuple[type, ...]] = (
1773
+ types.FunctionType,
1774
+ types.MethodType,
1775
+
1776
+ classmethod,
1777
+ staticmethod,
1778
+
1779
+ functools.partial,
1780
+ functools.partialmethod,
1781
+ )
1782
+
1783
+ @classmethod
1784
+ def _is_fn(cls, obj: ta.Any) -> bool:
1785
+ return isinstance(obj, cls._FN_TYPES)
1786
+
1787
+ @classmethod
1788
+ def bind_as_fn(cls, icls: ta.Type[T]) -> ta.Type[T]:
1789
+ check.isinstance(icls, type)
1790
+ if icls not in cls._FN_TYPES:
1791
+ cls._FN_TYPES = (*cls._FN_TYPES, icls)
1792
+ return icls
1793
+
1794
+ _BANNED_BIND_TYPES: ta.ClassVar[ta.Tuple[type, ...]] = (
1795
+ InjectorProvider,
1796
+ )
1797
+
1798
+ @classmethod
1799
+ def bind(
1800
+ cls,
1801
+ obj: ta.Any,
1802
+ *,
1803
+ key: ta.Any = None,
1804
+ tag: ta.Any = None,
1805
+ array: ta.Optional[bool] = None, # noqa
1806
+
1807
+ to_fn: ta.Any = None,
1808
+ to_ctor: ta.Any = None,
1809
+ to_const: ta.Any = None,
1810
+ to_key: ta.Any = None,
1811
+
1812
+ in_: ta.Optional[ta.Type[InjectorScope]] = None,
1813
+ singleton: bool = False,
1814
+
1815
+ eager: bool = False,
1816
+ ) -> InjectorBindingOrBindings:
1817
+ if obj is None or obj is inspect.Parameter.empty:
1818
+ raise TypeError(obj)
1819
+ if isinstance(obj, cls._BANNED_BIND_TYPES):
1820
+ raise TypeError(obj)
1821
+
1822
+ #
1823
+
1824
+ if key is not None:
1825
+ key = as_injector_key(key)
1826
+
1827
+ #
1828
+
1829
+ has_to = (
1830
+ to_fn is not None or
1831
+ to_ctor is not None or
1832
+ to_const is not None or
1833
+ to_key is not None
1834
+ )
1835
+ if isinstance(obj, InjectorKey):
1836
+ if key is None:
1837
+ key = obj
1838
+ elif isinstance(obj, type):
1839
+ if not has_to:
1840
+ to_ctor = obj
1841
+ if key is None:
1842
+ key = InjectorKey(obj)
1843
+ elif cls._is_fn(obj) and not has_to:
1844
+ to_fn = obj
1845
+ if key is None:
1846
+ insp = _injection_inspect(obj)
1847
+ key_cls: ta.Any = check_valid_injector_key_cls(check.not_none(insp.type_hints.get('return')))
1848
+ key = InjectorKey(key_cls)
1849
+ else:
1850
+ if to_const is not None:
1851
+ raise TypeError('Cannot bind instance with to_const')
1852
+ to_const = obj
1853
+ if key is None:
1854
+ key = InjectorKey(type(obj))
1855
+ del has_to
1856
+
1857
+ #
1858
+
1859
+ if tag is not None:
1860
+ if key.tag is not None:
1861
+ raise TypeError('Tag already set')
1862
+ key = dc.replace(key, tag=tag)
1863
+
1864
+ if array is not None:
1865
+ key = dc.replace(key, array=array)
1866
+
1867
+ #
1868
+
1869
+ providers: ta.List[InjectorProvider] = []
1870
+ if to_fn is not None:
1871
+ providers.append(FnInjectorProvider(to_fn))
1872
+ if to_ctor is not None:
1873
+ providers.append(CtorInjectorProvider(to_ctor))
1874
+ if to_const is not None:
1875
+ providers.append(ConstInjectorProvider(to_const))
1876
+ if to_key is not None:
1877
+ providers.append(LinkInjectorProvider(as_injector_key(to_key)))
1878
+ if not providers:
1879
+ raise TypeError('Must specify provider')
1880
+ if len(providers) > 1:
1881
+ raise TypeError('May not specify multiple providers')
1882
+ provider = check.single(providers)
1883
+
1884
+ #
1885
+
1886
+ pws: ta.List[ta.Any] = []
1887
+ if in_ is not None:
1888
+ check.issubclass(in_, InjectorScope)
1889
+ check.not_in(Abstract, in_.__bases__)
1890
+ pws.append(functools.partial(ScopedInjectorProvider, k=key, sc=in_))
1891
+ if singleton:
1892
+ pws.append(SingletonInjectorProvider)
1893
+ if len(pws) > 1:
1894
+ raise TypeError('May not specify multiple provider wrappers')
1895
+ elif pws:
1896
+ provider = check.single(pws)(provider)
1897
+
1898
+ #
1899
+
1900
+ binding = InjectorBinding(key, provider)
1901
+
1902
+ #
1903
+
1904
+ extras: ta.List[InjectorBinding] = []
1905
+
1906
+ if eager:
1907
+ extras.append(bind_injector_eager_key(key))
1908
+
1909
+ #
1910
+
1911
+ if extras:
1912
+ return as_injector_bindings(binding, *extras)
1913
+ else:
1914
+ return binding
1915
+
1916
+
1917
+ ###
1918
+ # injection helpers
1919
+
1920
+
1921
+ def make_injector_factory(
1922
+ fn: ta.Callable[..., T],
1923
+ cls: U,
1924
+ ann: ta.Any = None,
1925
+ ) -> ta.Callable[..., U]:
1926
+ if ann is None:
1927
+ ann = cls
1928
+
1929
+ def outer(injector: Injector) -> ann:
1930
+ def inner(*args, **kwargs):
1931
+ return injector.inject(fn, args=args, kwargs=kwargs)
1932
+ return cls(inner) # type: ignore
1933
+
1934
+ return outer
1935
+
1936
+
1937
+ def bind_injector_array(
1938
+ obj: ta.Any = None,
1939
+ *,
1940
+ tag: ta.Any = None,
1941
+ ) -> InjectorBindingOrBindings:
1942
+ key = as_injector_key(obj)
1943
+ if tag is not None:
1944
+ if key.tag is not None:
1945
+ raise ValueError('Must not specify multiple tags')
1946
+ key = dc.replace(key, tag=tag)
1947
+
1948
+ if key.array:
1949
+ raise ValueError('Key must not be array')
1950
+
1951
+ return InjectorBinding(
1952
+ dc.replace(key, array=True),
1953
+ ArrayInjectorProvider([]),
1954
+ )
1955
+
1956
+
1957
+ def make_injector_array_type(
1958
+ ele: ta.Union[InjectorKey, InjectorKeyCls],
1959
+ cls: U,
1960
+ ann: ta.Any = None,
1961
+ ) -> ta.Callable[..., U]:
1962
+ if isinstance(ele, InjectorKey):
1963
+ if not ele.array:
1964
+ raise InjectorError('Provided key must be array', ele)
1965
+ key = ele
1966
+ else:
1967
+ key = dc.replace(as_injector_key(ele), array=True)
1968
+
1969
+ if ann is None:
1970
+ ann = cls
1971
+
1972
+ def inner(injector: Injector) -> ann:
1973
+ return cls(injector.provide(key)) # type: ignore[operator]
1974
+
1975
+ return inner
1976
+
1977
+
1978
+ def bind_injector_eager_key(key: ta.Any) -> InjectorBinding:
1979
+ return InjectorBinding(_INJECTOR_EAGER_ARRAY_KEY, ConstInjectorProvider(_InjectorEager(as_injector_key(key))))
1980
+
1981
+
1982
+ ###
1983
+ # api
1984
+
1985
+
1986
+ class InjectionApi:
1987
+ # keys
1988
+
1989
+ def as_key(self, o: ta.Any) -> InjectorKey:
1990
+ return as_injector_key(o)
1991
+
1992
+ def array(self, o: ta.Any) -> InjectorKey:
1993
+ return dc.replace(as_injector_key(o), array=True)
1994
+
1995
+ def tag(self, o: ta.Any, t: ta.Any) -> InjectorKey:
1996
+ return dc.replace(as_injector_key(o), tag=t)
1997
+
1998
+ # bindings
1999
+
2000
+ def as_bindings(self, *args: InjectorBindingOrBindings) -> InjectorBindings:
2001
+ return as_injector_bindings(*args)
2002
+
2003
+ # overrides
2004
+
2005
+ def override(self, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
2006
+ return injector_override(p, *args)
2007
+
2008
+ # scopes
2009
+
2010
+ def bind_scope(self, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
2011
+ return bind_injector_scope(sc)
2012
+
2013
+ def bind_scope_seed(self, k: ta.Any, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
2014
+ return bind_injector_scope_seed(k, sc)
2015
+
2016
+ # injector
2017
+
2018
+ def create_injector(self, *args: InjectorBindingOrBindings, parent: ta.Optional[Injector] = None) -> Injector:
2019
+ return _Injector(as_injector_bindings(*args), parent)
2020
+
2021
+ # binder
2022
+
2023
+ def bind(
2024
+ self,
2025
+ obj: ta.Any,
2026
+ *,
2027
+ key: ta.Any = None,
2028
+ tag: ta.Any = None,
2029
+ array: ta.Optional[bool] = None, # noqa
2030
+
2031
+ to_fn: ta.Any = None,
2032
+ to_ctor: ta.Any = None,
2033
+ to_const: ta.Any = None,
2034
+ to_key: ta.Any = None,
2035
+
2036
+ in_: ta.Optional[ta.Type[InjectorScope]] = None,
2037
+ singleton: bool = False,
2038
+
2039
+ eager: bool = False,
2040
+ ) -> InjectorBindingOrBindings:
2041
+ return InjectorBinder.bind(
2042
+ obj,
2043
+
2044
+ key=key,
2045
+ tag=tag,
2046
+ array=array,
2047
+
2048
+ to_fn=to_fn,
2049
+ to_ctor=to_ctor,
2050
+ to_const=to_const,
2051
+ to_key=to_key,
2052
+
2053
+ in_=in_,
2054
+ singleton=singleton,
2055
+
2056
+ eager=eager,
2057
+ )
2058
+
2059
+ # helpers
2060
+
2061
+ def bind_factory(
2062
+ self,
2063
+ fn: ta.Callable[..., T],
2064
+ cls_: U,
2065
+ ann: ta.Any = None,
2066
+ ) -> InjectorBindingOrBindings:
2067
+ return self.bind(make_injector_factory(fn, cls_, ann))
2068
+
2069
+ def bind_array(
2070
+ self,
2071
+ obj: ta.Any = None,
2072
+ *,
2073
+ tag: ta.Any = None,
2074
+ ) -> InjectorBindingOrBindings:
2075
+ return bind_injector_array(obj, tag=tag)
2076
+
2077
+ def bind_array_type(
2078
+ self,
2079
+ ele: ta.Union[InjectorKey, InjectorKeyCls],
2080
+ cls_: U,
2081
+ ann: ta.Any = None,
2082
+ ) -> InjectorBindingOrBindings:
2083
+ return self.bind(make_injector_array_type(ele, cls_, ann))
2084
+
2085
+
2086
+ inj = InjectionApi()