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 CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev177'
2
- __revision__ = '3c81e0413acb397557436f3f66780dd6e8555500'
1
+ __version__ = '0.0.0.dev179'
2
+ __revision__ = '40949c92a73910f364c60391f73bb2fcf9538caf'
3
3
 
4
4
 
5
5
  #
@@ -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: object.__setattr__(self, {self.CACHED_HASH_ATTR!r}, h := hash({self_tuple}))',
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: frozenset[Element] | None = dc.xfield(None, coerce=check.of_isinstance((frozenset, None)))
23
- cs: frozenset['Elements'] | None = dc.xfield(None, coerce=check.of_isinstance((frozenset, None)))
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: set[Element] = set()
42
- cs: set[Elements] = set()
40
+ es: list[Element] = []
41
+ cs: list[Elements] = []
43
42
 
44
43
  def rec(a):
45
44
  if isinstance(a, Element):
46
- es.add(a)
45
+ es.append(a)
47
46
  elif isinstance(a, Elements):
48
- cs.add(a)
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
- frozenset(es) if es else None,
63
- frozenset(cs) if cs else None,
61
+ es if es else None,
62
+ cs if cs else None,
64
63
  )
omlish/inject/scopes.py CHANGED
@@ -80,7 +80,7 @@ class ScopeSeededProvider(Provider):
80
80
  return self.key.ty
81
81
 
82
82
 
83
- def bind_scope_seed(ss: SeededScope, k: ta.Any) -> Element:
83
+ def bind_scope_seed(k: ta.Any, ss: SeededScope) -> Element:
84
84
  k = as_key(k)
85
85
  return Binding(k, ScopeSeededProvider(ss, k))
86
86
 
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
- def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
314
- pm: ta.Dict[InjectorKey, InjectorProvider] = {}
315
- am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
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
- for b in bs.bindings():
318
- if b.key.array:
319
- al = am.setdefault(b.key, [])
320
- if isinstance(b.provider, ArrayInjectorProvider):
321
- al.extend(b.provider.ps)
322
- else:
323
- al.append(b.provider)
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
- if b.key in pm:
326
- raise KeyError(b.key)
327
- pm[b.key] = b.provider
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
- return pm
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 = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
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, = providers
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
- provider = SingletonInjectorProvider(provider)
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(ss, inj.Key(pytest.FixtureRequest, tag=pts)),
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev177
3
+ Version: 0.0.0.dev179
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=lRkBDFxlAbf6lN5upo3WSf-owW8YG1T21dfpbQL-XHM,7598
2
- omlish/__about__.py,sha256=TAC3lMwapwKbJCzCLJZmxUXYiCSpx1geJBOJjIaiTic,3409
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=FKnHuXCg9ylrzK2TLGqO5yfRN4HX3F415CSLlVYXtYE,3190
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=BzTnkNS-3iAMI47LMC2543u6A8Tfk3aJXn3CO191ez4,1547
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=bxbpEPqRs9N61GDKD-4ZWXkB6xiLDrILLjcE2IvZEtM,1989
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=EEaioN9ESAveVCMe2s5osjwI97FPRUVoU8P95vGUiYo,23376
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=v4DaKJ0KL8oQjzIMK43Gh8GHP4hiI6-lY37O9lyOHRk,5724
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.dev177.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
574
- omlish-0.0.0.dev177.dist-info/METADATA,sha256=e-GdbTv5rKr8yLrMqila1JpqW1XD3Hh-5KHOiFyxAQI,4264
575
- omlish-0.0.0.dev177.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
576
- omlish-0.0.0.dev177.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
577
- omlish-0.0.0.dev177.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
578
- omlish-0.0.0.dev177.dist-info/RECORD,,
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,,