omlish 0.0.0.dev177__py3-none-any.whl → 0.0.0.dev179__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 +2 -2
- omlish/dataclasses/impl/hashing.py +2 -1
- omlish/inject/elements.py +8 -9
- omlish/inject/scopes.py +1 -1
- omlish/lite/inject.py +224 -29
- omlish/testing/pytest/inject/harness.py +1 -1
- omlish/text/minja.py +223 -0
- {omlish-0.0.0.dev177.dist-info → omlish-0.0.0.dev179.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev177.dist-info → omlish-0.0.0.dev179.dist-info}/RECORD +13 -12
- {omlish-0.0.0.dev177.dist-info → omlish-0.0.0.dev179.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev177.dist-info → omlish-0.0.0.dev179.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev177.dist-info → omlish-0.0.0.dev179.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev177.dist-info → omlish-0.0.0.dev179.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -50,7 +50,8 @@ class HashProcessor(Processor):
|
|
50
50
|
if self._info.params_extras.cache_hash:
|
51
51
|
body = [
|
52
52
|
f'try: return self.{self.CACHED_HASH_ATTR}',
|
53
|
-
f'except AttributeError:
|
53
|
+
f'except AttributeError: pass',
|
54
|
+
f'object.__setattr__(self, {self.CACHED_HASH_ATTR!r}, h := hash({self_tuple}))',
|
54
55
|
f'return h',
|
55
56
|
]
|
56
57
|
else:
|
omlish/inject/elements.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import abc
|
2
2
|
import typing as ta
|
3
3
|
|
4
|
-
from .. import check
|
5
4
|
from .. import dataclasses as dc
|
6
5
|
from .. import lang
|
7
6
|
from .impl.origins import HasOriginsImpl
|
@@ -19,8 +18,8 @@ class ElementGenerator(lang.Abstract, lang.PackageSealed):
|
|
19
18
|
|
20
19
|
@dc.dataclass(frozen=True)
|
21
20
|
class Elements(lang.Final):
|
22
|
-
es:
|
23
|
-
cs:
|
21
|
+
es: ta.Collection[Element] | None = None
|
22
|
+
cs: ta.Collection['Elements'] | None = None
|
24
23
|
|
25
24
|
def __iter__(self) -> ta.Generator[Element, None, None]:
|
26
25
|
if self.es:
|
@@ -38,14 +37,14 @@ Elemental = ta.Union[ # noqa
|
|
38
37
|
|
39
38
|
|
40
39
|
def as_elements(*args: Elemental) -> Elements:
|
41
|
-
es:
|
42
|
-
cs:
|
40
|
+
es: list[Element] = []
|
41
|
+
cs: list[Elements] = []
|
43
42
|
|
44
43
|
def rec(a):
|
45
44
|
if isinstance(a, Element):
|
46
|
-
es.
|
45
|
+
es.append(a)
|
47
46
|
elif isinstance(a, Elements):
|
48
|
-
cs.
|
47
|
+
cs.append(a)
|
49
48
|
elif isinstance(a, ElementGenerator):
|
50
49
|
for n in a:
|
51
50
|
rec(n)
|
@@ -59,6 +58,6 @@ def as_elements(*args: Elemental) -> Elements:
|
|
59
58
|
return next(iter(cs))
|
60
59
|
|
61
60
|
return Elements(
|
62
|
-
|
63
|
-
|
61
|
+
es if es else None,
|
62
|
+
cs if cs else None,
|
64
63
|
)
|
omlish/inject/scopes.py
CHANGED
omlish/lite/inject.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import abc
|
3
3
|
import contextlib
|
4
|
+
import contextvars
|
4
5
|
import dataclasses as dc
|
5
6
|
import functools
|
6
7
|
import inspect
|
@@ -70,6 +71,10 @@ class InjectorBinding:
|
|
70
71
|
key: InjectorKey
|
71
72
|
provider: InjectorProvider
|
72
73
|
|
74
|
+
def __post_init__(self) -> None:
|
75
|
+
check.isinstance(self.key, InjectorKey)
|
76
|
+
check.isinstance(self.provider, InjectorProvider)
|
77
|
+
|
73
78
|
|
74
79
|
class InjectorBindings(abc.ABC):
|
75
80
|
@abc.abstractmethod
|
@@ -286,6 +291,33 @@ def as_injector_bindings(*args: InjectorBindingOrBindings) -> InjectorBindings:
|
|
286
291
|
##
|
287
292
|
|
288
293
|
|
294
|
+
def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
|
295
|
+
pm: ta.Dict[InjectorKey, InjectorProvider] = {}
|
296
|
+
am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
|
297
|
+
|
298
|
+
for b in bs.bindings():
|
299
|
+
if b.key.array:
|
300
|
+
al = am.setdefault(b.key, [])
|
301
|
+
if isinstance(b.provider, ArrayInjectorProvider):
|
302
|
+
al.extend(b.provider.ps)
|
303
|
+
else:
|
304
|
+
al.append(b.provider)
|
305
|
+
else:
|
306
|
+
if b.key in pm:
|
307
|
+
raise KeyError(b.key)
|
308
|
+
pm[b.key] = b.provider
|
309
|
+
|
310
|
+
if am:
|
311
|
+
for k, aps in am.items():
|
312
|
+
pm[k] = ArrayInjectorProvider(aps)
|
313
|
+
|
314
|
+
return pm
|
315
|
+
|
316
|
+
|
317
|
+
###
|
318
|
+
# overrides
|
319
|
+
|
320
|
+
|
289
321
|
@dc.dataclass(frozen=True)
|
290
322
|
class OverridesInjectorBindings(InjectorBindings):
|
291
323
|
p: InjectorBindings
|
@@ -307,30 +339,160 @@ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) ->
|
|
307
339
|
return OverridesInjectorBindings(p, m)
|
308
340
|
|
309
341
|
|
310
|
-
|
342
|
+
###
|
343
|
+
# scopes
|
311
344
|
|
312
345
|
|
313
|
-
|
314
|
-
|
315
|
-
|
346
|
+
class InjectorScope(abc.ABC): # noqa
|
347
|
+
def __init__(
|
348
|
+
self,
|
349
|
+
*,
|
350
|
+
_i: Injector,
|
351
|
+
) -> None:
|
352
|
+
check.not_in(abc.ABC, type(self).__bases__)
|
316
353
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
354
|
+
super().__init__()
|
355
|
+
|
356
|
+
self._i = _i
|
357
|
+
|
358
|
+
all_seeds: ta.Iterable[_InjectorScopeSeed] = self._i.provide(InjectorKey(_InjectorScopeSeed, array=True))
|
359
|
+
self._sks = {s.k for s in all_seeds if s.sc is type(self)}
|
360
|
+
|
361
|
+
#
|
362
|
+
|
363
|
+
@dc.dataclass(frozen=True)
|
364
|
+
class State:
|
365
|
+
seeds: ta.Dict[InjectorKey, ta.Any]
|
366
|
+
provisions: ta.Dict[InjectorKey, ta.Any] = dc.field(default_factory=dict)
|
367
|
+
|
368
|
+
def new_state(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> State:
|
369
|
+
vs = dict(vs)
|
370
|
+
check.equal(set(vs.keys()), self._sks)
|
371
|
+
return InjectorScope.State(vs)
|
372
|
+
|
373
|
+
#
|
374
|
+
|
375
|
+
@abc.abstractmethod
|
376
|
+
def state(self) -> State:
|
377
|
+
raise NotImplementedError
|
378
|
+
|
379
|
+
@abc.abstractmethod
|
380
|
+
def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.ContextManager[None]:
|
381
|
+
raise NotImplementedError
|
382
|
+
|
383
|
+
|
384
|
+
class ExclusiveInjectorScope(InjectorScope, abc.ABC):
|
385
|
+
_st: ta.Optional[InjectorScope.State] = None
|
386
|
+
|
387
|
+
def state(self) -> InjectorScope.State:
|
388
|
+
return check.not_none(self._st)
|
389
|
+
|
390
|
+
@contextlib.contextmanager
|
391
|
+
def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.Iterator[None]:
|
392
|
+
check.none(self._st)
|
393
|
+
self._st = self.new_state(vs)
|
394
|
+
try:
|
395
|
+
yield
|
396
|
+
finally:
|
397
|
+
self._st = None
|
398
|
+
|
399
|
+
|
400
|
+
class ContextvarInjectorScope(InjectorScope, abc.ABC):
|
401
|
+
_cv: contextvars.ContextVar
|
402
|
+
|
403
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
404
|
+
super().__init_subclass__(**kwargs)
|
405
|
+
check.not_in(abc.ABC, cls.__bases__)
|
406
|
+
check.state(not hasattr(cls, '_cv'))
|
407
|
+
cls._cv = contextvars.ContextVar(f'{cls.__name__}_cv')
|
408
|
+
|
409
|
+
def state(self) -> InjectorScope.State:
|
410
|
+
return self._cv.get()
|
411
|
+
|
412
|
+
@contextlib.contextmanager
|
413
|
+
def enter(self, vs: ta.Mapping[InjectorKey, ta.Any]) -> ta.Iterator[None]:
|
414
|
+
try:
|
415
|
+
self._cv.get()
|
416
|
+
except LookupError:
|
417
|
+
pass
|
324
418
|
else:
|
325
|
-
|
326
|
-
|
327
|
-
|
419
|
+
raise RuntimeError(f'Scope already entered: {self}')
|
420
|
+
st = self.new_state(vs)
|
421
|
+
tok = self._cv.set(st)
|
422
|
+
try:
|
423
|
+
yield
|
424
|
+
finally:
|
425
|
+
self._cv.reset(tok)
|
328
426
|
|
329
|
-
if am:
|
330
|
-
for k, aps in am.items():
|
331
|
-
pm[k] = ArrayInjectorProvider(aps)
|
332
427
|
|
333
|
-
|
428
|
+
#
|
429
|
+
|
430
|
+
|
431
|
+
@dc.dataclass(frozen=True)
|
432
|
+
class ScopedInjectorProvider(InjectorProvider):
|
433
|
+
p: InjectorProvider
|
434
|
+
k: InjectorKey
|
435
|
+
sc: ta.Type[InjectorScope]
|
436
|
+
|
437
|
+
def __post_init__(self) -> None:
|
438
|
+
check.isinstance(self.p, InjectorProvider)
|
439
|
+
check.isinstance(self.k, InjectorKey)
|
440
|
+
check.issubclass(self.sc, InjectorScope)
|
441
|
+
|
442
|
+
def provider_fn(self) -> InjectorProviderFn:
|
443
|
+
def pfn(i: Injector) -> ta.Any:
|
444
|
+
st = i[self.sc].state()
|
445
|
+
try:
|
446
|
+
return st.provisions[self.k]
|
447
|
+
except KeyError:
|
448
|
+
pass
|
449
|
+
v = ufn(i)
|
450
|
+
st.provisions[self.k] = v
|
451
|
+
return v
|
452
|
+
|
453
|
+
ufn = self.p.provider_fn()
|
454
|
+
return pfn
|
455
|
+
|
456
|
+
|
457
|
+
@dc.dataclass(frozen=True)
|
458
|
+
class _ScopeSeedInjectorProvider(InjectorProvider):
|
459
|
+
k: InjectorKey
|
460
|
+
sc: ta.Type[InjectorScope]
|
461
|
+
|
462
|
+
def __post_init__(self) -> None:
|
463
|
+
check.isinstance(self.k, InjectorKey)
|
464
|
+
check.issubclass(self.sc, InjectorScope)
|
465
|
+
|
466
|
+
def provider_fn(self) -> InjectorProviderFn:
|
467
|
+
def pfn(i: Injector) -> ta.Any:
|
468
|
+
st = i[self.sc].state()
|
469
|
+
return st.seeds[self.k]
|
470
|
+
return pfn
|
471
|
+
|
472
|
+
|
473
|
+
def bind_injector_scope(sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
|
474
|
+
return InjectorBinder.bind(sc, singleton=True)
|
475
|
+
|
476
|
+
|
477
|
+
#
|
478
|
+
|
479
|
+
|
480
|
+
@dc.dataclass(frozen=True)
|
481
|
+
class _InjectorScopeSeed:
|
482
|
+
sc: ta.Type['InjectorScope']
|
483
|
+
k: InjectorKey
|
484
|
+
|
485
|
+
def __post_init__(self) -> None:
|
486
|
+
check.issubclass(self.sc, InjectorScope)
|
487
|
+
check.isinstance(self.k, InjectorKey)
|
488
|
+
|
489
|
+
|
490
|
+
def bind_injector_scope_seed(k: ta.Any, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
|
491
|
+
kk = as_injector_key(k)
|
492
|
+
return as_injector_bindings(
|
493
|
+
InjectorBinding(kk, _ScopeSeedInjectorProvider(kk, sc)),
|
494
|
+
InjectorBinder.bind(_InjectorScopeSeed(sc, kk), array=True),
|
495
|
+
)
|
334
496
|
|
335
497
|
|
336
498
|
###
|
@@ -477,13 +639,21 @@ _INJECTOR_EAGER_ARRAY_KEY: InjectorKey[_InjectorEager] = InjectorKey(_InjectorEa
|
|
477
639
|
|
478
640
|
|
479
641
|
class _Injector(Injector):
|
642
|
+
_DEFAULT_BINDINGS: ta.ClassVar[ta.List[InjectorBinding]] = []
|
643
|
+
|
480
644
|
def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
|
481
645
|
super().__init__()
|
482
646
|
|
483
647
|
self._bs = check.isinstance(bs, InjectorBindings)
|
484
648
|
self._p: ta.Optional[Injector] = check.isinstance(p, (Injector, type(None)))
|
485
649
|
|
486
|
-
self._pfm = {
|
650
|
+
self._pfm = {
|
651
|
+
k: v.provider_fn()
|
652
|
+
for k, v in build_injector_provider_map(as_injector_bindings(
|
653
|
+
*self._DEFAULT_BINDINGS,
|
654
|
+
bs,
|
655
|
+
)).items()
|
656
|
+
}
|
487
657
|
|
488
658
|
if _INJECTOR_INJECTOR_KEY in self._pfm:
|
489
659
|
raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
|
@@ -650,6 +820,7 @@ class InjectorBinder:
|
|
650
820
|
to_const: ta.Any = None,
|
651
821
|
to_key: ta.Any = None,
|
652
822
|
|
823
|
+
in_: ta.Optional[ta.Type[InjectorScope]] = None,
|
653
824
|
singleton: bool = False,
|
654
825
|
|
655
826
|
eager: bool = False,
|
@@ -659,12 +830,12 @@ class InjectorBinder:
|
|
659
830
|
if isinstance(obj, cls._BANNED_BIND_TYPES):
|
660
831
|
raise TypeError(obj)
|
661
832
|
|
662
|
-
|
833
|
+
#
|
663
834
|
|
664
835
|
if key is not None:
|
665
836
|
key = as_injector_key(key)
|
666
837
|
|
667
|
-
|
838
|
+
#
|
668
839
|
|
669
840
|
has_to = (
|
670
841
|
to_fn is not None or
|
@@ -694,7 +865,7 @@ class InjectorBinder:
|
|
694
865
|
key = InjectorKey(type(obj))
|
695
866
|
del has_to
|
696
867
|
|
697
|
-
|
868
|
+
#
|
698
869
|
|
699
870
|
if tag is not None:
|
700
871
|
if key.tag is not None:
|
@@ -704,7 +875,7 @@ class InjectorBinder:
|
|
704
875
|
if array is not None:
|
705
876
|
key = dc.replace(key, array=array)
|
706
877
|
|
707
|
-
|
878
|
+
#
|
708
879
|
|
709
880
|
providers: ta.List[InjectorProvider] = []
|
710
881
|
if to_fn is not None:
|
@@ -719,23 +890,34 @@ class InjectorBinder:
|
|
719
890
|
raise TypeError('Must specify provider')
|
720
891
|
if len(providers) > 1:
|
721
892
|
raise TypeError('May not specify multiple providers')
|
722
|
-
provider
|
893
|
+
provider = check.single(providers)
|
723
894
|
|
724
|
-
|
895
|
+
#
|
725
896
|
|
897
|
+
pws: ta.List[ta.Any] = []
|
898
|
+
if in_ is not None:
|
899
|
+
check.issubclass(in_, InjectorScope)
|
900
|
+
check.not_in(abc.ABC, in_.__bases__)
|
901
|
+
pws.append(functools.partial(ScopedInjectorProvider, k=key, sc=in_))
|
726
902
|
if singleton:
|
727
|
-
|
903
|
+
pws.append(SingletonInjectorProvider)
|
904
|
+
if len(pws) > 1:
|
905
|
+
raise TypeError('May not specify multiple provider wrappers')
|
906
|
+
elif pws:
|
907
|
+
provider = check.single(pws)(provider)
|
908
|
+
|
909
|
+
#
|
728
910
|
|
729
911
|
binding = InjectorBinding(key, provider)
|
730
912
|
|
731
|
-
|
913
|
+
#
|
732
914
|
|
733
915
|
extras: ta.List[InjectorBinding] = []
|
734
916
|
|
735
917
|
if eager:
|
736
918
|
extras.append(bind_injector_eager_key(key))
|
737
919
|
|
738
|
-
|
920
|
+
#
|
739
921
|
|
740
922
|
if extras:
|
741
923
|
return as_injector_bindings(binding, *extras)
|
@@ -808,7 +990,8 @@ def bind_injector_eager_key(key: ta.Any) -> InjectorBinding:
|
|
808
990
|
return InjectorBinding(_INJECTOR_EAGER_ARRAY_KEY, ConstInjectorProvider(_InjectorEager(as_injector_key(key))))
|
809
991
|
|
810
992
|
|
811
|
-
|
993
|
+
###
|
994
|
+
# api
|
812
995
|
|
813
996
|
|
814
997
|
class InjectionApi:
|
@@ -828,9 +1011,19 @@ class InjectionApi:
|
|
828
1011
|
def as_bindings(self, *args: InjectorBindingOrBindings) -> InjectorBindings:
|
829
1012
|
return as_injector_bindings(*args)
|
830
1013
|
|
1014
|
+
# overrides
|
1015
|
+
|
831
1016
|
def override(self, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
|
832
1017
|
return injector_override(p, *args)
|
833
1018
|
|
1019
|
+
# scopes
|
1020
|
+
|
1021
|
+
def bind_scope(self, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
|
1022
|
+
return bind_injector_scope(sc)
|
1023
|
+
|
1024
|
+
def bind_scope_seed(self, k: ta.Any, sc: ta.Type[InjectorScope]) -> InjectorBindingOrBindings:
|
1025
|
+
return bind_injector_scope_seed(k, sc)
|
1026
|
+
|
834
1027
|
# injector
|
835
1028
|
|
836
1029
|
def create_injector(self, *args: InjectorBindingOrBindings, parent: ta.Optional[Injector] = None) -> Injector:
|
@@ -851,6 +1044,7 @@ class InjectionApi:
|
|
851
1044
|
to_const: ta.Any = None,
|
852
1045
|
to_key: ta.Any = None,
|
853
1046
|
|
1047
|
+
in_: ta.Optional[ta.Type[InjectorScope]] = None,
|
854
1048
|
singleton: bool = False,
|
855
1049
|
|
856
1050
|
eager: bool = False,
|
@@ -867,6 +1061,7 @@ class InjectionApi:
|
|
867
1061
|
to_const=to_const,
|
868
1062
|
to_key=to_key,
|
869
1063
|
|
1064
|
+
in_=in_,
|
870
1065
|
singleton=singleton,
|
871
1066
|
|
872
1067
|
eager=eager,
|
@@ -54,7 +54,7 @@ class Harness:
|
|
54
54
|
*[
|
55
55
|
inj.as_elements(
|
56
56
|
inj.bind_scope(ss),
|
57
|
-
inj.bind_scope_seed(
|
57
|
+
inj.bind_scope_seed(inj.Key(pytest.FixtureRequest, tag=pts), ss),
|
58
58
|
)
|
59
59
|
for pts, ss in _SCOPES_BY_PYTEST_SCOPE.items()
|
60
60
|
],
|
omlish/text/minja.py
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- raw
|
6
|
+
- blocks / inheritance
|
7
|
+
"""
|
8
|
+
import io
|
9
|
+
import re
|
10
|
+
import typing as ta
|
11
|
+
|
12
|
+
from omlish.lite.cached import cached_nullary
|
13
|
+
from omlish.lite.check import check
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
class MinjaTemplate:
|
20
|
+
def __init__(self, fn: ta.Callable) -> None:
|
21
|
+
super().__init__()
|
22
|
+
|
23
|
+
self._fn = fn
|
24
|
+
|
25
|
+
def __call__(self, **kwargs: ta.Any) -> str:
|
26
|
+
return self._fn(**kwargs)
|
27
|
+
|
28
|
+
|
29
|
+
##
|
30
|
+
|
31
|
+
|
32
|
+
class MinjaTemplateCompiler:
|
33
|
+
"""
|
34
|
+
Compiles a template string into a Python function. The returned function takes a dictionary 'context' and returns
|
35
|
+
the rendered string.
|
36
|
+
|
37
|
+
Supported syntax:
|
38
|
+
- Literal text remains literal.
|
39
|
+
- {{ expr }}: Evaluates 'expr' in the given context and writes its str() to output.
|
40
|
+
- {% code %}: Executes the given Python code (e.g. 'for x in items:'), must be terminated appropriately.
|
41
|
+
- {% endfor %} to close for loops.
|
42
|
+
- {% endif %} to close if blocks.
|
43
|
+
- {# comment #}: Ignored completely.
|
44
|
+
"""
|
45
|
+
|
46
|
+
DEFAULT_INDENT: str = ' '
|
47
|
+
|
48
|
+
def __init__(
|
49
|
+
self,
|
50
|
+
src: str,
|
51
|
+
args: ta.Sequence[str],
|
52
|
+
*,
|
53
|
+
indent: str = DEFAULT_INDENT,
|
54
|
+
) -> None:
|
55
|
+
super().__init__()
|
56
|
+
|
57
|
+
self._src = check.isinstance(src, str)
|
58
|
+
self._args = check.not_isinstance(args, str)
|
59
|
+
self._indent_str: str = check.non_empty_str(indent)
|
60
|
+
|
61
|
+
self._stack: ta.List[ta.Literal['for', 'if']] = []
|
62
|
+
self._lines: ta.List[str] = []
|
63
|
+
|
64
|
+
#
|
65
|
+
|
66
|
+
_TAG_PAT = re.compile(
|
67
|
+
r'({{.*?}}|{%.*?%}|{#.*?#})',
|
68
|
+
flags=re.DOTALL,
|
69
|
+
)
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def _split_tags(cls, src: str) -> ta.List[ta.Tuple[str, str]]:
|
73
|
+
raw = cls._TAG_PAT.split(src)
|
74
|
+
|
75
|
+
#
|
76
|
+
|
77
|
+
parts: ta.List[ta.Tuple[str, str]] = []
|
78
|
+
for s in raw:
|
79
|
+
if not s:
|
80
|
+
continue
|
81
|
+
|
82
|
+
for g, l, r in [
|
83
|
+
('{', '{{', '}}'),
|
84
|
+
('%', '{%', '%}'),
|
85
|
+
('#', '{#', '#}'),
|
86
|
+
]:
|
87
|
+
if s.startswith(l) and s.endswith(r):
|
88
|
+
parts.append((g, s[len(l):-len(r)]))
|
89
|
+
break
|
90
|
+
else:
|
91
|
+
parts.append(('', s))
|
92
|
+
|
93
|
+
#
|
94
|
+
|
95
|
+
for i, (g, s) in enumerate(parts):
|
96
|
+
if s.startswith('-'):
|
97
|
+
if i > 0:
|
98
|
+
lg, ls = parts[i - 1]
|
99
|
+
parts[i - 1] = (lg, ls.rstrip())
|
100
|
+
s = s[1:].lstrip()
|
101
|
+
|
102
|
+
if s.endswith('-'):
|
103
|
+
if i < len(parts) - 1:
|
104
|
+
rg, rs = parts[i + 1]
|
105
|
+
parts[i + 1] = (rg, rs.lstrip())
|
106
|
+
s = s[:-1].rstrip()
|
107
|
+
|
108
|
+
parts[i] = (g, s)
|
109
|
+
|
110
|
+
#
|
111
|
+
|
112
|
+
parts = [(g, s) for g, s in parts if g or s]
|
113
|
+
|
114
|
+
#
|
115
|
+
|
116
|
+
return parts
|
117
|
+
|
118
|
+
#
|
119
|
+
|
120
|
+
def _indent(self, line: str, ofs: int = 0) -> str:
|
121
|
+
return self._indent_str * (len(self._stack) + 1 + ofs) + line
|
122
|
+
|
123
|
+
#
|
124
|
+
|
125
|
+
_RENDER_FN_NAME = '__render'
|
126
|
+
|
127
|
+
@cached_nullary
|
128
|
+
def render(self) -> ta.Tuple[str, ta.Mapping[str, ta.Any]]:
|
129
|
+
parts = self._split_tags(self._src)
|
130
|
+
|
131
|
+
self._lines.append(f'def {self._RENDER_FN_NAME}({", ".join(self._args)}):')
|
132
|
+
self._lines.append(self._indent('__output = __StringIO()'))
|
133
|
+
|
134
|
+
for g, s in parts:
|
135
|
+
if g == '{':
|
136
|
+
expr = s.strip()
|
137
|
+
self._lines.append(self._indent(f'__output.write(str({expr}))'))
|
138
|
+
|
139
|
+
elif g == '%':
|
140
|
+
stmt = s.strip()
|
141
|
+
|
142
|
+
if stmt.startswith('for '):
|
143
|
+
self._lines.append(self._indent(stmt + ':'))
|
144
|
+
self._stack.append('for')
|
145
|
+
elif stmt.startswith('endfor'):
|
146
|
+
check.equal(self._stack.pop(), 'for')
|
147
|
+
|
148
|
+
elif stmt.startswith('if '):
|
149
|
+
self._lines.append(self._indent(stmt + ':'))
|
150
|
+
self._stack.append('if')
|
151
|
+
elif stmt.startswith('elif '):
|
152
|
+
check.equal(self._stack[-1], 'if')
|
153
|
+
self._lines.append(self._indent(stmt + ':', -1))
|
154
|
+
elif stmt.strip() == 'else':
|
155
|
+
check.equal(self._stack[-1], 'if')
|
156
|
+
self._lines.append(self._indent('else:', -1))
|
157
|
+
elif stmt.startswith('endif'):
|
158
|
+
check.equal(self._stack.pop(), 'if')
|
159
|
+
|
160
|
+
else:
|
161
|
+
self._lines.append(self._indent(stmt))
|
162
|
+
|
163
|
+
elif g == '#':
|
164
|
+
pass
|
165
|
+
|
166
|
+
elif not g:
|
167
|
+
if s:
|
168
|
+
safe_text = s.replace('"""', '\\"""')
|
169
|
+
self._lines.append(self._indent(f'__output.write("""{safe_text}""")'))
|
170
|
+
|
171
|
+
else:
|
172
|
+
raise KeyError(g)
|
173
|
+
|
174
|
+
check.empty(self._stack)
|
175
|
+
|
176
|
+
self._lines.append(self._indent('return __output.getvalue()'))
|
177
|
+
|
178
|
+
ns = {
|
179
|
+
'__StringIO': io.StringIO,
|
180
|
+
}
|
181
|
+
|
182
|
+
return ('\n'.join(self._lines), ns)
|
183
|
+
|
184
|
+
#
|
185
|
+
|
186
|
+
@classmethod
|
187
|
+
def _make_fn(
|
188
|
+
cls,
|
189
|
+
name: str,
|
190
|
+
src: str,
|
191
|
+
ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
192
|
+
) -> ta.Callable:
|
193
|
+
glo: dict = {}
|
194
|
+
if ns:
|
195
|
+
glo.update(ns)
|
196
|
+
exec(src, glo)
|
197
|
+
return glo[name]
|
198
|
+
|
199
|
+
#
|
200
|
+
|
201
|
+
@cached_nullary
|
202
|
+
def compile(self) -> MinjaTemplate:
|
203
|
+
render_src, render_ns = self.render()
|
204
|
+
|
205
|
+
render_fn = self._make_fn(
|
206
|
+
self._RENDER_FN_NAME,
|
207
|
+
render_src,
|
208
|
+
render_ns,
|
209
|
+
)
|
210
|
+
|
211
|
+
return MinjaTemplate(render_fn)
|
212
|
+
|
213
|
+
|
214
|
+
##
|
215
|
+
|
216
|
+
|
217
|
+
def compile_minja_template(src: str, args: ta.Sequence[str] = ()) -> MinjaTemplate:
|
218
|
+
return MinjaTemplateCompiler(src, args).compile()
|
219
|
+
|
220
|
+
|
221
|
+
def render_minja_template(src: str, **kwargs: ta.Any) -> str:
|
222
|
+
tmpl = compile_minja_template(src, list(kwargs))
|
223
|
+
return tmpl(**kwargs)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=lRkBDFxlAbf6lN5upo3WSf-owW8YG1T21dfpbQL-XHM,7598
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=hsTRZl82-_TWSF4oD3MRaC6rua1ked88afN1hbpqoUc,3409
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
5
5
|
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
@@ -164,7 +164,7 @@ omlish/dataclasses/impl/descriptors.py,sha256=rEYE1Len99agTQCC25hSPMnM19BgPr0ZCh
|
|
164
164
|
omlish/dataclasses/impl/exceptions.py,sha256=-vqxZmfXVflymVuiM553XTlJProse5HEMktTpfdPCIY,1275
|
165
165
|
omlish/dataclasses/impl/fields.py,sha256=4_5qMz9LlOS6U67cDKQ-oorTtSGGxR77I4hw88soM44,6878
|
166
166
|
omlish/dataclasses/impl/frozen.py,sha256=x87DSM8FIMZ3c_BIUE8NooCkExFjPsabeqIueEP5qKs,2988
|
167
|
-
omlish/dataclasses/impl/hashing.py,sha256=
|
167
|
+
omlish/dataclasses/impl/hashing.py,sha256=0Gr6XIRkKy4pr-mdHblIlQCy3mBxycjMqJk3oZDw43s,3215
|
168
168
|
omlish/dataclasses/impl/init.py,sha256=5kYcMDlI6EVeLQ6RCTk1bvYjb-cwg0AYfVE9FPZJlYI,6365
|
169
169
|
omlish/dataclasses/impl/internals.py,sha256=UvZYjrLT1S8ntyxJ_vRPIkPOF00K8HatGAygErgoXTU,2990
|
170
170
|
omlish/dataclasses/impl/main.py,sha256=Ti0PKbFKraKvfmoPuR-G7nLVNzRC8mvEuXhCuC-M2kc,2574
|
@@ -282,7 +282,7 @@ omlish/inject/__init__.py,sha256=n0RC9UDGsBQQ39cST39-XJqJPq2M0tnnh9yJubW9azo,189
|
|
282
282
|
omlish/inject/binder.py,sha256=DAbc8TZi5w8Mna0TUtq0mT4jeDVA7i7SlBtOFrh2swc,4185
|
283
283
|
omlish/inject/bindings.py,sha256=pLXn2U3kvmAS-68IOG-tr77DbiI-wp9hGyy4lhG6_H8,525
|
284
284
|
omlish/inject/eagers.py,sha256=5AkGYuwijG0ihsH9NSaZotggalJ5_xWXhHE9mkn6IBA,329
|
285
|
-
omlish/inject/elements.py,sha256=
|
285
|
+
omlish/inject/elements.py,sha256=npppGgYBQja0oPHkdBDmRhpm3md1mZT1FVcIQ--W2qI,1398
|
286
286
|
omlish/inject/exceptions.py,sha256=_wkN2tF55gQzmMOMKJC_9jYHBZzaBiCDcyqI9Sf2UZs,626
|
287
287
|
omlish/inject/injector.py,sha256=CoCUeMm1Oot4sG4Ti1sKCWrhlvtJ5QAeAI22AFWu2RQ,1066
|
288
288
|
omlish/inject/inspect.py,sha256=tw49r1RJVHrEHmT8WWA3_Bl9Z0L3lEGRqlLhbM5OmAM,592
|
@@ -294,7 +294,7 @@ omlish/inject/origins.py,sha256=OVQkiuRxx6ZtE8ZliufdndtFexcfpj-wZSDkUeGUCYM,534
|
|
294
294
|
omlish/inject/overrides.py,sha256=hrm243slCw_DDRbn3dK5QK1jfHezVokG-WYO2JaQOV8,535
|
295
295
|
omlish/inject/privates.py,sha256=hZOa_keY3KlXAzyiZ-sfN697UKXpkfXXNUIEmGT5TAA,641
|
296
296
|
omlish/inject/providers.py,sha256=Z6UzNCwRhKHHR0L5CyBMo4F-1M_xElLkPA6EKQWcqlw,754
|
297
|
-
omlish/inject/scopes.py,sha256=
|
297
|
+
omlish/inject/scopes.py,sha256=iQ8gD5V2z6ahkJhfy7zycMnUky-scfT_M-GjLjKN6Uw,1989
|
298
298
|
omlish/inject/types.py,sha256=11WVEPkZ-_8cv1BeTDRU-soIYxB_6x7dyWtsa2Iej9U,251
|
299
299
|
omlish/inject/utils.py,sha256=_UOZqA8IcLWPqf4Mcg9iIusQ5yxP_6Txg6PWtUYl23o,408
|
300
300
|
omlish/inject/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -379,7 +379,7 @@ omlish/lite/cached.py,sha256=O7ozcoDNFm1Hg2wtpHEqYSp_i_nCLNOP6Ueq_Uk-7mU,1300
|
|
379
379
|
omlish/lite/check.py,sha256=0PD-GKtaDqDX6jU5KbzbMvH-vl6jH82xgYfplmfTQkg,12941
|
380
380
|
omlish/lite/contextmanagers.py,sha256=m9JO--p7L7mSl4cycXysH-1AO27weDKjP3DZG61cwwM,1683
|
381
381
|
omlish/lite/dataclasses.py,sha256=M6UD4VwGo0Ky7RNzKWbO0IOy7iBZVCIbTiC6EYbFnX8,1035
|
382
|
-
omlish/lite/inject.py,sha256=
|
382
|
+
omlish/lite/inject.py,sha256=qBUftFeXMiRgANYbNS2e7TePMYyFAcuLgsJiLyMTW5o,28769
|
383
383
|
omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
|
384
384
|
omlish/lite/logs.py,sha256=CWFG0NKGhqNeEgryF5atN2gkPYbUdTINEw_s1phbINM,51
|
385
385
|
omlish/lite/marshal.py,sha256=O_v_slgjAAiSGWComdRhqLXdumuIlBDc3SvoG_p00QM,15863
|
@@ -550,7 +550,7 @@ omlish/testing/pytest/helpers.py,sha256=TJpD60mBtLi9FtxX4TThfuXvg5FIRPSiZk1aeRwe
|
|
550
550
|
omlish/testing/pytest/marks.py,sha256=4-3WgunN_fcmhkmQ6mtX_AFo5QRDwflNSH7NoyoXui4,432
|
551
551
|
omlish/testing/pytest/skip.py,sha256=NxTkAQiS3HKZR3sfFdxOR2LCFwtCveY6Ap-qtexiZbw,839
|
552
552
|
omlish/testing/pytest/inject/__init__.py,sha256=pdRKv1HcDmJ_yArKJbYITPXXZthRSGgBJWqITr0Er38,117
|
553
|
-
omlish/testing/pytest/inject/harness.py,sha256=
|
553
|
+
omlish/testing/pytest/inject/harness.py,sha256=_Qf7lLcYc_dpauYOE68u_a65jPCFWmQUYv9m_OOdNqs,5724
|
554
554
|
omlish/testing/pytest/plugins/__init__.py,sha256=ys1zXrYrNm7Uo6YOIVJ6Bd3dQo6kv387k7MbTYlqZSI,467
|
555
555
|
omlish/testing/pytest/plugins/_registry.py,sha256=IK04KlBgiOJxKAyCCgjpX2R-9tE-btalYJkgjLc8Te8,77
|
556
556
|
omlish/testing/pytest/plugins/asyncs.py,sha256=CG-cWWxCtxVIyKJKEjxfFV0MVwYBHPo1mb-umCGz9X8,5532
|
@@ -568,11 +568,12 @@ omlish/text/asdl.py,sha256=AS3irh-sag5pqyH3beJif78PjCbOaFso1NeKq-HXuTs,16867
|
|
568
568
|
omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
|
569
569
|
omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
|
570
570
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
571
|
+
omlish/text/minja.py,sha256=KAmZ2POcLcxwF4DPKxdWa16uWxXmVz1UnJXLSwt4oZo,5761
|
571
572
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
572
573
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
573
|
-
omlish-0.0.0.
|
574
|
-
omlish-0.0.0.
|
575
|
-
omlish-0.0.0.
|
576
|
-
omlish-0.0.0.
|
577
|
-
omlish-0.0.0.
|
578
|
-
omlish-0.0.0.
|
574
|
+
omlish-0.0.0.dev179.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
575
|
+
omlish-0.0.0.dev179.dist-info/METADATA,sha256=IYVOAZRpPvFaMBtdeTokZT1AlSGB41FLnxa00c4kz54,4264
|
576
|
+
omlish-0.0.0.dev179.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
577
|
+
omlish-0.0.0.dev179.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
578
|
+
omlish-0.0.0.dev179.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
579
|
+
omlish-0.0.0.dev179.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|