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