omlish 0.0.0.dev120__py3-none-any.whl → 0.0.0.dev122__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev120'
2
- __revision__ = 'aada6beb1d685cb095429e9c588d266b8a83aa23'
1
+ __version__ = '0.0.0.dev122'
2
+ __revision__ = 'f29d374baac7fe7644958440b50d3ba934c14595'
3
3
 
4
4
 
5
5
  #
omlish/inject/binder.py CHANGED
@@ -74,7 +74,7 @@ def bind_as_fn(cls: type[T]) -> type[T]:
74
74
  ##
75
75
 
76
76
 
77
- _BANNED_BIND_TYPES = (
77
+ _BANNED_BIND_TYPES: tuple[type, ...] = (
78
78
  Element,
79
79
  Provider,
80
80
  Elements,
@@ -7,7 +7,6 @@ TODO:
7
7
  """
8
8
  import dataclasses as dc
9
9
  import inspect
10
- import types
11
10
  import typing as ta
12
11
  import weakref
13
12
 
@@ -28,38 +27,38 @@ R = ta.TypeVar('R')
28
27
  ##
29
28
 
30
29
 
31
- _signature_cache: ta.MutableMapping[ta.Any, inspect.Signature] = weakref.WeakKeyDictionary()
30
+ _SIGNATURE_CACHE: ta.MutableMapping[ta.Any, inspect.Signature] = weakref.WeakKeyDictionary()
32
31
 
33
32
 
34
33
  def signature(obj: ta.Any) -> inspect.Signature:
35
34
  try:
36
- return _signature_cache[obj]
35
+ return _SIGNATURE_CACHE[obj]
37
36
  except TypeError:
38
37
  return inspect.signature(obj)
39
38
  except KeyError:
40
39
  pass
41
40
  sig = inspect.signature(obj)
42
- _signature_cache[obj] = sig
41
+ _SIGNATURE_CACHE[obj] = sig
43
42
  return sig
44
43
 
45
44
 
46
45
  ##
47
46
 
48
47
 
49
- _tags: ta.MutableMapping[ta.Any, dict[str, ta.Any]] = weakref.WeakKeyDictionary()
48
+ _TAGS: ta.MutableMapping[ta.Any, dict[str, ta.Any]] = weakref.WeakKeyDictionary()
50
49
 
51
50
 
52
51
  def tag(obj: T, **kwargs: ta.Any) -> T:
53
52
  for v in kwargs.values():
54
53
  if isinstance(v, Tag):
55
54
  raise TypeError(v)
56
- _tags.setdefault(obj, {}).update(**kwargs)
55
+ _TAGS.setdefault(obj, {}).update(**kwargs)
57
56
  return obj
58
57
 
59
58
 
60
59
  def tags(**kwargs: ta.Any) -> ta.Callable[[ta.Callable[P, R]], ta.Callable[P, R]]:
61
60
  def inner(obj):
62
- _tags[obj] = kwargs
61
+ _TAGS[obj] = kwargs
63
62
  return obj
64
63
  return inner
65
64
 
@@ -75,7 +74,7 @@ def build_kwargs_target(
75
74
  raw_optional: bool = False,
76
75
  ) -> KwargsTarget:
77
76
  sig = signature(obj)
78
- tags = _tags.get(obj)
77
+ tags = _TAGS.get(obj)
79
78
 
80
79
  seen: set[Key] = set(map(as_key, skip_kwargs)) if skip_kwargs is not None else set()
81
80
  kws: list[Kwarg] = []
@@ -92,10 +91,9 @@ def build_kwargs_target(
92
91
  if (
93
92
  not raw_optional and
94
93
  isinstance(rf := rfl.type_(ann), rfl.Union) and
95
- len(rf.args) == 2 # noqa
96
- and types.NoneType in rf.args
94
+ rf.is_optional
97
95
  ):
98
- [ann] = [a for a in rf.args if a is not types.NoneType]
96
+ ann = rf.without_none()
99
97
 
100
98
  rty = rfl.type_(ann)
101
99
 
omlish/lite/cached.py CHANGED
@@ -22,5 +22,5 @@ class _cached_nullary: # noqa
22
22
  return bound
23
23
 
24
24
 
25
- def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
25
+ def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
26
26
  return _cached_nullary(fn)
@@ -25,8 +25,12 @@ class ExitStacked:
25
25
  def __exit__(self, exc_type, exc_val, exc_tb):
26
26
  if (es := self._exit_stack) is None:
27
27
  return None
28
+ self._exit_contexts()
28
29
  return es.__exit__(exc_type, exc_val, exc_tb)
29
30
 
31
+ def _exit_contexts(self) -> None:
32
+ pass
33
+
30
34
  def _enter_context(self, cm: ta.ContextManager[T]) -> T:
31
35
  es = check_not_none(self._exit_stack)
32
36
  return es.enter_context(cm)
omlish/lite/inject.py ADDED
@@ -0,0 +1,629 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import dataclasses as dc
4
+ import functools
5
+ import inspect
6
+ import types
7
+ import typing as ta
8
+ import weakref
9
+
10
+ from .check import check_isinstance
11
+ from .check import check_not_isinstance
12
+ from .maybes import Maybe
13
+ from .reflect import get_optional_alias_arg
14
+ from .reflect import is_new_type
15
+ from .reflect import is_optional_alias
16
+
17
+
18
+ T = ta.TypeVar('T')
19
+
20
+ InjectorKeyCls = ta.Union[type, ta.NewType]
21
+
22
+ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
23
+ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
24
+
25
+ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
26
+
27
+
28
+ ###
29
+ # types
30
+
31
+
32
+ @dc.dataclass(frozen=True)
33
+ class InjectorKey:
34
+ cls: InjectorKeyCls
35
+ tag: ta.Any = None
36
+ array: bool = False
37
+
38
+
39
+ ##
40
+
41
+
42
+ class InjectorProvider(abc.ABC):
43
+ @abc.abstractmethod
44
+ def provider_fn(self) -> InjectorProviderFn:
45
+ raise NotImplementedError
46
+
47
+
48
+ ##
49
+
50
+
51
+ @dc.dataclass(frozen=True)
52
+ class InjectorBinding:
53
+ key: InjectorKey
54
+ provider: InjectorProvider
55
+
56
+
57
+ class InjectorBindings(abc.ABC):
58
+ @abc.abstractmethod
59
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
60
+ raise NotImplementedError
61
+
62
+ ##
63
+
64
+
65
+ class Injector(abc.ABC):
66
+ @abc.abstractmethod
67
+ def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
68
+ raise NotImplementedError
69
+
70
+ @abc.abstractmethod
71
+ def provide(self, key: ta.Any) -> ta.Any:
72
+ raise NotImplementedError
73
+
74
+ @abc.abstractmethod
75
+ def provide_kwargs(self, obj: ta.Any) -> ta.Mapping[str, ta.Any]:
76
+ raise NotImplementedError
77
+
78
+ @abc.abstractmethod
79
+ def inject(self, obj: ta.Any) -> ta.Any:
80
+ raise NotImplementedError
81
+
82
+
83
+ ###
84
+ # exceptions
85
+
86
+
87
+ @dc.dataclass(frozen=True)
88
+ class InjectorKeyError(Exception):
89
+ key: InjectorKey
90
+
91
+ source: ta.Any = None
92
+ name: ta.Optional[str] = None
93
+
94
+
95
+ @dc.dataclass(frozen=True)
96
+ class UnboundInjectorKeyError(InjectorKeyError):
97
+ pass
98
+
99
+
100
+ @dc.dataclass(frozen=True)
101
+ class DuplicateInjectorKeyError(InjectorKeyError):
102
+ pass
103
+
104
+
105
+ ###
106
+ # keys
107
+
108
+
109
+ def as_injector_key(o: ta.Any) -> InjectorKey:
110
+ if o is inspect.Parameter.empty:
111
+ raise TypeError(o)
112
+ if isinstance(o, InjectorKey):
113
+ return o
114
+ if isinstance(o, type) or is_new_type(o):
115
+ return InjectorKey(o)
116
+ raise TypeError(o)
117
+
118
+
119
+ ###
120
+ # providers
121
+
122
+
123
+ @dc.dataclass(frozen=True)
124
+ class FnInjectorProvider(InjectorProvider):
125
+ fn: ta.Any
126
+
127
+ def __post_init__(self) -> None:
128
+ check_not_isinstance(self.fn, type)
129
+
130
+ def provider_fn(self) -> InjectorProviderFn:
131
+ def pfn(i: Injector) -> ta.Any:
132
+ return i.inject(self.fn)
133
+
134
+ return pfn
135
+
136
+
137
+ @dc.dataclass(frozen=True)
138
+ class CtorInjectorProvider(InjectorProvider):
139
+ cls: type
140
+
141
+ def __post_init__(self) -> None:
142
+ check_isinstance(self.cls, type)
143
+
144
+ def provider_fn(self) -> InjectorProviderFn:
145
+ def pfn(i: Injector) -> ta.Any:
146
+ return i.inject(self.cls)
147
+
148
+ return pfn
149
+
150
+
151
+ @dc.dataclass(frozen=True)
152
+ class ConstInjectorProvider(InjectorProvider):
153
+ v: ta.Any
154
+
155
+ def provider_fn(self) -> InjectorProviderFn:
156
+ return lambda _: self.v
157
+
158
+
159
+ @dc.dataclass(frozen=True)
160
+ class SingletonInjectorProvider(InjectorProvider):
161
+ p: InjectorProvider
162
+
163
+ def __post_init__(self) -> None:
164
+ check_isinstance(self.p, InjectorProvider)
165
+
166
+ def provider_fn(self) -> InjectorProviderFn:
167
+ v = not_set = object()
168
+
169
+ def pfn(i: Injector) -> ta.Any:
170
+ nonlocal v
171
+ if v is not_set:
172
+ v = ufn(i)
173
+ return v
174
+
175
+ ufn = self.p.provider_fn()
176
+ return pfn
177
+
178
+
179
+ @dc.dataclass(frozen=True)
180
+ class LinkInjectorProvider(InjectorProvider):
181
+ k: InjectorKey
182
+
183
+ def __post_init__(self) -> None:
184
+ check_isinstance(self.k, InjectorKey)
185
+
186
+ def provider_fn(self) -> InjectorProviderFn:
187
+ def pfn(i: Injector) -> ta.Any:
188
+ return i.provide(self.k)
189
+
190
+ return pfn
191
+
192
+
193
+ @dc.dataclass(frozen=True)
194
+ class ArrayInjectorProvider(InjectorProvider):
195
+ ps: ta.Sequence[InjectorProvider]
196
+
197
+ def provider_fn(self) -> InjectorProviderFn:
198
+ ps = [p.provider_fn() for p in self.ps]
199
+
200
+ def pfn(i: Injector) -> ta.Any:
201
+ rv = []
202
+ for ep in ps:
203
+ o = ep(i)
204
+ rv.append(o)
205
+ return rv
206
+
207
+ return pfn
208
+
209
+
210
+ ###
211
+ # bindings
212
+
213
+
214
+ @dc.dataclass(frozen=True)
215
+ class _InjectorBindings(InjectorBindings):
216
+ bs: ta.Optional[ta.Sequence[InjectorBinding]] = None
217
+ ps: ta.Optional[ta.Sequence[InjectorBindings]] = None
218
+
219
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
220
+ if self.bs is not None:
221
+ yield from self.bs
222
+ if self.ps is not None:
223
+ for p in self.ps:
224
+ yield from p.bindings()
225
+
226
+
227
+ def as_injector_bindings(*args: InjectorBindingOrBindings) -> InjectorBindings:
228
+ bs: ta.List[InjectorBinding] = []
229
+ ps: ta.List[InjectorBindings] = []
230
+
231
+ for a in args:
232
+ if isinstance(a, InjectorBindings):
233
+ ps.append(a)
234
+ elif isinstance(a, InjectorBinding):
235
+ bs.append(a)
236
+ else:
237
+ raise TypeError(a)
238
+
239
+ return _InjectorBindings(
240
+ bs or None,
241
+ ps or None,
242
+ )
243
+
244
+
245
+ ##
246
+
247
+
248
+ @dc.dataclass(frozen=True)
249
+ class OverridesInjectorBindings(InjectorBindings):
250
+ p: InjectorBindings
251
+ m: ta.Mapping[InjectorKey, InjectorBinding]
252
+
253
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
254
+ for b in self.p.bindings():
255
+ yield self.m.get(b.key, b)
256
+
257
+
258
+ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
259
+ m: ta.Dict[InjectorKey, InjectorBinding] = {}
260
+
261
+ for b in as_injector_bindings(*args).bindings():
262
+ if b.key in m:
263
+ raise DuplicateInjectorKeyError(b.key)
264
+ m[b.key] = b
265
+
266
+ return OverridesInjectorBindings(p, m)
267
+
268
+
269
+ ##
270
+
271
+
272
+ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
273
+ pm: ta.Dict[InjectorKey, InjectorProvider] = {}
274
+ am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
275
+
276
+ for b in bs.bindings():
277
+ if b.key.array:
278
+ am.setdefault(b.key, []).append(b.provider)
279
+ else:
280
+ if b.key in pm:
281
+ raise KeyError(b.key)
282
+ pm[b.key] = b.provider
283
+
284
+ if am:
285
+ for k, aps in am.items():
286
+ pm[k] = ArrayInjectorProvider(aps)
287
+
288
+ return pm
289
+
290
+
291
+ ###
292
+ # inspection
293
+
294
+
295
+ _INJECTION_SIGNATURE_CACHE: ta.MutableMapping[ta.Any, inspect.Signature] = weakref.WeakKeyDictionary()
296
+
297
+
298
+ def _injection_signature(obj: ta.Any) -> inspect.Signature:
299
+ try:
300
+ return _INJECTION_SIGNATURE_CACHE[obj]
301
+ except TypeError:
302
+ return inspect.signature(obj)
303
+ except KeyError:
304
+ pass
305
+ sig = inspect.signature(obj)
306
+ _INJECTION_SIGNATURE_CACHE[obj] = sig
307
+ return sig
308
+
309
+
310
+ class InjectionKwarg(ta.NamedTuple):
311
+ name: str
312
+ key: InjectorKey
313
+ has_default: bool
314
+
315
+
316
+ class InjectionKwargsTarget(ta.NamedTuple):
317
+ obj: ta.Any
318
+ kwargs: ta.Sequence[InjectionKwarg]
319
+
320
+
321
+ def build_injection_kwargs_target(
322
+ obj: ta.Any,
323
+ *,
324
+ skip_args: int = 0,
325
+ skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
326
+ raw_optional: bool = False,
327
+ ) -> InjectionKwargsTarget:
328
+ sig = _injection_signature(obj)
329
+
330
+ seen: ta.Set[InjectorKey] = set(map(as_injector_key, skip_kwargs)) if skip_kwargs is not None else set()
331
+ kws: ta.List[InjectionKwarg] = []
332
+ for p in list(sig.parameters.values())[skip_args:]:
333
+ if p.annotation is inspect.Signature.empty:
334
+ if p.default is not inspect.Parameter.empty:
335
+ raise KeyError(f'{obj}, {p.name}')
336
+ continue
337
+
338
+ if p.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY):
339
+ raise TypeError(sig)
340
+
341
+ ann = p.annotation
342
+ if (
343
+ not raw_optional and
344
+ is_optional_alias(ann)
345
+ ):
346
+ ann = get_optional_alias_arg(ann)
347
+
348
+ k = as_injector_key(ann)
349
+
350
+ if k in seen:
351
+ raise DuplicateInjectorKeyError(k)
352
+ seen.add(k)
353
+
354
+ kws.append(InjectionKwarg(
355
+ p.name,
356
+ k,
357
+ p.default is not inspect.Parameter.empty,
358
+ ))
359
+
360
+ return InjectionKwargsTarget(
361
+ obj,
362
+ kws,
363
+ )
364
+
365
+
366
+ ###
367
+ # binder
368
+
369
+
370
+ class InjectorBinder:
371
+ def __new__(cls, *args, **kwargs): # noqa
372
+ raise TypeError
373
+
374
+ _FN_TYPES: ta.Tuple[type, ...] = (
375
+ types.FunctionType,
376
+ types.MethodType,
377
+
378
+ classmethod,
379
+ staticmethod,
380
+
381
+ functools.partial,
382
+ functools.partialmethod,
383
+ )
384
+
385
+ @classmethod
386
+ def _is_fn(cls, obj: ta.Any) -> bool:
387
+ return isinstance(obj, cls._FN_TYPES)
388
+
389
+ @classmethod
390
+ def bind_as_fn(cls, icls: ta.Type[T]) -> ta.Type[T]:
391
+ check_isinstance(icls, type)
392
+ if icls not in cls._FN_TYPES:
393
+ cls._FN_TYPES = (*cls._FN_TYPES, icls)
394
+ return icls
395
+
396
+ _BANNED_BIND_TYPES: ta.Tuple[type, ...] = (
397
+ InjectorProvider,
398
+ )
399
+
400
+ @classmethod
401
+ def bind(
402
+ cls,
403
+ obj: ta.Any,
404
+ *,
405
+ key: ta.Any = None,
406
+ tag: ta.Any = None,
407
+ array: ta.Optional[bool] = None, # noqa
408
+
409
+ to_fn: ta.Any = None,
410
+ to_ctor: ta.Any = None,
411
+ to_const: ta.Any = None,
412
+ to_key: ta.Any = None,
413
+
414
+ singleton: bool = False,
415
+ ) -> InjectorBinding:
416
+ if obj is None or obj is inspect.Parameter.empty:
417
+ raise TypeError(obj)
418
+ if isinstance(obj, cls._BANNED_BIND_TYPES):
419
+ raise TypeError(obj)
420
+
421
+ ##
422
+
423
+ if key is not None:
424
+ key = as_injector_key(key)
425
+
426
+ ##
427
+
428
+ has_to = (
429
+ to_fn is not None or
430
+ to_ctor is not None or
431
+ to_const is not None or
432
+ to_key is not None
433
+ )
434
+ if isinstance(obj, InjectorKey):
435
+ if key is None:
436
+ key = obj
437
+ elif isinstance(obj, type):
438
+ if not has_to:
439
+ to_ctor = obj
440
+ if key is None:
441
+ key = InjectorKey(obj)
442
+ elif cls._is_fn(obj) and not has_to:
443
+ to_fn = obj
444
+ if key is None:
445
+ sig = _injection_signature(obj)
446
+ ty = check_isinstance(sig.return_annotation, type)
447
+ key = InjectorKey(ty)
448
+ else:
449
+ if to_const is not None:
450
+ raise TypeError('Cannot bind instance with to_const')
451
+ to_const = obj
452
+ if key is None:
453
+ key = InjectorKey(type(obj))
454
+ del has_to
455
+
456
+ ##
457
+
458
+ if tag is not None:
459
+ if key.tag is not None:
460
+ raise TypeError('Tag already set')
461
+ key = dc.replace(key, tag=tag)
462
+
463
+ if array is not None:
464
+ key = dc.replace(key, array=array)
465
+
466
+ ##
467
+
468
+ providers: ta.List[InjectorProvider] = []
469
+ if to_fn is not None:
470
+ providers.append(FnInjectorProvider(to_fn))
471
+ if to_ctor is not None:
472
+ providers.append(CtorInjectorProvider(to_ctor))
473
+ if to_const is not None:
474
+ providers.append(ConstInjectorProvider(to_const))
475
+ if to_key is not None:
476
+ providers.append(LinkInjectorProvider(as_injector_key(to_key)))
477
+ if not providers:
478
+ raise TypeError('Must specify provider')
479
+ if len(providers) > 1:
480
+ raise TypeError('May not specify multiple providers')
481
+ provider, = providers
482
+
483
+ ##
484
+
485
+ if singleton:
486
+ provider = SingletonInjectorProvider(provider)
487
+
488
+ ##
489
+
490
+ binding = InjectorBinding(key, provider)
491
+
492
+ ##
493
+
494
+ return binding
495
+
496
+
497
+ ###
498
+ # injector
499
+
500
+
501
+ _INJECTOR_INJECTOR_KEY = InjectorKey(Injector)
502
+
503
+
504
+ class _Injector(Injector):
505
+ def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
506
+ super().__init__()
507
+
508
+ self._bs = check_isinstance(bs, InjectorBindings)
509
+ self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
510
+
511
+ self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
512
+
513
+ if _INJECTOR_INJECTOR_KEY in self._pfm:
514
+ raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
515
+
516
+ def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
517
+ key = as_injector_key(key)
518
+
519
+ if key == _INJECTOR_INJECTOR_KEY:
520
+ return Maybe.just(self)
521
+
522
+ fn = self._pfm.get(key)
523
+ if fn is not None:
524
+ return Maybe.just(fn(self))
525
+
526
+ if self._p is not None:
527
+ pv = self._p.try_provide(key)
528
+ if pv is not None:
529
+ return Maybe.empty()
530
+
531
+ return Maybe.empty()
532
+
533
+ def provide(self, key: ta.Any) -> ta.Any:
534
+ v = self.try_provide(key)
535
+ if v.present:
536
+ return v.must()
537
+ raise UnboundInjectorKeyError(key)
538
+
539
+ def provide_kwargs(self, obj: ta.Any) -> ta.Mapping[str, ta.Any]:
540
+ kt = build_injection_kwargs_target(obj)
541
+ ret: ta.Dict[str, ta.Any] = {}
542
+ for kw in kt.kwargs:
543
+ if kw.has_default:
544
+ if not (mv := self.try_provide(kw.key)).present:
545
+ continue
546
+ v = mv.must()
547
+ else:
548
+ v = self.provide(kw.key)
549
+ ret[kw.name] = v
550
+ return ret
551
+
552
+ def inject(self, obj: ta.Any) -> ta.Any:
553
+ kws = self.provide_kwargs(obj)
554
+ return obj(**kws)
555
+
556
+
557
+ ###
558
+ # injection helpers
559
+
560
+
561
+ class Injection:
562
+ def __new__(cls, *args, **kwargs): # noqa
563
+ raise TypeError
564
+
565
+ # keys
566
+
567
+ @classmethod
568
+ def as_key(cls, o: ta.Any) -> InjectorKey:
569
+ return as_injector_key(o)
570
+
571
+ @classmethod
572
+ def array(cls, o: ta.Any) -> InjectorKey:
573
+ return dc.replace(as_injector_key(o), array=True)
574
+
575
+ @classmethod
576
+ def tag(cls, o: ta.Any, t: ta.Any) -> InjectorKey:
577
+ return dc.replace(as_injector_key(o), tag=t)
578
+
579
+ # bindings
580
+
581
+ @classmethod
582
+ def as_bindings(cls, *args: InjectorBindingOrBindings) -> InjectorBindings:
583
+ return as_injector_bindings(*args)
584
+
585
+ @classmethod
586
+ def override(cls, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
587
+ return injector_override(p, *args)
588
+
589
+ # binder
590
+
591
+ @classmethod
592
+ def bind(
593
+ cls,
594
+ obj: ta.Any,
595
+ *,
596
+ key: ta.Any = None,
597
+ tag: ta.Any = None,
598
+ array: ta.Optional[bool] = None, # noqa
599
+
600
+ to_fn: ta.Any = None,
601
+ to_ctor: ta.Any = None,
602
+ to_const: ta.Any = None,
603
+ to_key: ta.Any = None,
604
+
605
+ singleton: bool = False,
606
+ ) -> InjectorBinding:
607
+ return InjectorBinder.bind(
608
+ obj,
609
+
610
+ key=key,
611
+ tag=tag,
612
+ array=array,
613
+
614
+ to_fn=to_fn,
615
+ to_ctor=to_ctor,
616
+ to_const=to_const,
617
+ to_key=to_key,
618
+
619
+ singleton=singleton,
620
+ )
621
+
622
+ # injector
623
+
624
+ @classmethod
625
+ def create_injector(cls, *args: InjectorBindingOrBindings, p: ta.Optional[Injector] = None) -> Injector:
626
+ return _Injector(as_injector_bindings(*args), p)
627
+
628
+
629
+ inj = Injection
omlish/lite/maybes.py ADDED
@@ -0,0 +1,45 @@
1
+ import abc
2
+ import typing as ta
3
+
4
+
5
+ T = ta.TypeVar('T')
6
+
7
+
8
+ class Maybe(ta.Generic[T]):
9
+ @property
10
+ @abc.abstractmethod
11
+ def present(self) -> bool:
12
+ raise NotImplementedError
13
+
14
+ @abc.abstractmethod
15
+ def must(self) -> T:
16
+ raise NotImplementedError
17
+
18
+ @classmethod
19
+ def just(cls, v: T) -> 'Maybe[T]':
20
+ return tuple.__new__(_Maybe, (v,)) # noqa
21
+
22
+ _empty: ta.ClassVar['Maybe']
23
+
24
+ @classmethod
25
+ def empty(cls) -> 'Maybe[T]':
26
+ return Maybe._empty
27
+
28
+
29
+ class _Maybe(Maybe[T], tuple):
30
+ __slots__ = ()
31
+
32
+ def __init_subclass__(cls, **kwargs):
33
+ raise TypeError
34
+
35
+ @property
36
+ def present(self) -> bool:
37
+ return bool(self)
38
+
39
+ def must(self) -> T:
40
+ if not self:
41
+ raise ValueError
42
+ return self[0]
43
+
44
+
45
+ Maybe._empty = tuple.__new__(_Maybe, ()) # noqa
omlish/lite/reflect.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # ruff: noqa: UP006
2
2
  import functools
3
+ import types
3
4
  import typing as ta
4
5
 
5
6
 
@@ -37,6 +38,14 @@ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
37
38
  return it
38
39
 
39
40
 
41
+ def is_new_type(spec: ta.Any) -> bool:
42
+ if isinstance(ta.NewType, type):
43
+ return isinstance(spec, ta.NewType)
44
+ else:
45
+ # Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
46
+ return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
47
+
48
+
40
49
  def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
41
50
  seen = set()
42
51
  todo = list(reversed(cls.__subclasses__()))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev120
3
+ Version: 0.0.0.dev122
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=CxGnj-UiRPlZgmgWoovDWrOnqpSEmBy_kqA7cdfSA3w,1431
2
- omlish/__about__.py,sha256=JGKddDla0hzssCiRfhxitgG_mY-VKslBVWxhzDUU-Fg,3352
2
+ omlish/__about__.py,sha256=W3oofnGfBAEjbOcl3if2RLeaUM_umelQSS1abx6YVZ0,3352
3
3
  omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omlish/argparse.py,sha256=cqKGAqcxuxv_s62z0gq29L9KAvg_3-_rFvXKjVpRJjo,8126
5
5
  omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
@@ -232,7 +232,7 @@ omlish/http/sessions.py,sha256=VZ_WS5uiQG5y7i3u8oKuQMqf8dPKUOjFm_qk_0OvI8c,4793
232
232
  omlish/http/sse.py,sha256=MDs9RvxQXoQliImcc6qK1ERajEYM7Q1l8xmr-9ceNBc,2315
233
233
  omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
234
234
  omlish/inject/__init__.py,sha256=JQ7x8l9MjU-kJ5ap7cPVq7SY7zbbCIrjyJAF0UeE5-s,1886
235
- omlish/inject/binder.py,sha256=H8AQ4ecmBOtDL8fMgrU1yUJl1gBADLNcdysRbvO8Wso,4167
235
+ omlish/inject/binder.py,sha256=DAbc8TZi5w8Mna0TUtq0mT4jeDVA7i7SlBtOFrh2swc,4185
236
236
  omlish/inject/bindings.py,sha256=pLXn2U3kvmAS-68IOG-tr77DbiI-wp9hGyy4lhG6_H8,525
237
237
  omlish/inject/eagers.py,sha256=5AkGYuwijG0ihsH9NSaZotggalJ5_xWXhHE9mkn6IBA,329
238
238
  omlish/inject/elements.py,sha256=BzTnkNS-3iAMI47LMC2543u6A8Tfk3aJXn3CO191ez4,1547
@@ -254,7 +254,7 @@ omlish/inject/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
254
254
  omlish/inject/impl/bindings.py,sha256=8H586RCgmvwq53XBL9WMbb-1-Tdw_hh9zxIDCwcHA1c,414
255
255
  omlish/inject/impl/elements.py,sha256=bJBbHce_eZyIua2wbcejMwd9Uv-QeYcQ-c5N1qOXSmU,5950
256
256
  omlish/inject/impl/injector.py,sha256=WxIVOF6zvipb44302_j-xQZt8vnNCY0gdLX6UWzslXI,7550
257
- omlish/inject/impl/inspect.py,sha256=erLL1w6GzoPVGwORm56ra9vR-IH5BoMQrr7POwaFDjU,3050
257
+ omlish/inject/impl/inspect.py,sha256=qtHAOo7MvM7atWgxApYXZ0Dmw5zoP45ErPH3mnx9Kvk,2947
258
258
  omlish/inject/impl/multis.py,sha256=rRIWNCiTGaSWQUz_jxEy8LUmzdJDAlG94sLHYDS-ncg,2048
259
259
  omlish/inject/impl/origins.py,sha256=-cdcwz3BWb5LuC9Yn5ynYOwyPsKH06-kCc-3U0PxZ5w,1640
260
260
  omlish/inject/impl/privates.py,sha256=alpCYyk5VJ9lJknbRH2nLVNFYVvFhkj-VC1Vco3zCFQ,2613
@@ -298,17 +298,19 @@ omlish/lifecycles/manager.py,sha256=Au66KaO-fI-SEJALaPUJsCHYW2GE20xextk1wKn2BEU,
298
298
  omlish/lifecycles/states.py,sha256=zqMOU2ZU-MDNnWuwauM3_anIAiXM8LoBDElDEraptFg,1292
299
299
  omlish/lifecycles/transitions.py,sha256=qQtFby-h4VzbvgaUqT2NnbNumlcOx9FVVADP9t83xj4,1939
300
300
  omlish/lite/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
301
- omlish/lite/cached.py,sha256=2Yuoi1edt2nnq3RxF3xDS40RZABmHrnommwv8iGEYQE,690
301
+ omlish/lite/cached.py,sha256=Fs-ljXVJmHBjAaHc-JuJXMEV4MNSX5c_KHZIM3AEaIw,694
302
302
  omlish/lite/check.py,sha256=ouJme9tkzWXKymm_xZDK4mngdYSkxDrok6CSegvf-1w,1015
303
- omlish/lite/contextmanagers.py,sha256=_n6a9xhn06BD8H6A_SDtcipMrSBpzBqcxI0Ob2juomM,1226
303
+ omlish/lite/contextmanagers.py,sha256=_jfNdpYvxkbKwyjQLbK-o69W89GoEuUfl_NrCosE9lU,1308
304
304
  omlish/lite/docker.py,sha256=3IVZZtIm7-UdB2SwArmN_MosTva1_KifyYp3YWjODbE,337
305
+ omlish/lite/inject.py,sha256=wIFQouJVxjevw_Ik9t0YyKIpi2j1KGDWTGUdaDP2xN4,15101
305
306
  omlish/lite/io.py,sha256=lcpI1cS_Kn90tvYMg8ZWkSlYloS4RFqXCk-rKyclhdg,3148
306
307
  omlish/lite/journald.py,sha256=3nfahFbTrdrfp9txhtto6JYAyrus2kcAFtviqdm3qAo,3949
307
308
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
308
309
  omlish/lite/logs.py,sha256=tw7mbQslkyo9LopfgQnj0tYiqJ9TDNiw7D07aF7Dm2g,6176
309
310
  omlish/lite/marshal.py,sha256=SyYMsJ-TaGO9FX7LykBB0WtqsqetX9eLBotPiz3M_xg,9478
311
+ omlish/lite/maybes.py,sha256=7OlHJ8Q2r4wQ-aRbZSlJY7x0e8gDvufFdlohGEIJ3P4,833
310
312
  omlish/lite/pidfile.py,sha256=PRSDOAXmNkNwxh-Vwif0Nrs8RAmWroiNhLKIbdjwzBc,1723
311
- omlish/lite/reflect.py,sha256=9QYJwdINraq1JNMEgvoqeSlVvRRgOXpxAkpgX8EgRXc,1307
313
+ omlish/lite/reflect.py,sha256=ad_ya_zZJOQB8HoNjs9yc66R54zgflwJVPJqiBXMzqA,1681
312
314
  omlish/lite/runtime.py,sha256=VUhmNQvwf8QzkWSKj4Q0ReieJA_PzHaJNRBivfTseow,452
313
315
  omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
314
316
  omlish/lite/strings.py,sha256=QURcE4-1pKVW8eT_5VCJpXaHDWR2dW2pYOChTJnZDiQ,1504
@@ -471,9 +473,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
471
473
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
472
474
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
473
475
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
474
- omlish-0.0.0.dev120.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
475
- omlish-0.0.0.dev120.dist-info/METADATA,sha256=9KUQ86p-Od1hMU_EibbqCHSW6pojpLGYyvZCxg935BE,4000
476
- omlish-0.0.0.dev120.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
477
- omlish-0.0.0.dev120.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
478
- omlish-0.0.0.dev120.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
479
- omlish-0.0.0.dev120.dist-info/RECORD,,
476
+ omlish-0.0.0.dev122.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
477
+ omlish-0.0.0.dev122.dist-info/METADATA,sha256=_jwjqVyP1FYdDhGjA0juayR13H-KGTmaJ3wqd4gbQ6I,4000
478
+ omlish-0.0.0.dev122.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
479
+ omlish-0.0.0.dev122.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
480
+ omlish-0.0.0.dev122.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
481
+ omlish-0.0.0.dev122.dist-info/RECORD,,