omlish 0.0.0.dev373__py3-none-any.whl → 0.0.0.dev375__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.dev373'
2
- __revision__ = '0a27e61ad530198ac027dde9d0b88d9bd995d0b5'
1
+ __version__ = '0.0.0.dev375'
2
+ __revision__ = '5e3b55f993cef1d0198721ee8b5d9f827c357fe9'
3
3
 
4
4
 
5
5
  #
@@ -37,10 +37,6 @@ from .coerce import ( # noqa
37
37
  seq_or_none,
38
38
  )
39
39
 
40
- from .errors import ( # noqa
41
- DuplicateKeyError,
42
- )
43
-
44
40
  from .frozen import ( # noqa
45
41
  Frozen,
46
42
  FrozenDict,
@@ -66,9 +66,27 @@ class TypeMap(ta.Generic[T]):
66
66
  def get(self, ty: type[T]) -> T | None:
67
67
  return self._dct.get(ty)
68
68
 
69
- def __getitem__(self, ty: type[T]) -> ta.Sequence[T]:
69
+ def __getitem__(self, ty: type[T]) -> T:
70
70
  return self._dct[ty]
71
71
 
72
+ _any_dct: dict[type | tuple[type, ...], tuple[T, ...]]
73
+
74
+ def get_any(self, cls: type | tuple[type, ...]) -> ta.Sequence[T]:
75
+ try:
76
+ any_dct = self._any_dct
77
+ except AttributeError:
78
+ any_dct = {}
79
+ self._any_dct = any_dct
80
+
81
+ try:
82
+ return any_dct[cls]
83
+ except KeyError:
84
+ pass
85
+
86
+ ret = tuple(tv for tv in self if isinstance(tv, cls))
87
+ any_dct[cls] = ret
88
+ return ret
89
+
72
90
 
73
91
  class DynamicTypeMap(ta.Generic[V]):
74
92
  def __init__(self, items: ta.Iterable[V] = (), *, weak: bool = False) -> None:
@@ -1,7 +1,6 @@
1
1
  import typing as ta
2
2
 
3
3
  from .. import lang
4
- from .errors import DuplicateKeyError
5
4
  from .identity import IdentityKeyDict
6
5
  from .identity import IdentitySet
7
6
 
@@ -48,7 +47,7 @@ def unique(
48
47
  k = key(e)
49
48
  if k in seen:
50
49
  if strict:
51
- raise DuplicateKeyError(k, e)
50
+ raise lang.DuplicateKeyError(k, e)
52
51
  else:
53
52
  seen.add(k)
54
53
  ret.append(e)
@@ -88,7 +87,7 @@ def make_map(
88
87
  for k, v in kvs:
89
88
  if k in d:
90
89
  if strict:
91
- raise DuplicateKeyError(k)
90
+ raise lang.DuplicateKeyError(k)
92
91
  else:
93
92
  d[k] = v
94
93
  return d
@@ -0,0 +1,162 @@
1
+ # ruff: noqa: UP006 UP045
2
+ # @omlish-lite
3
+ import abc
4
+ import io
5
+ import os.path
6
+ import sys
7
+ import typing as ta
8
+
9
+ from ..lite.cached import cached_nullary
10
+ from ..lite.check import check
11
+ from ..os.paths import is_path_in_dir
12
+
13
+
14
+ ##
15
+
16
+
17
+ class FnBuilder(abc.ABC):
18
+ @abc.abstractmethod
19
+ def build_fn(
20
+ self,
21
+ name: str,
22
+ src: str,
23
+ ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
24
+ ) -> ta.Callable:
25
+ ...
26
+
27
+
28
+ #
29
+
30
+
31
+ class SimpleFnBuilder(FnBuilder):
32
+ def build_fn(
33
+ self,
34
+ name: str,
35
+ src: str,
36
+ ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
37
+ ) -> ta.Callable:
38
+ ns = dict(ns or {})
39
+ exec(src, ns)
40
+ return ns[name]
41
+
42
+
43
+ build_fn = SimpleFnBuilder().build_fn
44
+
45
+
46
+ #
47
+
48
+
49
+ class DebugFnBuilder(FnBuilder):
50
+ def __init__(
51
+ self,
52
+ *,
53
+ mod_name_prefix: ta.Optional[str] = None,
54
+ src_dir: ta.Optional[str] = None,
55
+ ) -> None:
56
+ super().__init__()
57
+
58
+ if mod_name_prefix is None:
59
+ mod_name_prefix = f'_{self.__class__.__name__}_{id(self):x}_'
60
+ self._mod_name_prefix = mod_name_prefix
61
+
62
+ self._given_src_dir = src_dir
63
+
64
+ self._num_fns = 0
65
+ self._mod_names: ta.Set[str] = set()
66
+ self._installed_sys_path = False
67
+
68
+ @cached_nullary
69
+ def _src_dir(self) -> str:
70
+ if self._given_src_dir is not None:
71
+ return self._given_src_dir
72
+ else:
73
+ return __import__('tempfile').mkdtemp(prefix=f'_{self.__class__.__name__}_{os.getpid()}__') # noqa
74
+
75
+ @cached_nullary
76
+ def _install_sys_path(self) -> None:
77
+ if (src_dir := self._src_dir()) not in sys.path:
78
+ self._installed_sys_path = True
79
+ sys.path.append(src_dir)
80
+
81
+ def uninstall_sys_path(self) -> None:
82
+ if self._installed_sys_path:
83
+ while True:
84
+ try:
85
+ sys.path.remove(self._src_dir())
86
+ except ValueError:
87
+ break
88
+ self._installed_sys_path = True
89
+
90
+ def _gen_mod_name(
91
+ self,
92
+ fn_num: int,
93
+ fn_name: str,
94
+ *,
95
+ suffix: ta.Optional[str] = None,
96
+ ) -> str:
97
+ mod_name = f'{self._mod_name_prefix}{fn_num}__{fn_name}{suffix or ""}'
98
+
99
+ check.not_in(mod_name, self._mod_names)
100
+ check.not_in(mod_name, sys.modules)
101
+
102
+ self._mod_names.add(mod_name)
103
+
104
+ return mod_name
105
+
106
+ def _build_mod(
107
+ self,
108
+ mod_name: str,
109
+ mod_src: str,
110
+ ) -> ta.Any:
111
+ self._install_sys_path()
112
+
113
+ src_dir = self._src_dir()
114
+ src_file = os.path.join(src_dir, f'{mod_name}.py')
115
+ check.state(is_path_in_dir(src_dir, src_file))
116
+
117
+ with open(src_file, 'w') as f:
118
+ f.write(mod_src)
119
+
120
+ mod = __import__(mod_name)
121
+
122
+ check.equal(mod.__file__, src_file)
123
+
124
+ return mod
125
+
126
+ def build_fn(
127
+ self,
128
+ name: str,
129
+ src: str,
130
+ ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
131
+ ) -> ta.Callable:
132
+ fn_num = self._num_fns
133
+ self._num_fns += 1
134
+
135
+ mod_name = self._gen_mod_name(fn_num, name)
136
+
137
+ src_preamble: ta.List[str] = []
138
+
139
+ if ns:
140
+ ns_mod_name = self._gen_mod_name(fn_num, name, suffix='__ns')
141
+ ns_mod = self._build_mod(ns_mod_name, '')
142
+
143
+ for k, v in ns.items():
144
+ setattr(ns_mod, k, v)
145
+
146
+ src_preamble.append(f'from {ns_mod_name} import (')
147
+ for k in sorted(ns):
148
+ src_preamble.append(f' {k},')
149
+ src_preamble.append(')')
150
+
151
+ src_sb = io.StringIO()
152
+ if src_preamble:
153
+ src_sb.write('\n'.join(src_preamble))
154
+ src_sb.write('\n\n')
155
+ src_sb.write(src)
156
+ if not src.endswith('\n'):
157
+ src_sb.write('\n')
158
+
159
+ mod = self._build_mod(mod_name, src_sb.getvalue())
160
+
161
+ fn = mod.__dict__[name]
162
+ return fn
omlish/funcs/match.py CHANGED
@@ -1,4 +1,6 @@
1
1
  """
2
+ Analogous to scala's partial functions ( https://www.scala-lang.org/api/current/scala/PartialFunction.html ).
3
+
2
4
  TODO:
3
5
  - unify MatchFnClass with dispatch.method?
4
6
  - __call__ = mfs.method(); @__call__.register(lambda: ...) def _call_... ?
omlish/lang/__init__.py CHANGED
@@ -173,8 +173,11 @@ from .enums import ( # noqa
173
173
  enum_name_repr,
174
174
  )
175
175
 
176
+ from .errors import ( # noqa
177
+ DuplicateKeyError,
178
+ )
179
+
176
180
  from .functions import ( # noqa
177
- Args,
178
181
  VoidError,
179
182
  as_async,
180
183
  call_with,
@@ -359,6 +362,10 @@ from .typing import ( # noqa
359
362
 
360
363
  ##
361
364
 
365
+ from ..lite.args import ( # noqa
366
+ Args,
367
+ )
368
+
362
369
  from ..lite.contextmanagers import ( # noqa
363
370
  AsyncExitStacked,
364
371
  ExitStacked,
omlish/lang/functions.py CHANGED
@@ -1,4 +1,3 @@
1
- import dataclasses as dc
2
1
  import functools
3
2
  import time
4
3
  import types
@@ -210,37 +209,6 @@ def periodically(
210
209
  ##
211
210
 
212
211
 
213
- @dc.dataclass(init=False)
214
- class Args:
215
- args: ta.Sequence[ta.Any]
216
- kwargs: ta.Mapping[str, ta.Any]
217
-
218
- def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
219
- super().__init__()
220
-
221
- self.args = args
222
- self.kwargs = kwargs
223
-
224
- def __bool__(self) -> bool:
225
- return bool(self.args) or bool(self.kwargs)
226
-
227
- def update(self, *args: ta.Any, **kwargs: ta.Any) -> 'Args':
228
- return Args(
229
- *self.args,
230
- *args,
231
- **{
232
- **self.kwargs,
233
- **kwargs,
234
- },
235
- )
236
-
237
- def __call__(self, fn: ta.Callable[..., T]) -> T:
238
- return fn(*self.args, **self.kwargs)
239
-
240
-
241
- ##
242
-
243
-
244
212
  def coalesce(*vs: T | None) -> T:
245
213
  for v in vs:
246
214
  if v is not None:
omlish/lite/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # @omlish-lite
2
2
  """
3
- These are the 'core' lite modules. These generally have a 'full' equivalent, in which case non-lite code should prefer
3
+ These are the 'core' lite modules. These generally have a 'full' equivalent, in which case standard code should prefer
4
4
  that.
5
5
  """
omlish/lite/args.py ADDED
@@ -0,0 +1,36 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+
5
+ T = ta.TypeVar('T')
6
+
7
+
8
+ ##
9
+
10
+
11
+ @dc.dataclass(init=False)
12
+ class Args:
13
+ args: ta.Sequence[ta.Any]
14
+ kwargs: ta.Mapping[str, ta.Any]
15
+
16
+ def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
17
+ super().__init__()
18
+
19
+ self.args = args
20
+ self.kwargs = kwargs
21
+
22
+ def __bool__(self) -> bool:
23
+ return bool(self.args) or bool(self.kwargs)
24
+
25
+ def update(self, *args: ta.Any, **kwargs: ta.Any) -> 'Args':
26
+ return Args(
27
+ *self.args,
28
+ *args,
29
+ **{
30
+ **self.kwargs,
31
+ **kwargs,
32
+ },
33
+ )
34
+
35
+ def __call__(self, fn: ta.Callable[..., T]) -> T:
36
+ return fn(*self.args, **self.kwargs)
omlish/lite/json.py CHANGED
@@ -12,7 +12,7 @@ JSON_PRETTY_KWARGS: ta.Mapping[str, ta.Any] = dict(
12
12
  indent=JSON_PRETTY_INDENT,
13
13
  )
14
14
 
15
- json_dump_pretty: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON_PRETTY_KWARGS) # type: ignore
15
+ json_dump_pretty: ta.Callable[..., None] = functools.partial(json.dump, **JSON_PRETTY_KWARGS)
16
16
  json_dumps_pretty: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_PRETTY_KWARGS)
17
17
 
18
18
 
@@ -26,5 +26,5 @@ JSON_COMPACT_KWARGS: ta.Mapping[str, ta.Any] = dict(
26
26
  separators=JSON_COMPACT_SEPARATORS,
27
27
  )
28
28
 
29
- json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON_COMPACT_KWARGS) # type: ignore
29
+ json_dump_compact: ta.Callable[..., None] = functools.partial(json.dump, **JSON_COMPACT_KWARGS)
30
30
  json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
@@ -43,13 +43,13 @@ import types
43
43
  import typing as ta
44
44
  import unittest
45
45
 
46
- from .types import Test
46
+ from .types import UnittestTest
47
47
 
48
48
 
49
49
  ##
50
50
 
51
51
 
52
- class TestTargetLoader:
52
+ class UnittestTargetLoader:
53
53
  def __init__(
54
54
  self,
55
55
  *,
@@ -81,7 +81,7 @@ class TestTargetLoader:
81
81
  pattern: ta.Optional[str] = None
82
82
  top: ta.Optional[str] = None
83
83
 
84
- def load(self, target: Target) -> Test:
84
+ def load(self, target: Target) -> UnittestTest:
85
85
  loader = self._loader
86
86
  if loader is None:
87
87
  loader = unittest.loader.TestLoader()
@@ -89,8 +89,8 @@ class TestTargetLoader:
89
89
  if self._test_name_patterns:
90
90
  loader.testNamePatterns = self._test_name_patterns # type: ignore[assignment]
91
91
 
92
- if isinstance(target, TestTargetLoader.DiscoveryTarget):
93
- return ta.cast(Test, loader.discover(
92
+ if isinstance(target, UnittestTargetLoader.DiscoveryTarget):
93
+ return ta.cast(UnittestTest, loader.discover(
94
94
  target.start, # type: ignore[arg-type]
95
95
  target.pattern, # type: ignore[arg-type]
96
96
  target.top,
@@ -102,11 +102,11 @@ class TestTargetLoader:
102
102
  for part in module.split('.')[1:]:
103
103
  module = getattr(module, part)
104
104
 
105
- if isinstance(target, TestTargetLoader.ModuleTarget):
106
- return ta.cast(Test, loader.loadTestsFromModule(module))
105
+ if isinstance(target, UnittestTargetLoader.ModuleTarget):
106
+ return ta.cast(UnittestTest, loader.loadTestsFromModule(module))
107
107
 
108
- elif isinstance(target, TestTargetLoader.NamesTarget):
109
- return ta.cast(Test, loader.loadTestsFromNames(
108
+ elif isinstance(target, UnittestTargetLoader.NamesTarget):
109
+ return ta.cast(UnittestTest, loader.loadTestsFromNames(
110
110
  target.test_names, # type: ignore[arg-type]
111
111
  module,
112
112
  ))
@@ -2,6 +2,9 @@
2
2
  """
3
3
  https://docs.python.org/3/library/unittest.html#command-line-interface
4
4
  ~ https://github.com/python/cpython/tree/f66c75f11d3aeeb614600251fd5d3fe1a34b5ff1/Lib/unittest
5
+
6
+ TODO:
7
+ - giving it filenames doesn't work
5
8
  """
6
9
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
7
10
  # --------------------------------------------
@@ -46,25 +49,14 @@ import sys
46
49
  import types
47
50
  import typing as ta
48
51
 
49
- from .loading import TestTargetLoader
50
- from .running import TestRunner
52
+ from .loading import UnittestTargetLoader
53
+ from .running import UnittestTestRunner
51
54
 
52
55
 
53
56
  ##
54
57
 
55
58
 
56
- def _get_attr_dict(obj: ta.Optional[ta.Any], *attrs: str) -> ta.Dict[str, ta.Any]:
57
- if obj is None:
58
- return {}
59
-
60
- return {
61
- a: v
62
- for a in attrs
63
- if (v := getattr(obj, a, None)) is not None
64
- }
65
-
66
-
67
- class TestRunCli:
59
+ class UnittestRunCli:
68
60
  def __init__(self) -> None:
69
61
  super().__init__()
70
62
 
@@ -192,9 +184,22 @@ class TestRunCli:
192
184
 
193
185
  #
194
186
 
187
+ @staticmethod
188
+ def _get_attr_dict(obj: ta.Optional[ta.Any], *attrs: str) -> ta.Dict[str, ta.Any]:
189
+ if obj is None:
190
+ return {}
191
+
192
+ return {
193
+ a: v
194
+ for a in attrs
195
+ if (v := getattr(obj, a, None)) is not None
196
+ }
197
+
198
+ #
199
+
195
200
  IMPORT_PATH_PAT = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*')
196
201
 
197
- def _build_target(self, name: str, args: ParsedArgs) -> TestTargetLoader.Target:
202
+ def _build_target(self, name: str, args: ParsedArgs) -> UnittestTargetLoader.Target:
198
203
  is_discovery = False
199
204
  if os.path.isdir(name):
200
205
  is_discovery = True
@@ -204,12 +209,12 @@ class TestRunCli:
204
209
  is_discovery = True
205
210
 
206
211
  if not is_discovery:
207
- return TestTargetLoader.NamesTarget([name])
212
+ return UnittestTargetLoader.NamesTarget([name])
208
213
 
209
214
  else:
210
- return TestTargetLoader.DiscoveryTarget(
215
+ return UnittestTargetLoader.DiscoveryTarget(
211
216
  start=name,
212
- **_get_attr_dict(
217
+ **self._get_attr_dict(
213
218
  args.args,
214
219
  'pattern',
215
220
  'top',
@@ -226,7 +231,7 @@ class TestRunCli:
226
231
  *,
227
232
  exit: bool = False, # noqa
228
233
  ) -> None:
229
- loader = TestTargetLoader(**_get_attr_dict(
234
+ loader = UnittestTargetLoader(**self._get_attr_dict(
230
235
  args.args,
231
236
  'test_name_patterns',
232
237
  ))
@@ -236,7 +241,7 @@ class TestRunCli:
236
241
  for target_arg in (args.args.target if args.args is not None else None) or [] # noqa
237
242
  ]
238
243
 
239
- runner = TestRunner(TestRunner.Args(**_get_attr_dict(
244
+ runner = UnittestTestRunner(UnittestTestRunner.Args(**self._get_attr_dict(
240
245
  args.args,
241
246
  'verbosity',
242
247
  'failfast',
@@ -263,7 +268,7 @@ class TestRunCli:
263
268
 
264
269
 
265
270
  def _main() -> None:
266
- cli = TestRunCli()
271
+ cli = UnittestRunCli()
267
272
  args = cli.parse_args(sys.argv[1:])
268
273
  cli.run(args, exit=True)
269
274
 
@@ -45,30 +45,13 @@ import typing as ta
45
45
  import unittest
46
46
  import warnings
47
47
 
48
- from .types import Test
48
+ from .types import UnittestTest
49
49
 
50
50
 
51
51
  ##
52
52
 
53
53
 
54
- class _WritelnDecorator:
55
- def __init__(self, stream):
56
- super().__init__()
57
-
58
- self.stream = stream
59
-
60
- def __getattr__(self, attr):
61
- if attr in ('stream', '__getstate__'):
62
- raise AttributeError(attr)
63
- return getattr(self.stream, attr)
64
-
65
- def writeln(self, arg=None):
66
- if arg:
67
- self.write(arg)
68
- self.write('\n') # text-mode streams translate to \r\n if needed
69
-
70
-
71
- class TestRunner:
54
+ class UnittestTestRunner:
72
55
  """
73
56
  A test runner class that displays results in textual form.
74
57
 
@@ -98,7 +81,25 @@ class TestRunner:
98
81
 
99
82
  if stream is None:
100
83
  stream = sys.stderr
101
- self._stream = _WritelnDecorator(stream)
84
+ self._stream = UnittestTestRunner._WritelnDecorator(stream)
85
+
86
+ #
87
+
88
+ class _WritelnDecorator:
89
+ def __init__(self, stream):
90
+ super().__init__()
91
+
92
+ self.stream = stream
93
+
94
+ def __getattr__(self, attr):
95
+ if attr in ('stream', '__getstate__'):
96
+ raise AttributeError(attr)
97
+ return getattr(self.stream, attr)
98
+
99
+ def writeln(self, arg=None):
100
+ if arg:
101
+ self.write(arg)
102
+ self.write('\n') # text-mode streams translate to \r\n if needed
102
103
 
103
104
  #
104
105
 
@@ -179,7 +180,7 @@ class TestRunner:
179
180
 
180
181
  time_taken = stop_time - start_time
181
182
 
182
- return TestRunner._InternalRunTestResult(
183
+ return UnittestTestRunner._InternalRunTestResult(
183
184
  result,
184
185
  time_taken,
185
186
  )
@@ -206,7 +207,7 @@ class TestRunner:
206
207
  unexpected_successes: ta.Sequence[str]
207
208
 
208
209
  @classmethod
209
- def merge(cls, results: ta.Iterable['TestRunner.RunResult']) -> 'TestRunner.RunResult':
210
+ def merge(cls, results: ta.Iterable['UnittestTestRunner.RunResult']) -> 'UnittestTestRunner.RunResult':
210
211
  def reduce_attr(fn, a):
211
212
  return fn(getattr(r, a) for r in results)
212
213
 
@@ -233,11 +234,11 @@ class TestRunner:
233
234
 
234
235
  def as_test_and_reasons(l):
235
236
  return [
236
- TestRunner.RunResult.TestAndReason(result.getDescription(t), r)
237
+ UnittestTestRunner.RunResult.TestAndReason(result.getDescription(t), r)
237
238
  for t, r in l
238
239
  ]
239
240
 
240
- return TestRunner.RunResult(
241
+ return UnittestTestRunner.RunResult(
241
242
  raw_results=[result],
242
243
  time_taken=internal_result.time_taken,
243
244
 
@@ -254,11 +255,11 @@ class TestRunner:
254
255
 
255
256
  #
256
257
 
257
- def run(self, test: Test) -> RunResult:
258
+ def run(self, test: UnittestTest) -> RunResult:
258
259
  return self._build_run_result(self._internal_run_test(test))
259
260
 
260
- def run_many(self, tests: ta.Iterable[Test]) -> RunResult:
261
- return TestRunner.RunResult.merge([self.run(t) for t in tests])
261
+ def run_many(self, tests: ta.Iterable[UnittestTest]) -> RunResult:
262
+ return UnittestTestRunner.RunResult.merge([self.run(t) for t in tests])
262
263
 
263
264
  #
264
265
 
@@ -2,4 +2,4 @@ import typing as ta
2
2
  import unittest
3
3
 
4
4
 
5
- Test = ta.Callable[[unittest.TestResult], None] # ta.TypeAlias
5
+ UnittestTest = ta.Callable[[unittest.TestResult], None] # ta.TypeAlias
omlish/text/minja.py CHANGED
@@ -10,6 +10,8 @@ import io
10
10
  import re
11
11
  import typing as ta
12
12
 
13
+ from ..funcs.builders import FnBuilder
14
+ from ..funcs.builders import SimpleFnBuilder
13
15
  from ..lite.cached import cached_nullary
14
16
  from ..lite.check import check
15
17
  from ..lite.maybes import Maybe
@@ -49,10 +51,15 @@ class MinjaTemplateParam:
49
51
  return cls(name, dfl)
50
52
 
51
53
 
54
+ class MinjaTemplateFn(ta.Protocol):
55
+ def __call__(self, **kwargs: ta.Any) -> str:
56
+ ...
57
+
58
+
52
59
  class MinjaTemplate:
53
60
  def __init__(
54
61
  self,
55
- fn: ta.Callable,
62
+ fn: MinjaTemplateFn,
56
63
  params: ta.Sequence[MinjaTemplateParam],
57
64
  ) -> None:
58
65
  super().__init__()
@@ -73,7 +80,7 @@ class MinjaTemplate:
73
80
 
74
81
  class MinjaTemplateCompiler:
75
82
  """
76
- Compiles a template string into a Python function. The returned function takes a dictionary 'context' and returns
83
+ Compiles a template string into a Python fn. The returned fn takes a dictionary 'context' and returns
77
84
  the rendered string.
78
85
 
79
86
  Supported syntax:
@@ -96,6 +103,7 @@ class MinjaTemplateCompiler:
96
103
  fragment_processor: ta.Optional[ta.Callable[[MinjaTemplateFragmentKind, str], str]] = None,
97
104
  strict_strings: bool = False,
98
105
  stringifier: ta.Optional[ta.Callable[[ta.Any], str]] = None,
106
+ fn_builder: ta.Optional[FnBuilder] = None,
99
107
  ) -> None:
100
108
  super().__init__()
101
109
 
@@ -120,6 +128,10 @@ class MinjaTemplateCompiler:
120
128
  stringifier = lambda o: str(o)
121
129
  self._stringifier = stringifier
122
130
 
131
+ if fn_builder is None:
132
+ fn_builder = SimpleFnBuilder()
133
+ self._fn_builder = fn_builder
134
+
123
135
  self._stack: ta.List[ta.Literal['for', 'if']] = []
124
136
 
125
137
  @staticmethod
@@ -210,11 +222,13 @@ class MinjaTemplateCompiler:
210
222
  lines.append(f'def {self._RENDER_FN_NAME}():')
211
223
  else:
212
224
  lines.append(f'def {self._RENDER_FN_NAME}(')
225
+ lines.append(self._indent('*,'))
213
226
  for p in self._params:
214
227
  if p.default.present:
215
228
  check.not_in(p.name, ns)
216
- ns[p.name] = p.default.must()
217
- lines.append(self._indent(f'{p.name}={p.name},'))
229
+ dn = f'__minja__default__{p.name}'
230
+ ns[dn] = p.default.must()
231
+ lines.append(self._indent(f'{p.name}={dn},'))
218
232
  else:
219
233
  lines.append(self._indent(f'{p.name},'))
220
234
  lines.append('):')
@@ -269,19 +283,6 @@ class MinjaTemplateCompiler:
269
283
 
270
284
  #
271
285
 
272
- @classmethod
273
- def _make_fn(
274
- cls,
275
- name: str,
276
- src: str,
277
- ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
278
- ) -> ta.Callable:
279
- glo: dict = {}
280
- if ns:
281
- glo.update(ns)
282
- exec(src, glo)
283
- return glo[name]
284
-
285
286
  def compile(
286
287
  self,
287
288
  ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
@@ -294,14 +295,14 @@ class MinjaTemplateCompiler:
294
295
  raise KeyError(k)
295
296
  ns[k] = v
296
297
 
297
- render_fn = self._make_fn(
298
+ render_fn = self._fn_builder.build_fn(
298
299
  self._RENDER_FN_NAME,
299
300
  rendered.src,
300
301
  ns,
301
302
  )
302
303
 
303
304
  return MinjaTemplate(
304
- render_fn,
305
+ ta.cast(MinjaTemplateFn, check.callable(render_fn)),
305
306
  self._params,
306
307
  )
307
308
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev373
3
+ Version: 0.0.0.dev375
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=aT8yZ-Zh-9wfHl5Ym5ouiWC1i0cy7Q7RlhzavB6VLPI,8587
2
- omlish/__about__.py,sha256=a_E5lH9U0Zxuae2uxWKtn3a6hmcIp8G2BThTgUgh_H0,3478
2
+ omlish/__about__.py,sha256=KWPKJkqc48_109D1coX6sQoDWUirvqJQFHZpuq1yU-8,3478
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=rer-TPOFDU6fYq_AWio_AmA-ckZ8JDY5shIzQ_yXfzA,8414
5
5
  omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
@@ -76,20 +76,19 @@ omlish/codecs/funcs.py,sha256=or0Jogczuzk7csDTRl-HURMEjl8LXXqxxXYK45xcM5w,855
76
76
  omlish/codecs/registry.py,sha256=2FnO5YP7ui1LzkguwESY0MP3WIdwgPTIJTM_4RyTOEg,3896
77
77
  omlish/codecs/standard.py,sha256=eiZ4u9ep0XrA4Z_D1zJI0vmWyuN8HLrX4Se_r_Cq_ZM,60
78
78
  omlish/codecs/text.py,sha256=MgAzXapiHie-hhLBmcho67WXfWbmhlHz4tNPcHXnWUk,5711
79
- omlish/collections/__init__.py,sha256=AKf3dEpZar2KTrSaRF0VQ3CRjqUBZObY3zNwYL7lbmY,2690
79
+ omlish/collections/__init__.py,sha256=-CcIq7e8OkQakexxpJOXf2q6LdVqzo7B-quPtwur_BQ,2634
80
80
  omlish/collections/abc.py,sha256=p9zhL5oNV5WPyWmMn34fWfkuxPQAjOtL7WQA-Xsyhwk,2628
81
81
  omlish/collections/bimap.py,sha256=3szDCscPJlFRtkpyVQNWneg4s50mr6Rd0jdTzVEIcnE,1661
82
82
  omlish/collections/coerce.py,sha256=tAls15v_7p5bUN33R7Zbko87KW5toWHl9fRialCqyNY,7030
83
- omlish/collections/errors.py,sha256=shcS-NCnEUudF8qC_SmO2TQyjivKlS4TDjaz_faqQ0c,44
84
83
  omlish/collections/frozen.py,sha256=LMbAHYDENIQk1hvjCTvpnx66m1TalrHa4CSn8n_tsXQ,4142
85
84
  omlish/collections/hasheq.py,sha256=uHypfZlHhicQPvx9OOlpT9MSLwfc_mFil-WaxF9dTOo,3732
86
85
  omlish/collections/identity.py,sha256=S_W508EkU-AW0TZ7cv1wWUc6EG54vww4XbcfGjDFTgg,4793
87
- omlish/collections/mappings.py,sha256=iXb7oq1rCQak0KgzblgrzWCJLrkfJAYHFvl9lprOVUI,2804
86
+ omlish/collections/mappings.py,sha256=lAzKP9RONLjFH7emIDoY-6jukuD-Sa7fdxzP7u6qrqU,3261
88
87
  omlish/collections/multimaps.py,sha256=8Sqi1iVxUObnL47s8b1esa2TERoiEyyY4RBWF1VjDiw,3820
89
88
  omlish/collections/ordered.py,sha256=XV_ufr3QycjQ3nIy9L3BufdBosWD-3wb5xVZEHpZocQ,2467
90
89
  omlish/collections/ranked.py,sha256=McB8C2UQfUvrbmxGTpBz1-EZuyCLkBFtktzncMdt8_Y,2287
91
90
  omlish/collections/unmodifiable.py,sha256=0ZkA_0paWlkgaU6OupFdSV0yFP--7XoI3lqhs3Ci6ME,4951
92
- omlish/collections/utils.py,sha256=1LED_KLXsEb39Byhql7BD5ObHoSkFn2OyUdnHsKXKNo,4091
91
+ omlish/collections/utils.py,sha256=JYBYoConWxApqJLOjXR6GuQI76G0KQDjg6kkXrTofUw,4063
93
92
  omlish/collections/cache/__init__.py,sha256=D1gO71VcwxFTZP9gAc9isHfg_TEdalwhsJcgGLvS9hg,233
94
93
  omlish/collections/cache/descriptor.py,sha256=5SOsKNxnhisJY22l7tujMOI6MGGr6TERzgsfjvGXOyA,5013
95
94
  omlish/collections/cache/impl.py,sha256=Y18OcAsNL6dIWuk89UlZBpqq0iBU-P4VDio6eis43Us,14760
@@ -305,8 +304,9 @@ omlish/formats/toml/codec.py,sha256=5HFGWEPd9IFxPlRMRheX8FEDlRIzLe1moHEOj2_PFKU,
305
304
  omlish/formats/toml/parser.py,sha256=O2M0penQV3t8NAsq_conJjvTsXI8iivUFuBg2a5J3dU,30643
306
305
  omlish/formats/toml/writer.py,sha256=kLLQNEA_Kzd3ue7UXPQ_torOKoaLT82W16Bt99sID-w,3231
307
306
  omlish/funcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
307
+ omlish/funcs/builders.py,sha256=mJkgvJxM98x5pbMQzRY95tRUsbAfy_ivHtt4fDKfzsM,3984
308
308
  omlish/funcs/genmachine.py,sha256=D9dChaliNBIjYE6lJP5ctcVQUCffNBhceyaaLvBJ7ns,2578
309
- omlish/funcs/match.py,sha256=CTk4aljIAB9DnDpK-v0yfFLjprWjo3hxVr7nThajOZM,6088
309
+ omlish/funcs/match.py,sha256=EPeKojvecnJuDEWaXEYuef0Sx1J6Y0uTL01h4Z8ldxc,6199
310
310
  omlish/funcs/pairs.py,sha256=VCkZjDmJGtR76BsejsHNfb4TcpHCtkkmak-zWDFchAo,3904
311
311
  omlish/funcs/pipes.py,sha256=E7Sz8Aj8ke_vCs5AMNwg1I36kRdHVGTnzxVQaDyn43U,2490
312
312
  omlish/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -419,7 +419,7 @@ omlish/iterators/iterators.py,sha256=RxW35yQ5ed8vBQ22IqpDXFx-i5JiLQdp7-pkMZXhJJ8
419
419
  omlish/iterators/recipes.py,sha256=wOwOZg-zWG9Zc3wcAxJFSe2rtavVBYwZOfG09qYEx_4,472
420
420
  omlish/iterators/tools.py,sha256=M16LXrJhMdsz5ea2qH0vws30ZvhQuQSCVFSLpRf_gTg,2096
421
421
  omlish/iterators/unique.py,sha256=Nw0pSaNEcHAkve0ugfLPvJcirDOn9ECyC5wIL8JlJKI,1395
422
- omlish/lang/__init__.py,sha256=XgAZGIq1UN2raHvvmDwsd52RePlee_8OxOFdg3U6rP4,6526
422
+ omlish/lang/__init__.py,sha256=1XbAE88POrWtIHreNk47HAcl6Ch15uTH4cJFo2L7wFM,6619
423
423
  omlish/lang/attrs.py,sha256=zFiVuGVOq88x45464T_LxDa-ZEq_RD9zJLq2zeVEBDc,5105
424
424
  omlish/lang/casing.py,sha256=cFUlbDdXLhwnWwcYx4qnM5c4zGX7hIRUfcjiZbxUD28,4636
425
425
  omlish/lang/clsdct.py,sha256=HAGIvBSbCefzRjXriwYSBLO7QHKRv2UsE78jixOb-fA,1828
@@ -429,7 +429,8 @@ omlish/lang/contextmanagers.py,sha256=7mbTG7yIwM2whQBuhja5Tt0C4kfefBUW4Y_AIn5j8i
429
429
  omlish/lang/datetimes.py,sha256=01tg21QOx-PWDlm-CSFTalym3vpqF0EKzeinmtcVNoU,379
430
430
  omlish/lang/descriptors.py,sha256=zBtgO9LjdSTGHNUgiIqswh78WOVoGH6KzS0NbgB1Wls,6572
431
431
  omlish/lang/enums.py,sha256=F9tflHfaAoV2MpyuhZzpfX9-H55M3zNa9hCszsngEo8,111
432
- omlish/lang/functions.py,sha256=qNqzWF6vI6PGTzKhgkhnNXP8e1gSerb8GsHG3_wVBP8,6386
432
+ omlish/lang/errors.py,sha256=shcS-NCnEUudF8qC_SmO2TQyjivKlS4TDjaz_faqQ0c,44
433
+ omlish/lang/functions.py,sha256=aLdxhmqG0Pj9tBgsKdoCu_q15r82WIkNqDDSPQU19L8,5689
433
434
  omlish/lang/generators.py,sha256=a4D5HU_mySs2T2z3xCmE_s3t4QJkj0YRrK4-hhpGd0A,5197
434
435
  omlish/lang/imports.py,sha256=y9W9Y-d_cQ35QCLuSIPoa6vnEqSErFCz8b-34IH128U,10552
435
436
  omlish/lang/iterables.py,sha256=y1SX2Co3VsOeX2wlfFF7K3rwLvF7Dtre7VY6EpfwAwA,3338
@@ -462,7 +463,8 @@ omlish/lifecycles/controller.py,sha256=U_4mfp3n0zxH3RgFrcAi6yODuLIR5RF-uwB2tEBdX
462
463
  omlish/lifecycles/manager.py,sha256=92s1IH_gDP25PM5tFuPMP2hD_6s5fPi_VzZiDS5549g,5449
463
464
  omlish/lifecycles/states.py,sha256=6gTdY3hn7-1sJ60lA3GeMx5RVKvXtFBBXE4KEjoO1Hs,1297
464
465
  omlish/lifecycles/transitions.py,sha256=3IFdWGtAeoy3XRlIyW7yCKV4e4Iof9ytkqklGMRFYQs,1944
465
- omlish/lite/__init__.py,sha256=ISLhM4q0LR1XXTCaHdZOZxBRyIsoZqYm4u0bf1BPcVk,148
466
+ omlish/lite/__init__.py,sha256=cyZEpGob7XjU8DohyNxVe5CQRk4CQ5vrHL42OdhQb8w,148
467
+ omlish/lite/args.py,sha256=_py7azSCqPJwA2P1qY0B91sSiORsvYIK7IBO6hPDYK4,739
466
468
  omlish/lite/cached.py,sha256=O7ozcoDNFm1Hg2wtpHEqYSp_i_nCLNOP6Ueq_Uk-7mU,1300
467
469
  omlish/lite/check.py,sha256=ytCkwZoKfOlJqylL-AGm8C2WfsWJd2q3kFbnZCzX3_M,13844
468
470
  omlish/lite/configs.py,sha256=4-1uVxo-aNV7vMKa7PVNhM610eejG1WepB42-Dw2xQI,914
@@ -470,7 +472,7 @@ omlish/lite/contextmanagers.py,sha256=jpMxp5xwooRQJxsQ6J2ll4AJP9O7a5_YrLCGgwUFfD
470
472
  omlish/lite/dataclasses.py,sha256=aRSCZz1jN_UI-CWJhN0SJeKxa-79vXNUZ6YOMgG31SE,3610
471
473
  omlish/lite/imports.py,sha256=JDYRFxu-ofHEBfd5VV3b27oKOLhtTpuzte1_Nt7yLgw,1352
472
474
  omlish/lite/inject.py,sha256=xvmLmtD3_2INnkurJQv76_Rkh9usbApEQrXJ4cvuVAk,29019
473
- omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
475
+ omlish/lite/json.py,sha256=m0Ce9eqUZG23-H7-oOp8n1sf4fzno5vtK4AK_4Vc-Mg,706
474
476
  omlish/lite/logs.py,sha256=CWFG0NKGhqNeEgryF5atN2gkPYbUdTINEw_s1phbINM,51
475
477
  omlish/lite/marshal.py,sha256=dxiDtmSXt4EBpTprMNkPsOYkRs0W9AC3Kby9UGEjuRo,20400
476
478
  omlish/lite/maybes.py,sha256=0p_fzb6yiOjEpvMKaQ53Q6CH1VPW1or7v7Lt1JIKcgM,4359
@@ -791,10 +793,10 @@ omlish/testing/pytest/plugins/switches/plugin.py,sha256=RBxjefl9RDJuSmT_W0lTSd9D
791
793
  omlish/testing/pytest/plugins/switches/switches.py,sha256=lj8S9RMwUAW7a93ZqqTjoD4dRVkeGts2sl8Cn-H17hc,1890
792
794
  omlish/testing/unittest/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
793
795
  omlish/testing/unittest/__main__.py,sha256=d23loR_cKfTYZwYiqpt_CmKI7dd5WcYFgIYzqMep75E,68
794
- omlish/testing/unittest/loading.py,sha256=DT6vIZwXBkbEXocaxhrbWj8SqsoP7pyxD5K3bZlAE34,4764
795
- omlish/testing/unittest/main.py,sha256=OrebAhWqlfNOx5LF-ZIu2KvclneJgBAi13LkvDiZ_yw,8408
796
- omlish/testing/unittest/running.py,sha256=sifL1gjhn1VR965T9fdZy8NbJpVtpEQety8ngh5Fw7A,11821
797
- omlish/testing/unittest/types.py,sha256=RvQwU9l2amH2mKv3W3QyPBFg0eXkQ8wWnIJeRXeDdi0,102
796
+ omlish/testing/unittest/loading.py,sha256=JBiCO0iv0moal2fXILic6iojLxsahMNqinIU-jMWbXg,4820
797
+ omlish/testing/unittest/main.py,sha256=R6Kevx8LyUjI2DSaE0IdhPwrO5AOXRNCJ8mYBNXJiUI,8574
798
+ omlish/testing/unittest/running.py,sha256=QBaj1ZNpMPMbPeFDmqRwuA9TMKE6O3b8Ck9QbUfuKWI,11974
799
+ omlish/testing/unittest/types.py,sha256=56ACNtKt-w_cx7Olsy-m1FYvcUHWsjou9ftXB6CWzeY,110
798
800
  omlish/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
799
801
  omlish/text/asdl.py,sha256=AS3irh-sag5pqyH3beJif78PjCbOaFso1NeKq-HXuTs,16867
800
802
  omlish/text/decoding.py,sha256=sQWGckWzRslRHYKpj1SBeoo6AVqXm5HFlWFRARN1QpM,1286
@@ -804,7 +806,7 @@ omlish/text/glyphsplit.py,sha256=ZX9mhwTtmUE8rJpuD1jBO1CTc6xzmBCtbfiHmp5vMM8,381
804
806
  omlish/text/indent.py,sha256=BWVVaHs_B1ppwHJJIxKCDG3iCutkYy5e1qr59Z_Suzg,1524
805
807
  omlish/text/linecache.py,sha256=hRYlEhD63ZfA6_ZOTkQIcnON-3W56QMAhcG3vEJqj9M,1858
806
808
  omlish/text/mangle.py,sha256=k7mYavVgxJ2ENV2wfjw3c9u3hqH5NeVpjoxYbyaYC0Y,2796
807
- omlish/text/minja.py,sha256=wz4ZkjPTvlVkLBkWyTCoyERyiofBMvDXZZcNsGtk8gU,8999
809
+ omlish/text/minja.py,sha256=7UKNalkWpTG_364OIo7p5ym--uiNPR2RFBW_W8rrO4I,9194
808
810
  omlish/text/parts.py,sha256=MpiCUyfpcL4PLb2Etj8V7Yj4qofhy0xVwBrIL6RfNdg,6646
809
811
  omlish/text/random.py,sha256=8feS5JE_tSjYlMl-lp0j93kCfzBae9AM2cXlRLebXMA,199
810
812
  omlish/text/templating.py,sha256=Nf5BVDgrYBf0WmYwV6SnHPDvl9XVMu8v7MX78pInLTw,3325
@@ -888,9 +890,9 @@ omlish/typedvalues/marshal.py,sha256=AtBz7Jq-BfW8vwM7HSxSpR85JAXmxK2T0xDblmm1HI0
888
890
  omlish/typedvalues/of_.py,sha256=UXkxSj504WI2UrFlqdZJbu2hyDwBhL7XVrc2qdR02GQ,1309
889
891
  omlish/typedvalues/reflect.py,sha256=PAvKW6T4cW7u--iX80w3HWwZUS3SmIZ2_lQjT65uAyk,1026
890
892
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
891
- omlish-0.0.0.dev373.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
892
- omlish-0.0.0.dev373.dist-info/METADATA,sha256=fed55fYjKmKimj_-64adhKjxXvjVgpztmAzJhqRHczs,4416
893
- omlish-0.0.0.dev373.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
894
- omlish-0.0.0.dev373.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
895
- omlish-0.0.0.dev373.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
896
- omlish-0.0.0.dev373.dist-info/RECORD,,
893
+ omlish-0.0.0.dev375.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
894
+ omlish-0.0.0.dev375.dist-info/METADATA,sha256=IhVfzMkNMySdjER4EgktRUN72cnUv-EMyJ5Y3sCZ2Vk,4416
895
+ omlish-0.0.0.dev375.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
896
+ omlish-0.0.0.dev375.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
897
+ omlish-0.0.0.dev375.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
898
+ omlish-0.0.0.dev375.dist-info/RECORD,,
File without changes