omlish 0.0.0.dev119__py3-none-any.whl → 0.0.0.dev121__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.dev119'
2
- __revision__ = 'abf03cc06fe1610ddf750a1392aa9c9de00acfd3'
1
+ __version__ = '0.0.0.dev121'
2
+ __revision__ = '8d604587a14f8faa089e1a5d3ff68756b93b0c1f'
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,624 @@
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_optional_alias
15
+
16
+
17
+ T = ta.TypeVar('T')
18
+
19
+ InjectorKeyCls = ta.Union[type, ta.NewType]
20
+
21
+ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
22
+ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
23
+
24
+ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
25
+
26
+
27
+ ###
28
+ # types
29
+
30
+
31
+ @dc.dataclass(frozen=True)
32
+ class InjectorKey:
33
+ cls: InjectorKeyCls
34
+ tag: ta.Any = None
35
+ array: bool = False
36
+
37
+
38
+ ##
39
+
40
+
41
+ class InjectorProvider(abc.ABC):
42
+ @abc.abstractmethod
43
+ def provider_fn(self) -> InjectorProviderFn:
44
+ raise NotImplementedError
45
+
46
+
47
+ ##
48
+
49
+
50
+ @dc.dataclass(frozen=True)
51
+ class InjectorBinding:
52
+ key: InjectorKey
53
+ provider: InjectorProvider
54
+
55
+
56
+ class InjectorBindings(abc.ABC):
57
+ @abc.abstractmethod
58
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
59
+ raise NotImplementedError
60
+
61
+ ##
62
+
63
+
64
+ class Injector(abc.ABC):
65
+ @abc.abstractmethod
66
+ def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
67
+ raise NotImplementedError
68
+
69
+ @abc.abstractmethod
70
+ def provide(self, key: ta.Any) -> ta.Any:
71
+ raise NotImplementedError
72
+
73
+ @abc.abstractmethod
74
+ def provide_kwargs(self, obj: ta.Any) -> ta.Mapping[str, ta.Any]:
75
+ raise NotImplementedError
76
+
77
+ @abc.abstractmethod
78
+ def inject(self, obj: ta.Any) -> ta.Any:
79
+ raise NotImplementedError
80
+
81
+
82
+ ###
83
+ # exceptions
84
+
85
+
86
+ @dc.dataclass(frozen=True)
87
+ class InjectorKeyError(Exception):
88
+ key: InjectorKey
89
+
90
+ source: ta.Any = None
91
+ name: ta.Optional[str] = None
92
+
93
+
94
+ @dc.dataclass(frozen=True)
95
+ class UnboundInjectorKeyError(InjectorKeyError):
96
+ pass
97
+
98
+
99
+ @dc.dataclass(frozen=True)
100
+ class DuplicateInjectorKeyError(InjectorKeyError):
101
+ pass
102
+
103
+
104
+ ###
105
+ # keys
106
+
107
+
108
+ def as_injector_key(o: ta.Any) -> InjectorKey:
109
+ if o is inspect.Parameter.empty:
110
+ raise TypeError(o)
111
+ if isinstance(o, InjectorKey):
112
+ return o
113
+ if isinstance(o, (type, ta.NewType)):
114
+ return InjectorKey(o)
115
+ raise TypeError(o)
116
+
117
+
118
+ ###
119
+ # providers
120
+
121
+
122
+ @dc.dataclass(frozen=True)
123
+ class FnInjectorProvider(InjectorProvider):
124
+ fn: ta.Any
125
+
126
+ def __post_init__(self) -> None:
127
+ check_not_isinstance(self.fn, type)
128
+
129
+ def provider_fn(self) -> InjectorProviderFn:
130
+ def pfn(i: Injector) -> ta.Any:
131
+ return i.inject(self.fn)
132
+
133
+ return pfn
134
+
135
+
136
+ @dc.dataclass(frozen=True)
137
+ class CtorInjectorProvider(InjectorProvider):
138
+ cls: type
139
+
140
+ def __post_init__(self) -> None:
141
+ check_isinstance(self.cls, type)
142
+
143
+ def provider_fn(self) -> InjectorProviderFn:
144
+ def pfn(i: Injector) -> ta.Any:
145
+ return i.inject(self.cls)
146
+
147
+ return pfn
148
+
149
+
150
+ @dc.dataclass(frozen=True)
151
+ class ConstInjectorProvider(InjectorProvider):
152
+ v: ta.Any
153
+
154
+ def provider_fn(self) -> InjectorProviderFn:
155
+ return lambda _: self.v
156
+
157
+
158
+ @dc.dataclass(frozen=True)
159
+ class SingletonInjectorProvider(InjectorProvider):
160
+ p: InjectorProvider
161
+
162
+ def __post_init__(self) -> None:
163
+ check_isinstance(self.p, InjectorProvider)
164
+
165
+ def provider_fn(self) -> InjectorProviderFn:
166
+ v = not_set = object()
167
+
168
+ def pfn(i: Injector) -> ta.Any:
169
+ nonlocal v
170
+ if v is not_set:
171
+ v = ufn(i)
172
+ return v
173
+
174
+ ufn = self.p.provider_fn()
175
+ return pfn
176
+
177
+
178
+ @dc.dataclass(frozen=True)
179
+ class LinkInjectorProvider(InjectorProvider):
180
+ k: InjectorKey
181
+
182
+ def __post_init__(self) -> None:
183
+ check_isinstance(self.k, InjectorKey)
184
+
185
+ def provider_fn(self) -> InjectorProviderFn:
186
+ def pfn(i: Injector) -> ta.Any:
187
+ return i.provide(self.k)
188
+
189
+ return pfn
190
+
191
+
192
+ @dc.dataclass(frozen=True)
193
+ class ArrayInjectorProvider(InjectorProvider):
194
+ ps: ta.Sequence[InjectorProvider]
195
+
196
+ def provider_fn(self) -> InjectorProviderFn:
197
+ ps = [p.provider_fn() for p in self.ps]
198
+
199
+ def pfn(i: Injector) -> ta.Any:
200
+ rv = []
201
+ for ep in ps:
202
+ o = ep(i)
203
+ rv.append(o)
204
+ return rv
205
+
206
+ return pfn
207
+
208
+
209
+ ###
210
+ # bindings
211
+
212
+
213
+ @dc.dataclass(frozen=True)
214
+ class _InjectorBindings(InjectorBindings):
215
+ bs: ta.Optional[ta.Sequence[InjectorBinding]] = None
216
+ ps: ta.Optional[ta.Sequence[InjectorBindings]] = None
217
+
218
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
219
+ if self.bs is not None:
220
+ yield from self.bs
221
+ if self.ps is not None:
222
+ for p in self.ps:
223
+ yield from p.bindings()
224
+
225
+
226
+ def as_injector_bindings(*args: InjectorBindingOrBindings) -> InjectorBindings:
227
+ bs: ta.List[InjectorBinding] = []
228
+ ps: ta.List[InjectorBindings] = []
229
+ for a in args:
230
+ if isinstance(a, InjectorBindings):
231
+ ps.append(a)
232
+ elif isinstance(a, InjectorBinding):
233
+ bs.append(a)
234
+ else:
235
+ raise TypeError(a)
236
+ return _InjectorBindings(
237
+ bs or None,
238
+ ps or None,
239
+ )
240
+
241
+
242
+ ##
243
+
244
+
245
+ @dc.dataclass(frozen=True)
246
+ class OverridesInjectorBindings(InjectorBindings):
247
+ p: InjectorBindings
248
+ m: ta.Mapping[InjectorKey, InjectorBinding]
249
+
250
+ def bindings(self) -> ta.Iterator[InjectorBinding]:
251
+ for b in self.p.bindings():
252
+ yield self.m.get(b.key, b)
253
+
254
+
255
+ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
256
+ m: ta.Dict[InjectorKey, InjectorBinding] = {}
257
+ for b in as_injector_bindings(*args).bindings():
258
+ if b.key in m:
259
+ raise DuplicateInjectorKeyError(b.key)
260
+ m[b.key] = b
261
+ return OverridesInjectorBindings(p, m)
262
+
263
+
264
+ ##
265
+
266
+
267
+ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
268
+ pm: ta.Dict[InjectorKey, InjectorProvider] = {}
269
+ am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
270
+
271
+ for b in bs.bindings():
272
+ if b.key.array:
273
+ am.setdefault(b.key, []).append(b.provider)
274
+ else:
275
+ if b.key in pm:
276
+ raise KeyError(b.key)
277
+ pm[b.key] = b.provider
278
+
279
+ if am:
280
+ for k, aps in am.items():
281
+ pm[k] = ArrayInjectorProvider(aps)
282
+
283
+ return pm
284
+
285
+
286
+ ###
287
+ # inspection
288
+
289
+
290
+ _INJECTION_SIGNATURE_CACHE: ta.MutableMapping[ta.Any, inspect.Signature] = weakref.WeakKeyDictionary()
291
+
292
+
293
+ def _injection_signature(obj: ta.Any) -> inspect.Signature:
294
+ try:
295
+ return _INJECTION_SIGNATURE_CACHE[obj]
296
+ except TypeError:
297
+ return inspect.signature(obj)
298
+ except KeyError:
299
+ pass
300
+ sig = inspect.signature(obj)
301
+ _INJECTION_SIGNATURE_CACHE[obj] = sig
302
+ return sig
303
+
304
+
305
+ class InjectionKwarg(ta.NamedTuple):
306
+ name: str
307
+ key: InjectorKey
308
+ has_default: bool
309
+
310
+
311
+ class InjectionKwargsTarget(ta.NamedTuple):
312
+ obj: ta.Any
313
+ kwargs: ta.Sequence[InjectionKwarg]
314
+
315
+
316
+ def build_injection_kwargs_target(
317
+ obj: ta.Any,
318
+ *,
319
+ skip_args: int = 0,
320
+ skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
321
+ raw_optional: bool = False,
322
+ ) -> InjectionKwargsTarget:
323
+ sig = _injection_signature(obj)
324
+
325
+ seen: ta.Set[InjectorKey] = set(map(as_injector_key, skip_kwargs)) if skip_kwargs is not None else set()
326
+ kws: ta.List[InjectionKwarg] = []
327
+ for p in list(sig.parameters.values())[skip_args:]:
328
+ if p.annotation is inspect.Signature.empty:
329
+ if p.default is not inspect.Parameter.empty:
330
+ raise KeyError(f'{obj}, {p.name}')
331
+ continue
332
+
333
+ if p.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY):
334
+ raise TypeError(sig)
335
+
336
+ ann = p.annotation
337
+ if (
338
+ not raw_optional and
339
+ is_optional_alias(ann)
340
+ ):
341
+ ann = get_optional_alias_arg(ann)
342
+
343
+ k = as_injector_key(ann)
344
+
345
+ if k in seen:
346
+ raise DuplicateInjectorKeyError(k)
347
+ seen.add(k)
348
+
349
+ kws.append(InjectionKwarg(
350
+ p.name,
351
+ k,
352
+ p.default is not inspect.Parameter.empty,
353
+ ))
354
+
355
+ return InjectionKwargsTarget(
356
+ obj,
357
+ kws,
358
+ )
359
+
360
+
361
+ ###
362
+ # binder
363
+
364
+
365
+ class InjectorBinder:
366
+ def __new__(cls, *args, **kwargs): # noqa
367
+ raise TypeError
368
+
369
+ _FN_TYPES: ta.Tuple[type, ...] = (
370
+ types.FunctionType,
371
+ types.MethodType,
372
+
373
+ classmethod,
374
+ staticmethod,
375
+
376
+ functools.partial,
377
+ functools.partialmethod,
378
+ )
379
+
380
+ @classmethod
381
+ def _is_fn(cls, obj: ta.Any) -> bool:
382
+ return isinstance(obj, cls._FN_TYPES)
383
+
384
+ @classmethod
385
+ def bind_as_fn(cls, icls: ta.Type[T]) -> ta.Type[T]:
386
+ check_isinstance(icls, type)
387
+ if icls not in cls._FN_TYPES:
388
+ cls._FN_TYPES = (*cls._FN_TYPES, icls)
389
+ return icls
390
+
391
+ _BANNED_BIND_TYPES: ta.Tuple[type, ...] = (
392
+ InjectorProvider,
393
+ )
394
+
395
+ @classmethod
396
+ def bind(
397
+ cls,
398
+ obj: ta.Any,
399
+ *,
400
+ key: ta.Any = None,
401
+ tag: ta.Any = None,
402
+ array: ta.Optional[bool] = None, # noqa
403
+
404
+ to_fn: ta.Any = None,
405
+ to_ctor: ta.Any = None,
406
+ to_const: ta.Any = None,
407
+ to_key: ta.Any = None,
408
+
409
+ singleton: bool = False,
410
+ ) -> InjectorBinding:
411
+ if obj is None or obj is inspect.Parameter.empty:
412
+ raise TypeError(obj)
413
+ if isinstance(obj, cls._BANNED_BIND_TYPES):
414
+ raise TypeError(obj)
415
+
416
+ ##
417
+
418
+ if key is not None:
419
+ key = as_injector_key(key)
420
+
421
+ ##
422
+
423
+ has_to = (
424
+ to_fn is not None or
425
+ to_ctor is not None or
426
+ to_const is not None or
427
+ to_key is not None
428
+ )
429
+ if isinstance(obj, InjectorKey):
430
+ if key is None:
431
+ key = obj
432
+ elif isinstance(obj, type):
433
+ if not has_to:
434
+ to_ctor = obj
435
+ if key is None:
436
+ key = InjectorKey(obj)
437
+ elif cls._is_fn(obj) and not has_to:
438
+ to_fn = obj
439
+ if key is None:
440
+ sig = _injection_signature(obj)
441
+ ty = check_isinstance(sig.return_annotation, type)
442
+ key = InjectorKey(ty)
443
+ else:
444
+ if to_const is not None:
445
+ raise TypeError('Cannot bind instance with to_const')
446
+ to_const = obj
447
+ if key is None:
448
+ key = InjectorKey(type(obj))
449
+ del has_to
450
+
451
+ ##
452
+
453
+ if tag is not None:
454
+ if key.tag is not None:
455
+ raise TypeError('Tag already set')
456
+ key = dc.replace(key, tag=tag)
457
+
458
+ if array is not None:
459
+ key = dc.replace(key, array=array)
460
+
461
+ ##
462
+
463
+ providers: ta.List[InjectorProvider] = []
464
+ if to_fn is not None:
465
+ providers.append(FnInjectorProvider(to_fn))
466
+ if to_ctor is not None:
467
+ providers.append(CtorInjectorProvider(to_ctor))
468
+ if to_const is not None:
469
+ providers.append(ConstInjectorProvider(to_const))
470
+ if to_key is not None:
471
+ providers.append(LinkInjectorProvider(as_injector_key(to_key)))
472
+ if not providers:
473
+ raise TypeError('Must specify provider')
474
+ if len(providers) > 1:
475
+ raise TypeError('May not specify multiple providers')
476
+ provider, = providers
477
+
478
+ ##
479
+
480
+ if singleton:
481
+ provider = SingletonInjectorProvider(provider)
482
+
483
+ ##
484
+
485
+ binding = InjectorBinding(key, provider)
486
+
487
+ ##
488
+
489
+ return binding
490
+
491
+
492
+ ###
493
+ # injector
494
+
495
+
496
+ _INJECTOR_INJECTOR_KEY = InjectorKey(Injector)
497
+
498
+
499
+ class _Injector(Injector):
500
+ def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
501
+ super().__init__()
502
+
503
+ self._bs = check_isinstance(bs, InjectorBindings)
504
+ self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
505
+
506
+ self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
507
+
508
+ if _INJECTOR_INJECTOR_KEY in self._pfm:
509
+ raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
510
+
511
+ def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
512
+ key = as_injector_key(key)
513
+
514
+ if key == _INJECTOR_INJECTOR_KEY:
515
+ return Maybe.just(self)
516
+
517
+ fn = self._pfm.get(key)
518
+ if fn is not None:
519
+ return Maybe.just(fn(self))
520
+
521
+ if self._p is not None:
522
+ pv = self._p.try_provide(key)
523
+ if pv is not None:
524
+ return Maybe.empty()
525
+
526
+ return Maybe.empty()
527
+
528
+ def provide(self, key: ta.Any) -> ta.Any:
529
+ v = self.try_provide(key)
530
+ if v.present:
531
+ return v.must()
532
+ raise UnboundInjectorKeyError(key)
533
+
534
+ def provide_kwargs(self, obj: ta.Any) -> ta.Mapping[str, ta.Any]:
535
+ kt = build_injection_kwargs_target(obj)
536
+ ret: ta.Dict[str, ta.Any] = {}
537
+ for kw in kt.kwargs:
538
+ if kw.has_default:
539
+ if not (mv := self.try_provide(kw.key)).present:
540
+ continue
541
+ v = mv.must()
542
+ else:
543
+ v = self.provide(kw.key)
544
+ ret[kw.name] = v
545
+ return ret
546
+
547
+ def inject(self, obj: ta.Any) -> ta.Any:
548
+ kws = self.provide_kwargs(obj)
549
+ return obj(**kws)
550
+
551
+
552
+ ###
553
+ # injection helpers
554
+
555
+
556
+ class Injection:
557
+ def __new__(cls, *args, **kwargs): # noqa
558
+ raise TypeError
559
+
560
+ # keys
561
+
562
+ @classmethod
563
+ def as_key(cls, o: ta.Any) -> InjectorKey:
564
+ return as_injector_key(o)
565
+
566
+ @classmethod
567
+ def array(cls, o: ta.Any) -> InjectorKey:
568
+ return dc.replace(as_injector_key(o), array=True)
569
+
570
+ @classmethod
571
+ def tag(cls, o: ta.Any, t: ta.Any) -> InjectorKey:
572
+ return dc.replace(as_injector_key(o), tag=t)
573
+
574
+ # bindings
575
+
576
+ @classmethod
577
+ def as_bindings(cls, *args: InjectorBindingOrBindings) -> InjectorBindings:
578
+ return as_injector_bindings(*args)
579
+
580
+ @classmethod
581
+ def override(cls, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
582
+ return injector_override(p, *args)
583
+
584
+ # binder
585
+
586
+ @classmethod
587
+ def bind(
588
+ cls,
589
+ obj: ta.Any,
590
+ *,
591
+ key: ta.Any = None,
592
+ tag: ta.Any = None,
593
+ array: ta.Optional[bool] = None, # noqa
594
+
595
+ to_fn: ta.Any = None,
596
+ to_ctor: ta.Any = None,
597
+ to_const: ta.Any = None,
598
+ to_key: ta.Any = None,
599
+
600
+ singleton: bool = False,
601
+ ) -> InjectorBinding:
602
+ return InjectorBinder.bind(
603
+ obj,
604
+
605
+ key=key,
606
+ tag=tag,
607
+ array=array,
608
+
609
+ to_fn=to_fn,
610
+ to_ctor=to_ctor,
611
+ to_const=to_const,
612
+ to_key=to_key,
613
+
614
+ singleton=singleton,
615
+ )
616
+
617
+ # injector
618
+
619
+ @classmethod
620
+ def create_injector(cls, *args: InjectorBindingOrBindings, p: ta.Optional[Injector] = None) -> Injector:
621
+ return _Injector(as_injector_bindings(*args), p)
622
+
623
+
624
+ 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
@@ -1,6 +1,10 @@
1
1
  """
2
2
  TODO:
3
- - @omlish-lite
3
+ - @omlish-lite ?
4
+ - 'recipes'
5
+ - abstract runtime
6
+ - https://github.com/wrmsr/omnibus/blob/c2ff67b6c5c80aa03fe27a9b6f36212f3212c7ca/omnibus/jmespath/eval.py#L64
7
+ - pytest-ify
4
8
 
5
9
  Applied:
6
10
  https://github.com/jmespath-community/python-jmespath/compare/bbe7300c60056f52413603cf3e2bcd0b6afeda3d...aef45e9d665de662eee31b06aeb8261e2bc8b90a
@@ -20,9 +20,15 @@ class LeafNode(Node, lang.Abstract):
20
20
  return []
21
21
 
22
22
 
23
+ UnaryArithmeticOperator: ta.TypeAlias = ta.Literal[
24
+ 'plus',
25
+ 'minus',
26
+ ]
27
+
28
+
23
29
  @dc.dataclass(frozen=True)
24
30
  class ArithmeticUnary(Node, lang.Final):
25
- operator: str
31
+ operator: UnaryArithmeticOperator
26
32
  expression: Node
27
33
 
28
34
  @property
@@ -30,9 +36,19 @@ class ArithmeticUnary(Node, lang.Final):
30
36
  return [self.expression]
31
37
 
32
38
 
39
+ ArithmeticOperator: ta.TypeAlias = ta.Literal[
40
+ 'div',
41
+ 'divide',
42
+ 'minus',
43
+ 'modulo',
44
+ 'multiply',
45
+ 'plus',
46
+ ]
47
+
48
+
33
49
  @dc.dataclass(frozen=True)
34
50
  class Arithmetic(Node, lang.Final):
35
- operator: str
51
+ operator: ArithmeticOperator
36
52
  left: Node
37
53
  right: Node
38
54
 
@@ -51,9 +67,19 @@ class Assign(Node, lang.Final):
51
67
  return [self.expr]
52
68
 
53
69
 
70
+ ComparatorName: ta.TypeAlias = ta.Literal[
71
+ 'eq',
72
+ 'ne',
73
+ 'lt',
74
+ 'gt',
75
+ 'lte',
76
+ 'gte',
77
+ ]
78
+
79
+
54
80
  @dc.dataclass(frozen=True)
55
81
  class Comparator(Node, lang.Final):
56
- name: str
82
+ name: ComparatorName
57
83
  first: Node
58
84
  second: Node
59
85
 
@@ -638,6 +638,14 @@ class ObjectFunctions(FunctionsClass):
638
638
  def _func_values(self, arg):
639
639
  return list(arg.values())
640
640
 
641
+ @signature({'types': ['expref']}, {'types': ['object']})
642
+ def _func_filter_keys(self, expref, arg):
643
+ return {k: v for k, v in arg.items() if expref.visit(expref.expression, k)}
644
+
645
+ @signature({'types': ['expref']}, {'types': ['object']})
646
+ def _func_filter_values(self, expref, arg):
647
+ return {k: v for k, v in arg.items() if expref.visit(expref.expression, v)}
648
+
641
649
 
642
650
  class KeyedFunctions(FunctionsClass):
643
651
  def _create_key_func(self, expref, allowed_types, function_name):
@@ -27,9 +27,11 @@ import typing as ta
27
27
  from ... import check
28
28
  from .ast import AndExpression
29
29
  from .ast import Arithmetic
30
+ from .ast import ArithmeticOperator
30
31
  from .ast import ArithmeticUnary
31
32
  from .ast import Assign
32
33
  from .ast import Comparator
34
+ from .ast import ComparatorName
33
35
  from .ast import CurrentNode
34
36
  from .ast import Expref
35
37
  from .ast import Field
@@ -52,6 +54,7 @@ from .ast import Projection
52
54
  from .ast import RootNode
53
55
  from .ast import Slice
54
56
  from .ast import Subexpression
57
+ from .ast import UnaryArithmeticOperator
55
58
  from .ast import ValueProjection
56
59
  from .ast import VariableRef
57
60
  from .exceptions import IncompleteExpressionError
@@ -491,15 +494,15 @@ class Parser:
491
494
 
492
495
  def _parse_comparator(self, left: Node, comparator: str) -> Node:
493
496
  right = self._expression(self.BINDING_POWER[comparator])
494
- return Comparator(comparator, left, right)
497
+ return Comparator(ta.cast(ComparatorName, comparator), left, right)
495
498
 
496
499
  def _parse_arithmetic_unary(self, token: Token) -> Node:
497
500
  expression = self._expression(self.BINDING_POWER[token['type']])
498
- return ArithmeticUnary(token['type'], expression)
501
+ return ArithmeticUnary(ta.cast(UnaryArithmeticOperator, token['type']), expression)
499
502
 
500
503
  def _parse_arithmetic(self, left: Node, operator: str) -> Node:
501
504
  right = self._expression(self.BINDING_POWER[operator])
502
- return Arithmetic(operator, left, right)
505
+ return Arithmetic(ta.cast(ArithmeticOperator, operator), left, right)
503
506
 
504
507
  def _parse_multi_select_list(self) -> Node:
505
508
  expressions: list[Node] = []
@@ -6,9 +6,11 @@ from ... import check
6
6
  from ... import lang
7
7
  from .ast import AndExpression
8
8
  from .ast import Arithmetic
9
+ from .ast import ArithmeticOperator
9
10
  from .ast import ArithmeticUnary
10
11
  from .ast import Assign
11
12
  from .ast import Comparator
13
+ from .ast import ComparatorName
12
14
  from .ast import CurrentNode
13
15
  from .ast import Expref
14
16
  from .ast import Field
@@ -31,6 +33,7 @@ from .ast import Projection
31
33
  from .ast import RootNode
32
34
  from .ast import Slice
33
35
  from .ast import Subexpression
36
+ from .ast import UnaryArithmeticOperator
34
37
  from .ast import ValueProjection
35
38
  from .ast import VariableRef
36
39
  from .exceptions import UndefinedVariableError
@@ -164,7 +167,7 @@ class _Expression:
164
167
 
165
168
 
166
169
  class TreeInterpreter(Visitor):
167
- COMPARATOR_FUNC: ta.Mapping[str, ta.Callable] = {
170
+ _COMPARATOR_FUNC: ta.Mapping[ComparatorName, ta.Callable] = {
168
171
  'eq': _equals,
169
172
  'ne': lambda x, y: not _equals(x, y),
170
173
  'lt': operator.lt,
@@ -173,17 +176,12 @@ class TreeInterpreter(Visitor):
173
176
  'gte': operator.ge,
174
177
  }
175
178
 
176
- _EQUALITY_OPS: ta.AbstractSet[str] = {
179
+ _EQUALITY_OPS: ta.AbstractSet[ComparatorName] = {
177
180
  'eq',
178
181
  'ne',
179
182
  }
180
183
 
181
- _ARITHMETIC_UNARY_FUNC: ta.Mapping[str, ta.Callable] = {
182
- 'minus': operator.neg,
183
- 'plus': lambda x: x,
184
- }
185
-
186
- _ARITHMETIC_FUNC: ta.Mapping[str, ta.Callable] = {
184
+ _ARITHMETIC_FUNC: ta.Mapping[ArithmeticOperator, ta.Callable] = {
187
185
  'div': operator.floordiv,
188
186
  'divide': operator.truediv,
189
187
  'minus': operator.sub,
@@ -192,6 +190,11 @@ class TreeInterpreter(Visitor):
192
190
  'plus': operator.add,
193
191
  }
194
192
 
193
+ _ARITHMETIC_UNARY_FUNC: ta.Mapping[UnaryArithmeticOperator, ta.Callable] = {
194
+ 'minus': operator.neg,
195
+ 'plus': lambda x: x,
196
+ }
197
+
195
198
  def __init__(self, options: Options | None = None) -> None:
196
199
  super().__init__()
197
200
 
@@ -235,7 +238,7 @@ class TreeInterpreter(Visitor):
235
238
 
236
239
  def visit_comparator(self, node: Comparator, value: ta.Any) -> ta.Any:
237
240
  # Common case: comparator is == or !=
238
- comparator_func = self.COMPARATOR_FUNC[node.name]
241
+ comparator_func = self._COMPARATOR_FUNC[node.name]
239
242
  if node.name in self._EQUALITY_OPS:
240
243
  return comparator_func(
241
244
  self.visit(node.first, value),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev119
3
+ Version: 0.0.0.dev121
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=cKBa887pzxYkOLw1HFwEiT4g4dP8AxuJQrGNkTmiju8,3352
2
+ omlish/__about__.py,sha256=aTRzdXeouYEQLsJZyhmeZxxg3aaQgoLMDv_T3jZ_HZE,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,15 +298,17 @@ 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=O3k8EYRKRKTkqoamM7awbcBvnYwy_fKIUPVf4FcObyg,15060
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
313
  omlish/lite/reflect.py,sha256=9QYJwdINraq1JNMEgvoqeSlVvRRgOXpxAkpgX8EgRXc,1307
312
314
  omlish/lite/runtime.py,sha256=VUhmNQvwf8QzkWSKj4Q0ReieJA_PzHaJNRBivfTseow,452
@@ -370,16 +372,16 @@ omlish/secrets/secrets.py,sha256=cnDGBoPknVxsCN04_gqcJT_7Ebk3iO3VPkRZ2oMjkMw,786
370
372
  omlish/secrets/subprocesses.py,sha256=EcnKlHHtnUMHGrBWXDfu8tv28wlgZx4P4GOiuPW9Vo8,1105
371
373
  omlish/specs/__init__.py,sha256=zZwF8yXTEkSstYtORkDhVLK-_hWU8WOJCuBpognb_NY,118
372
374
  omlish/specs/jmespath/LICENSE,sha256=IH-ZZlZkS8XMkf_ubNVD1aYHQ2l_wd0tmHtXrCcYpRU,1113
373
- omlish/specs/jmespath/__init__.py,sha256=Lz8JO0vP-pwrBtq9Y94y6LrOA7o1u1kCdPmMf_4lJBY,1918
375
+ omlish/specs/jmespath/__init__.py,sha256=MfBlUXNSPq5pRyboMgBk8JoIj0lQo-2TQ6Re7IwGQK0,2079
374
376
  omlish/specs/jmespath/__main__.py,sha256=wIXm6bs08etNG_GZlN2rBkADPb0rKfL2HSkm8spnpxw,200
375
- omlish/specs/jmespath/ast.py,sha256=jiIELiQSHU9haOqXJM4eiSgEapCzHIZ1F-fP_RGpRbk,5019
377
+ omlish/specs/jmespath/ast.py,sha256=XhcUGodHIdsY3-hVZEfpeW6LBehRjLbxVFXkMfZhRdk,5386
376
378
  omlish/specs/jmespath/cli.py,sha256=Lw57Eq5rmpwTwsvOzNmce_-XyoM84OIx5cuPgjUXNb0,2197
377
379
  omlish/specs/jmespath/exceptions.py,sha256=Co1HiUBPFNwFgZY3FV_ayuZoSgZIAmDcImImxauYNxc,4435
378
- omlish/specs/jmespath/functions.py,sha256=DG6wf4m0EuqD2FZnSKOFb4GvuXyCqlbncQvlBUN1NYY,22209
380
+ omlish/specs/jmespath/functions.py,sha256=lE_MlW5rDQirDCE9HtcAG-17kuHhH36RaPaQfk97xDY,22595
379
381
  omlish/specs/jmespath/lexer.py,sha256=hlPGCXPzGhd9ySj-z2cGTbyC9z3e0Io78IMYJZSEwNk,12647
380
- omlish/specs/jmespath/parser.py,sha256=yidV9KzHBsrO4-HJF53AOOxSvtqq5u0DESFaWY_0llA,24256
382
+ omlish/specs/jmespath/parser.py,sha256=IPJ0fCv1Aj2MhsDx5XuddtM2LnnLrynuGs4g7PYCmf0,24453
381
383
  omlish/specs/jmespath/scope.py,sha256=UyDsl9rv_c8DCjJBuVIA2ESu1jrgYvuwEKiaJDQKnT0,1590
382
- omlish/specs/jmespath/visitor.py,sha256=-A386ebMQAJE_vvbGHpQyESu1h0W-PGvaHE5_jgjiH4,16401
384
+ omlish/specs/jmespath/visitor.py,sha256=yneRMO4qf3k2Mdcm2cPC0ozRgOaudzlxRVRGatztJzs,16569
383
385
  omlish/specs/jsonrpc/__init__.py,sha256=E0EogYSKmbj1D-V57tBgPDTyVuD8HosHqVg0Vh1CVwM,416
384
386
  omlish/specs/jsonrpc/errors.py,sha256=-Zgmlo6bV6J8w5f8h9axQgLquIFBHDgIwcpufEH5NsE,707
385
387
  omlish/specs/jsonrpc/marshal.py,sha256=iXZNR7n0vfL_yiPFFYN-ZyGlzknNXExs2qC1HFChGPU,1913
@@ -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.dev119.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
475
- omlish-0.0.0.dev119.dist-info/METADATA,sha256=sMg30UlhW1GLNw8MkOhVqMaaiObB2IXCn22KIqaMpWU,4000
476
- omlish-0.0.0.dev119.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
477
- omlish-0.0.0.dev119.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
478
- omlish-0.0.0.dev119.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
479
- omlish-0.0.0.dev119.dist-info/RECORD,,
476
+ omlish-0.0.0.dev121.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
477
+ omlish-0.0.0.dev121.dist-info/METADATA,sha256=a5C2OhlCjHtn2VNKTUvzGAslZqvU4XAYaOvBfNd1884,4000
478
+ omlish-0.0.0.dev121.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
479
+ omlish-0.0.0.dev121.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
480
+ omlish-0.0.0.dev121.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
481
+ omlish-0.0.0.dev121.dist-info/RECORD,,