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.
@@ -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()