omlish 0.0.0.dev120__py3-none-any.whl → 0.0.0.dev122__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.
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,,