omlish 0.0.0.dev285__py3-none-any.whl → 0.0.0.dev287__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.dev285'
2
- __revision__ = 'e95058137e322e352c2579564d041f21cdf1220c'
1
+ __version__ = '0.0.0.dev287'
2
+ __revision__ = 'b5569962801bfa1ab3457339502eb364ee51bb0e'
3
3
 
4
4
 
5
5
  #
@@ -106,6 +106,10 @@ class Project(ProjectBase):
106
106
  'duckdb ~= 1.2',
107
107
  ],
108
108
 
109
+ 'templates': [
110
+ 'jinja2 ~= 3.1',
111
+ ],
112
+
109
113
  'testing': [
110
114
  'pytest ~= 8.3',
111
115
  ],
@@ -70,11 +70,11 @@ class TreapMap(PersistentMap[K, V]):
70
70
  return n.value
71
71
 
72
72
  def __iter__(self) -> ta.Iterator[tuple[K, V]]:
73
- i = self.iterate()
73
+ i = self.items()
74
74
  while i.has_next():
75
75
  yield i.next()
76
76
 
77
- def iterate(self) -> 'TreapMapIterator[K, V]':
77
+ def items(self) -> 'TreapMapIterator[K, V]':
78
78
  i = TreapMapIterator(
79
79
  _st=[],
80
80
  _n=self._n,
@@ -84,7 +84,7 @@ class TreapMap(PersistentMap[K, V]):
84
84
  i._n = n.left # noqa
85
85
  return i
86
86
 
87
- def iterate_from(self, k: K) -> 'TreapMapIterator[K, V]':
87
+ def items_from(self, k: K) -> 'TreapMapIterator[K, V]':
88
88
  lst = treap.place(self._n, (k, None), self._c) # type: ignore
89
89
  i = TreapMapIterator(
90
90
  _st=lst,
@@ -92,7 +92,7 @@ class TreapMap(PersistentMap[K, V]):
92
92
  )
93
93
  return i
94
94
 
95
- def reverse_iterate(self) -> 'TreapMapReverseIterator[K, V]':
95
+ def items_desc(self) -> 'TreapMapReverseIterator[K, V]':
96
96
  i = TreapMapReverseIterator(
97
97
  _st=[],
98
98
  _n=self._n,
@@ -102,7 +102,7 @@ class TreapMap(PersistentMap[K, V]):
102
102
  i._n = n.right # noqa
103
103
  return i
104
104
 
105
- def reverse_iterate_from(self, k: K) -> 'TreapMapReverseIterator[K, V]':
105
+ def items_from_desc(self, k: K) -> 'TreapMapReverseIterator[K, V]':
106
106
  lst = treap.place(self._n, (k, None), self._c, desc=True) # type: ignore
107
107
  i = TreapMapReverseIterator(
108
108
  _st=lst,
@@ -15,7 +15,10 @@ class RankedSeq(ta.Sequence[T]):
15
15
  super().__init__()
16
16
 
17
17
  self._lst = list(it)
18
- self._ranks: ta.Mapping[T, int] = (IdentityKeyDict if identity else dict)((e, i) for i, e in enumerate(self._lst)) # noqa
18
+ self._ranks: ta.Mapping[T, int] = (
19
+ (IdentityKeyDict if identity else dict)
20
+ ((e, i) for i, e in enumerate(self._lst))
21
+ )
19
22
  if len(self._ranks) != len(self._lst):
20
23
  raise ValueError(f'{len(self._ranks)} != {len(self._lst)}')
21
24
 
@@ -51,7 +54,10 @@ class RankedSetSeq(ta.Sequence[ta.AbstractSet[T]]):
51
54
  super().__init__()
52
55
 
53
56
  self._lst = [(IdentitySet if identity else set)(e) for e in it]
54
- self._ranks: ta.Mapping[T, int] = (IdentityKeyDict if identity else dict)((e, i) for i, es in enumerate(self._lst) for e in es) # noqa
57
+ self._ranks: ta.Mapping[T, int] = (
58
+ (IdentityKeyDict if identity else dict)
59
+ ((e, i) for i, es in enumerate(self._lst) for e in es)
60
+ )
55
61
  if len(self._ranks) != sum(map(len, self._lst)):
56
62
  raise ValueError(f'{len(self._ranks)} != {sum(map(len, self._lst))}')
57
63
 
@@ -182,7 +182,7 @@ class SkipList(SortedCollection[T]):
182
182
  yield cur.value # type: ignore
183
183
  cur = cur.next[0]
184
184
 
185
- def riter(self, base: T | None = None) -> ta.Iterable[T]:
185
+ def iter_desc(self, base: T | None = None) -> ta.Iterable[T]:
186
186
  if base is not None:
187
187
  cur = self._find(base)
188
188
  while cur is not self._head and self._compare(base, cur.value) < 0: # type: ignore
@@ -51,7 +51,7 @@ class SortedCollection(lang.Abstract, ta.Collection[T]):
51
51
  raise NotImplementedError
52
52
 
53
53
  @abc.abstractmethod
54
- def riter(self, base: T | None = None) -> ta.Iterable[T]:
54
+ def iter_desc(self, base: T | None = None) -> ta.Iterable[T]:
55
55
  raise NotImplementedError
56
56
 
57
57
 
@@ -61,15 +61,15 @@ class SortedMapping(ta.Mapping[K, V]):
61
61
  raise NotImplementedError
62
62
 
63
63
  @abc.abstractmethod
64
- def ritems(self) -> ta.Iterator[tuple[K, V]]:
64
+ def items_desc(self) -> ta.Iterator[tuple[K, V]]:
65
65
  raise NotImplementedError
66
66
 
67
67
  @abc.abstractmethod
68
- def itemsfrom(self, key: K) -> ta.Iterator[tuple[K, V]]:
68
+ def items_from(self, key: K) -> ta.Iterator[tuple[K, V]]:
69
69
  raise NotImplementedError
70
70
 
71
71
  @abc.abstractmethod
72
- def ritemsfrom(self, key: K) -> ta.Iterator[tuple[K, V]]:
72
+ def items_from_desc(self, key: K) -> ta.Iterator[tuple[K, V]]:
73
73
  raise NotImplementedError
74
74
 
75
75
 
@@ -115,11 +115,11 @@ class SortedListDict(SortedMutableMapping[K, V]):
115
115
  def items(self) -> ta.Iterator[tuple[K, V]]: # type: ignore
116
116
  yield from self._impl.iter()
117
117
 
118
- def ritems(self) -> ta.Iterator[tuple[K, V]]:
119
- yield from self._impl.riter()
118
+ def items_desc(self) -> ta.Iterator[tuple[K, V]]:
119
+ yield from self._impl.iter_desc()
120
120
 
121
- def itemsfrom(self, key: K) -> ta.Iterator[tuple[K, V]]:
121
+ def items_from(self, key: K) -> ta.Iterator[tuple[K, V]]:
122
122
  yield from self._impl.iter((key, None))
123
123
 
124
- def ritemsfrom(self, key: K) -> ta.Iterator[tuple[K, V]]:
125
- yield from self._impl.riter((key, None))
124
+ def items_from_desc(self, key: K) -> ta.Iterator[tuple[K, V]]:
125
+ yield from self._impl.iter_desc((key, None))
@@ -36,6 +36,10 @@ class PlanOnly(ta.NamedTuple):
36
36
  b: bool
37
37
 
38
38
 
39
+ class Verbose(ta.NamedTuple):
40
+ b: bool
41
+
42
+
39
43
  @register_processor_type(priority=ProcessorPriority.GENERATION)
40
44
  class GeneratorProcessor(Processor):
41
45
  class Mode(abc.ABC):
@@ -67,10 +71,11 @@ class GeneratorProcessor(Processor):
67
71
  gp.ops(),
68
72
  )
69
73
 
70
- # print(gp.prepare().plans.render())
71
- # print()
72
- # print(comp.src)
73
- # print()
74
+ if (vo := gp._ctx.option(Verbose)) is not None and vo.b: # noqa
75
+ print(gp.prepare().plans.render(), file=sys.stderr)
76
+ print(file=sys.stderr)
77
+ print(comp.src, file=sys.stderr)
78
+ print(file=sys.stderr)
74
79
 
75
80
  ns: dict = {}
76
81
  ns.update(compiler.style.globals_ns()) # noqa
@@ -75,9 +75,12 @@ class StdFieldType(enum.Enum):
75
75
  INIT_VAR = dc._FIELD_INITVAR # type: ignore # noqa
76
76
 
77
77
 
78
+ STD_FIELD_TYPE_MAP: ta.Mapping[ta.Any, StdFieldType] = {v.value: v for v in StdFieldType}
79
+
80
+
78
81
  def std_field_type(f: dc.Field) -> StdFieldType:
79
82
  if (ft := getattr(f, '_field_type')) is not None:
80
- return StdFieldType(ft)
83
+ return STD_FIELD_TYPE_MAP[ft]
81
84
  else:
82
85
  return StdFieldType.INSTANCE
83
86
 
@@ -1,8 +1,7 @@
1
1
  import typing as ta
2
2
 
3
3
  from .. import concerns as _concerns # noqa # imported for registration
4
- from ..generation import processor as _generation_processor # noqa # imported for registration
5
- from ..generation.processor import PlanOnly
4
+ from ..generation import processor as gp
6
5
  from ..specs import ClassSpec
7
6
  from .base import ProcessingContext
8
7
  from .base import Processor
@@ -18,10 +17,15 @@ def drive_cls_processing(
18
17
  cs: ClassSpec,
19
18
  *,
20
19
  plan_only: bool = False,
20
+ verbose: bool = False,
21
21
  ) -> type:
22
22
  options: list[ta.Any] = []
23
23
  if plan_only:
24
- options.append(PlanOnly(True))
24
+ options.append(gp.PlanOnly(True))
25
+ if verbose:
26
+ options.append(gp.Verbose(True))
27
+
28
+ #
25
29
 
26
30
  ctx = ProcessingContext(
27
31
  cls,
@@ -1,3 +1,12 @@
1
- from .dispatch import Dispatcher # noqa
2
- from .functions import function # noqa
3
- from .methods import method # noqa
1
+ from .dispatch import ( # noqa
2
+ Dispatcher,
3
+ )
4
+
5
+ from .functions import ( # noqa
6
+ function,
7
+ )
8
+
9
+ from .methods import ( # noqa
10
+ install_method,
11
+ method,
12
+ )
@@ -6,6 +6,9 @@ from .dispatch import Dispatcher
6
6
  from .impls import get_impl_func_cls_set
7
7
 
8
8
 
9
+ ##
10
+
11
+
9
12
  # USE_EXTENSION = True
10
13
  USE_EXTENSION = False
11
14
 
omlish/dispatch/impls.py CHANGED
@@ -5,8 +5,8 @@ TODO:
5
5
  - multidispatch? never solved..
6
6
  - just generic on tuple[A0, A1, ...]
7
7
  """
8
+ import inspect
8
9
  import typing as ta
9
- import weakref
10
10
 
11
11
  from .. import c3
12
12
  from .. import check
@@ -19,15 +19,7 @@ T = ta.TypeVar('T')
19
19
  ##
20
20
 
21
21
 
22
- _IMPL_FUNC_CLS_SET_CACHE: ta.MutableMapping[ta.Callable, frozenset[type]] = weakref.WeakKeyDictionary()
23
-
24
-
25
- def get_impl_func_cls_set(func: ta.Callable) -> frozenset[type]:
26
- try:
27
- return _IMPL_FUNC_CLS_SET_CACHE[func]
28
- except KeyError:
29
- pass
30
-
22
+ def get_impl_func_cls_set(func: ta.Callable, *, arg_offset: int = 0) -> frozenset[type]:
31
23
  ann = getattr(func, '__annotations__', {})
32
24
  if not ann:
33
25
  raise TypeError(f'Invalid impl func: {func!r}')
@@ -39,8 +31,11 @@ def get_impl_func_cls_set(func: ta.Callable) -> frozenset[type]:
39
31
  return check.isinstance(a, type)
40
32
 
41
33
  # Exclude 'return' to support difficult to handle return types - they are unimportant.
42
- # TODO: only get hints for first arg - requires inspection, which requires chopping off `self`, which can be tricky.
43
- _, cls = next(iter(rfl.get_filtered_type_hints(func, exclude=['return']).items()))
34
+ th_dct = rfl.get_filtered_type_hints(func, exclude=['return'])
35
+
36
+ ps = inspect.signature(func).parameters
37
+ p = list(ps.values())[arg_offset]
38
+ cls = th_dct[p.name]
44
39
 
45
40
  rty = rfl.type_(cls)
46
41
  if isinstance(rty, rfl.Union):
@@ -48,10 +43,12 @@ def get_impl_func_cls_set(func: ta.Callable) -> frozenset[type]:
48
43
  else:
49
44
  ret = frozenset([erase(rty)])
50
45
 
51
- _IMPL_FUNC_CLS_SET_CACHE[func] = ret
52
46
  return ret
53
47
 
54
48
 
49
+ ##
50
+
51
+
55
52
  def find_impl(cls: type, registry: ta.Mapping[type, T]) -> T | None:
56
53
  mro = c3.compose_mro(cls, registry.keys())
57
54
 
@@ -18,14 +18,25 @@ from .impls import get_impl_func_cls_set
18
18
  T = ta.TypeVar('T')
19
19
 
20
20
 
21
+ ##
22
+
23
+
21
24
  class Method:
22
- def __init__(self, func: ta.Callable) -> None:
25
+ def __init__(
26
+ self,
27
+ func: ta.Callable,
28
+ *,
29
+ installable: bool = False,
30
+ ) -> None:
31
+ super().__init__()
32
+
23
33
  if not callable(func) and not hasattr(func, '__get__'): # type: ignore
24
34
  raise TypeError(f'{func!r} is not callable or a descriptor')
25
35
 
26
36
  self._func = func
37
+ self._installable = installable
27
38
 
28
- self._impls: ta.MutableSet[ta.Callable] = weakref.WeakSet()
39
+ self._impls: ta.MutableMapping[ta.Callable, frozenset[type] | None] = weakref.WeakKeyDictionary()
29
40
 
30
41
  # bpo-45678: special-casing for classmethod/staticmethod in Python <=3.9, as functools.update_wrapper doesn't
31
42
  # work properly in singledispatchmethod.__get__ if it is applied to an unbound classmethod/staticmethod
@@ -48,6 +59,15 @@ class Method:
48
59
 
49
60
  self._dispatch_func_cache_remove = dispatch_func_cache_remove
50
61
 
62
+ self._owner: type | None = None
63
+ self._name: str | None = None
64
+
65
+ def __set_name__(self, owner, name):
66
+ if self._owner is None:
67
+ self._owner = owner
68
+ if self._name is None:
69
+ self._name = name
70
+
51
71
  def __repr__(self) -> str:
52
72
  return f'<{type(self).__module__}.{type(self).__qualname__}:{self._func} at 0x{id(self):x}>'
53
73
 
@@ -61,7 +81,7 @@ class Method:
61
81
  setattr(wrapper, '__isabstractmethod__', self._is_abstractmethod) # noqa
62
82
  return wrapper
63
83
 
64
- def register(self, impl: T) -> T:
84
+ def register(self, impl: T, cls_set: frozenset[type] | None = None) -> T:
65
85
  # bpo-39679: in Python <= 3.9, classmethods and staticmethods don't inherit __annotations__ of the wrapped
66
86
  # function (fixed in 3.10+ as a side-effect of bpo-43682) but we need that for annotation-derived
67
87
  # singledispatches. So we add that just-in-time here.
@@ -70,7 +90,7 @@ class Method:
70
90
 
71
91
  check.callable(impl)
72
92
  if impl not in self._impls:
73
- self._impls.add(impl) # type: ignore
93
+ self._impls[impl] = cls_set # type: ignore
74
94
  self._dispatch_func_cache.clear()
75
95
 
76
96
  return impl
@@ -85,14 +105,23 @@ class Method:
85
105
  hash(att)
86
106
  except TypeError:
87
107
  continue
88
- if att in self._impls:
89
- try:
90
- ex_nam = seen[att]
91
- except KeyError:
92
- pass
93
- else:
94
- raise TypeError(f'Duplicate impl: {owner_cls} {instance_cls} {nam} {ex_nam}')
95
- disp.register(nam, get_impl_func_cls_set(att))
108
+
109
+ if att not in self._impls:
110
+ continue
111
+ cls_set = self._impls[att]
112
+
113
+ if cls_set is None:
114
+ cls_set = get_impl_func_cls_set(att, arg_offset=1)
115
+ self._impls[att] = cls_set
116
+
117
+ try:
118
+ ex_nam = seen[att]
119
+ except KeyError:
120
+ pass
121
+ else:
122
+ raise TypeError(f'Duplicate impl: {owner_cls} {instance_cls} {nam} {ex_nam}')
123
+
124
+ disp.register(nam, cls_set)
96
125
 
97
126
  return disp
98
127
 
@@ -106,9 +135,11 @@ class Method:
106
135
  def __call__(self, *args, **kwargs): # noqa
107
136
  if not args:
108
137
  raise TypeError(f'{func_name} requires at least 1 positional argument')
138
+
109
139
  if (impl_att := dispatch(type_(args[0]))) is not None:
110
140
  fn = getattr_(self, impl_att)
111
141
  return fn(*args, **kwargs)
142
+
112
143
  return base_func.__get__(self)(*args, **kwargs) # noqa
113
144
 
114
145
  self.update_wrapper(__call__)
@@ -148,5 +179,29 @@ class Method:
148
179
  return func.__get__(instance)(*args, **kwargs) # noqa
149
180
 
150
181
 
151
- def method(func): # noqa
152
- return Method(func)
182
+ def method(func=None, /, *, installable=False): # noqa
183
+ kw = dict(installable=installable)
184
+ if func is None:
185
+ return functools.partial(Method, **kw)
186
+ return Method(func, **kw)
187
+
188
+
189
+ def install_method(mth: ta.Any, *, name: str | None = None) -> ta.Callable[[T], T]:
190
+ mth = check.isinstance(mth, Method)
191
+ if not mth._installable: # noqa
192
+ raise TypeError(f'Method not installable: {mth}')
193
+
194
+ def inner(fn):
195
+ a = name
196
+ if a is None:
197
+ a = fn.__name__
198
+
199
+ cls: type = check.not_none(mth._owner) # noqa
200
+ check.arg(not hasattr(cls, a))
201
+ setattr(cls, a, fn)
202
+
203
+ mth.register(fn)
204
+
205
+ return fn
206
+
207
+ return inner
@@ -5,6 +5,9 @@ import typing as ta
5
5
  T = ta.TypeVar('T')
6
6
 
7
7
 
8
+ ##
9
+
10
+
8
11
  _MISSING = object()
9
12
 
10
13
 
@@ -9,6 +9,9 @@ import typing as ta
9
9
  T = ta.TypeVar('T')
10
10
 
11
11
 
12
+ ##
13
+
14
+
12
15
  def sliding_window(it: ta.Iterable[T], n: int) -> ta.Iterator[tuple[T, ...]]:
13
16
  # sliding_window('ABCDEFG', 4) -> ABCD BCDE CDEF DEFG
14
17
  iterator = iter(it)
omlish/iterators/tools.py CHANGED
@@ -11,6 +11,9 @@ T = ta.TypeVar('T')
11
11
  U = ta.TypeVar('U')
12
12
 
13
13
 
14
+ ##
15
+
16
+
14
17
  def unzip(it: ta.Iterable[T], width: int | None = None) -> list:
15
18
  if width is None:
16
19
  if not isinstance(it, PeekIterator):
@@ -7,6 +7,9 @@ from .. import lang
7
7
  T = ta.TypeVar('T')
8
8
 
9
9
 
10
+ ##
11
+
12
+
10
13
  @dc.dataclass()
11
14
  class UniqueStats:
12
15
  key: ta.Any
omlish/lang/__init__.py CHANGED
@@ -1,11 +1,14 @@
1
1
  from .attrs import ( # noqa
2
2
  AttrOps,
3
+ AttributePresentError,
3
4
  DictAttrOps,
4
5
  STD_ATTR_OPS,
6
+ SetAttrIfPresent,
5
7
  StdAttrOps,
6
8
  TRANSIENT_ATTR_OPS,
7
9
  TransientAttrOps,
8
10
  TransientDict,
11
+ set_attr,
9
12
  transient_delattr,
10
13
  transient_getattr,
11
14
  transient_setattr,
@@ -130,6 +133,7 @@ from .functions import ( # noqa
130
133
  Args,
131
134
  VoidError,
132
135
  as_async,
136
+ call_with,
133
137
  coalesce,
134
138
  constant,
135
139
  finally_,
omlish/lang/attrs.py CHANGED
@@ -1,8 +1,76 @@
1
1
  import abc
2
2
  import collections.abc
3
+ import functools
3
4
  import typing as ta
4
5
 
5
6
 
7
+ T = ta.TypeVar('T')
8
+
9
+
10
+ ##
11
+
12
+
13
+ class AttributePresentError(Exception):
14
+ pass
15
+
16
+
17
+ SetAttrIfPresent: ta.TypeAlias = ta.Literal['overwrite', 'leave', 'raise']
18
+
19
+
20
+ _NO_SET_ATTR_VALUE = object()
21
+
22
+
23
+ @ta.overload
24
+ def set_attr(
25
+ obj: ta.Any,
26
+ name: str,
27
+ *,
28
+ if_present: SetAttrIfPresent = 'overwrite',
29
+ ) -> ta.Callable[[T], T]:
30
+ ...
31
+
32
+
33
+ @ta.overload
34
+ def set_attr(
35
+ obj: ta.Any,
36
+ name: str,
37
+ value: T,
38
+ *,
39
+ if_present: SetAttrIfPresent = 'overwrite',
40
+ ) -> T:
41
+ ...
42
+
43
+
44
+ def set_attr(
45
+ obj,
46
+ name,
47
+ value=_NO_SET_ATTR_VALUE,
48
+ *,
49
+ if_present='overwrite',
50
+ ):
51
+ if value is _NO_SET_ATTR_VALUE:
52
+ return functools.partial(
53
+ set_attr,
54
+ obj,
55
+ name,
56
+ if_present=if_present,
57
+ )
58
+
59
+ if hasattr(obj, name):
60
+ if if_present == 'overwrite':
61
+ pass
62
+ elif if_present == 'leave':
63
+ return value
64
+ elif if_present == 'raise':
65
+ raise AttributePresentError(name)
66
+ else:
67
+ raise ValueError(if_present)
68
+
69
+ setattr(obj, name, value)
70
+
71
+ return value
72
+
73
+
6
74
  ##
7
75
 
8
76
 
@@ -24,7 +92,7 @@ class AttrOps(abc.ABC):
24
92
  raise NotImplementedError
25
93
 
26
94
 
27
- ##
95
+ #
28
96
 
29
97
 
30
98
  class StdAttrOps(AttrOps):
@@ -44,7 +112,7 @@ class StdAttrOps(AttrOps):
44
112
  STD_ATTR_OPS = StdAttrOps()
45
113
 
46
114
 
47
- ##
115
+ #
48
116
 
49
117
 
50
118
  class DictAttrOps(AttrOps):
omlish/lang/functions.py CHANGED
@@ -22,7 +22,14 @@ def is_lambda(f: ta.Any) -> bool:
22
22
  ##
23
23
 
24
24
 
25
- def maybe_call(obj: ta.Any, att: str, *args, default: ta.Any = None, **kwargs) -> ta.Any:
25
+ def call_with(fn: ta.Any, *args: ta.Any, **kwargs: ta.Any) -> ta.Callable[[T], T]:
26
+ def inner(obj):
27
+ fn(obj, *args, **kwargs)
28
+ return obj
29
+ return inner
30
+
31
+
32
+ def maybe_call(obj: ta.Any, att: str, *args, default: ta.Any = None, **kwargs: ta.Any) -> ta.Any:
26
33
  try:
27
34
  fn = getattr(obj, att)
28
35
  except AttributeError:
omlish/lang/maybes.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import abc
2
2
  import typing as ta
3
3
 
4
+ from ..lite.maybes import Maybe as _LiteMaybe
5
+ from ..lite.maybes import as_maybe as _as_lite_maybe
6
+
4
7
 
5
8
  T = ta.TypeVar('T')
6
9
  U = ta.TypeVar('U')
@@ -123,6 +126,9 @@ class _Maybe(Maybe[T], tuple):
123
126
  raise exception_supplier()
124
127
 
125
128
 
129
+ #
130
+
131
+
126
132
  def just(v: T) -> Maybe[T]:
127
133
  return tuple.__new__(_Maybe, (v,)) # noqa
128
134
 
@@ -138,3 +144,14 @@ def maybe(o: T | None) -> Maybe[T]:
138
144
  if o is None:
139
145
  return _empty # noqa
140
146
  return just(o)
147
+
148
+
149
+ ##
150
+
151
+
152
+ @_as_lite_maybe.register
153
+ def _(obj: Maybe) -> _LiteMaybe:
154
+ if obj.present:
155
+ return _LiteMaybe.just(obj.must())
156
+ else:
157
+ return _LiteMaybe.empty()
omlish/lite/maybes.py CHANGED
@@ -1,10 +1,14 @@
1
1
  import abc
2
+ import functools
2
3
  import typing as ta
3
4
 
4
5
 
5
6
  T = ta.TypeVar('T')
6
7
 
7
8
 
9
+ ##
10
+
11
+
8
12
  class Maybe(ta.Generic[T]):
9
13
  @property
10
14
  @abc.abstractmethod
@@ -43,3 +47,16 @@ class _Maybe(Maybe[T], tuple):
43
47
 
44
48
 
45
49
  Maybe._empty = tuple.__new__(_Maybe, ()) # noqa
50
+
51
+
52
+ ##
53
+
54
+
55
+ @functools.singledispatch
56
+ def as_maybe(obj: ta.Any) -> Maybe:
57
+ raise TypeError(obj)
58
+
59
+
60
+ @as_maybe.register
61
+ def _(obj: Maybe) -> Maybe:
62
+ return obj
omlish/text/minja.py CHANGED
@@ -5,12 +5,14 @@ TODO:
5
5
  - raw
6
6
  - blocks / inheritance
7
7
  """
8
+ import dataclasses as dc
8
9
  import io
9
10
  import re
10
11
  import typing as ta
11
12
 
12
13
  from ..lite.cached import cached_nullary
13
14
  from ..lite.check import check
15
+ from ..lite.maybes import Maybe
14
16
 
15
17
 
16
18
  ##
@@ -29,6 +31,34 @@ class MinjaTemplate:
29
31
  ##
30
32
 
31
33
 
34
+ @dc.dataclass(frozen=True)
35
+ class MinjaTemplateParam:
36
+ name: str
37
+ default: Maybe[ta.Any] = Maybe.empty()
38
+
39
+ def __post_init__(self) -> None:
40
+ check.arg(self.name.isidentifier())
41
+
42
+ @classmethod
43
+ def of(cls, obj: ta.Union[str, 'MinjaTemplateParam']) -> 'MinjaTemplateParam':
44
+ if isinstance(obj, MinjaTemplateParam):
45
+ return obj
46
+ elif isinstance(obj, str):
47
+ return MinjaTemplateParam.new(obj)
48
+ else:
49
+ raise TypeError(obj)
50
+
51
+ @classmethod
52
+ def new(cls, name: str, *defaults: ta.Any) -> 'MinjaTemplateParam':
53
+ dfl: Maybe[ta.Any]
54
+ if defaults:
55
+ [dv] = defaults
56
+ dfl = Maybe.just(dv)
57
+ else:
58
+ dfl = Maybe.empty()
59
+ return cls(name, dfl)
60
+
61
+
32
62
  class MinjaTemplateCompiler:
33
63
  """
34
64
  Compiles a template string into a Python function. The returned function takes a dictionary 'context' and returns
@@ -43,23 +73,28 @@ class MinjaTemplateCompiler:
43
73
  - {# comment #}: Ignored completely.
44
74
  """
45
75
 
46
- DEFAULT_INDENT: str = ' '
76
+ DEFAULT_INDENT: str = ' ' * 4
47
77
 
48
78
  def __init__(
49
79
  self,
50
80
  src: str,
51
- args: ta.Sequence[str],
81
+ params: ta.Sequence[ta.Union[str, MinjaTemplateParam]],
52
82
  *,
53
83
  indent: str = DEFAULT_INDENT,
54
84
  ) -> None:
55
85
  super().__init__()
56
86
 
87
+ check.not_isinstance(params, str)
88
+
57
89
  self._src = check.isinstance(src, str)
58
- self._args = check.not_isinstance(args, str)
90
+ self._params = [
91
+ MinjaTemplateParam.of(p)
92
+ for p in params
93
+ ]
94
+ check.unique(p.name for p in self._params)
59
95
  self._indent_str: str = check.non_empty_str(indent)
60
96
 
61
97
  self._stack: ta.List[ta.Literal['for', 'if']] = []
62
- self._lines: ta.List[str] = []
63
98
 
64
99
  #
65
100
 
@@ -124,41 +159,63 @@ class MinjaTemplateCompiler:
124
159
 
125
160
  _RENDER_FN_NAME = '__render'
126
161
 
162
+ class Rendered(ta.NamedTuple):
163
+ src: str
164
+ ns: ta.Dict[str, ta.Any]
165
+
127
166
  @cached_nullary
128
- def render(self) -> ta.Tuple[str, ta.Mapping[str, ta.Any]]:
167
+ def render(self) -> Rendered:
168
+ lines: ta.List[str] = []
169
+
170
+ ns: ta.Dict[str, ta.Any] = {
171
+ '__StringIO': io.StringIO,
172
+ }
173
+
129
174
  parts = self._split_tags(self._src)
130
175
 
131
- self._lines.append(f'def {self._RENDER_FN_NAME}({", ".join(self._args)}):')
132
- self._lines.append(self._indent('__output = __StringIO()'))
176
+ if not self._params:
177
+ lines.append(f'def {self._RENDER_FN_NAME}():')
178
+ else:
179
+ lines.append(f'def {self._RENDER_FN_NAME}(')
180
+ for p in self._params:
181
+ if p.default.present:
182
+ check.not_in(p.name, ns)
183
+ ns[p.name] = p.default.must()
184
+ lines.append(self._indent(f'{p.name}={p.name},'))
185
+ else:
186
+ lines.append(self._indent(f'{p.name},'))
187
+ lines.append('):')
188
+
189
+ lines.append(self._indent('__output = __StringIO()'))
133
190
 
134
191
  for g, s in parts:
135
192
  if g == '{':
136
193
  expr = s.strip()
137
- self._lines.append(self._indent(f'__output.write(str({expr}))'))
194
+ lines.append(self._indent(f'__output.write(str({expr}))'))
138
195
 
139
196
  elif g == '%':
140
197
  stmt = s.strip()
141
198
 
142
199
  if stmt.startswith('for '):
143
- self._lines.append(self._indent(stmt + ':'))
200
+ lines.append(self._indent(stmt + ':'))
144
201
  self._stack.append('for')
145
202
  elif stmt.startswith('endfor'):
146
203
  check.equal(self._stack.pop(), 'for')
147
204
 
148
205
  elif stmt.startswith('if '):
149
- self._lines.append(self._indent(stmt + ':'))
206
+ lines.append(self._indent(stmt + ':'))
150
207
  self._stack.append('if')
151
208
  elif stmt.startswith('elif '):
152
209
  check.equal(self._stack[-1], 'if')
153
- self._lines.append(self._indent(stmt + ':', -1))
210
+ lines.append(self._indent(stmt + ':', -1))
154
211
  elif stmt.strip() == 'else':
155
212
  check.equal(self._stack[-1], 'if')
156
- self._lines.append(self._indent('else:', -1))
213
+ lines.append(self._indent('else:', -1))
157
214
  elif stmt.startswith('endif'):
158
215
  check.equal(self._stack.pop(), 'if')
159
216
 
160
217
  else:
161
- self._lines.append(self._indent(stmt))
218
+ lines.append(self._indent(stmt))
162
219
 
163
220
  elif g == '#':
164
221
  pass
@@ -166,20 +223,16 @@ class MinjaTemplateCompiler:
166
223
  elif not g:
167
224
  if s:
168
225
  safe_text = s.replace('"""', '\\"""')
169
- self._lines.append(self._indent(f'__output.write("""{safe_text}""")'))
226
+ lines.append(self._indent(f'__output.write("""{safe_text}""")'))
170
227
 
171
228
  else:
172
229
  raise KeyError(g)
173
230
 
174
231
  check.empty(self._stack)
175
232
 
176
- self._lines.append(self._indent('return __output.getvalue()'))
177
-
178
- ns = {
179
- '__StringIO': io.StringIO,
180
- }
233
+ lines.append(self._indent('return __output.getvalue()'))
181
234
 
182
- return ('\n'.join(self._lines), ns)
235
+ return self.Rendered('\n'.join(lines), ns)
183
236
 
184
237
  #
185
238
 
@@ -200,12 +253,12 @@ class MinjaTemplateCompiler:
200
253
 
201
254
  @cached_nullary
202
255
  def compile(self) -> MinjaTemplate:
203
- render_src, render_ns = self.render()
256
+ rendered = self.render()
204
257
 
205
258
  render_fn = self._make_fn(
206
259
  self._RENDER_FN_NAME,
207
- render_src,
208
- render_ns,
260
+ rendered.src,
261
+ rendered.ns,
209
262
  )
210
263
 
211
264
  return MinjaTemplate(render_fn)
@@ -214,8 +267,11 @@ class MinjaTemplateCompiler:
214
267
  ##
215
268
 
216
269
 
217
- def compile_minja_template(src: str, args: ta.Sequence[str] = ()) -> MinjaTemplate:
218
- return MinjaTemplateCompiler(src, args).compile()
270
+ def compile_minja_template(
271
+ src: str,
272
+ params: ta.Sequence[ta.Union[str, MinjaTemplateParam]] = (),
273
+ ) -> MinjaTemplate:
274
+ return MinjaTemplateCompiler(src, params).compile()
219
275
 
220
276
 
221
277
  def render_minja_template(src: str, **kwargs: ta.Any) -> str:
@@ -0,0 +1,116 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import string
4
+ import typing as ta
5
+
6
+ from .. import lang
7
+ from .minja import MinjaTemplate
8
+ from .minja import MinjaTemplateParam
9
+ from .minja import compile_minja_template
10
+
11
+
12
+ if ta.TYPE_CHECKING:
13
+ import jinja2
14
+ else:
15
+ jinja2 = lang.proxy_import('jinja2')
16
+
17
+
18
+ ##
19
+
20
+
21
+ class Templater(lang.Abstract):
22
+ @dc.dataclass(frozen=True)
23
+ class Context:
24
+ env: ta.Mapping[str, ta.Any] | None = None
25
+
26
+ @abc.abstractmethod
27
+ def render(self, ctx: Context) -> str:
28
+ raise NotImplementedError
29
+
30
+
31
+ ##
32
+
33
+
34
+ @dc.dataclass(frozen=True)
35
+ class FormatTemplater(Templater):
36
+ """https://docs.python.org/3/library/string.html#format-specification-mini-language"""
37
+
38
+ fmt: str
39
+
40
+ def render(self, ctx: Templater.Context) -> str:
41
+ return self.fmt.format(**(ctx.env or {}))
42
+
43
+
44
+ ##
45
+
46
+
47
+ @dc.dataclass(frozen=True)
48
+ class Pep292Templater(Templater):
49
+ """https://peps.python.org/pep-0292/"""
50
+
51
+ tmpl: string.Template
52
+
53
+ def render(self, ctx: Templater.Context) -> str:
54
+ return self.tmpl.substitute(ctx.env or {})
55
+
56
+ @classmethod
57
+ def from_string(cls, src: str) -> 'Pep292Templater':
58
+ return cls(string.Template(src))
59
+
60
+ ##
61
+
62
+
63
+ @dc.dataclass(frozen=True)
64
+ class MinjaTemplater(Templater):
65
+ tmpl: MinjaTemplate
66
+
67
+ ENV_IDENT: ta.ClassVar[str] = 'env'
68
+
69
+ def render(self, ctx: Templater.Context) -> str:
70
+ return self.tmpl(**{self.ENV_IDENT: ctx.env or {}})
71
+
72
+ @classmethod
73
+ def from_string(
74
+ cls,
75
+ src: str,
76
+ **ns: ta.Any,
77
+ ) -> 'MinjaTemplater':
78
+ tmpl = compile_minja_template(
79
+ src,
80
+ [
81
+ MinjaTemplateParam(cls.ENV_IDENT),
82
+ *[
83
+ MinjaTemplateParam.new(k, v)
84
+ for k, v in ns.items()
85
+ ],
86
+ ],
87
+ )
88
+
89
+ return cls(tmpl)
90
+
91
+
92
+ ##
93
+
94
+
95
+ @dc.dataclass(frozen=True)
96
+ class JinjaTemplater(Templater):
97
+ tmpl: jinja2.Template
98
+
99
+ def render(self, ctx: Templater.Context) -> str:
100
+ return self.tmpl.render(**(ctx.env or {}))
101
+
102
+ @classmethod
103
+ def from_string(
104
+ cls,
105
+ src: str,
106
+ *,
107
+ env: jinja2.Environment | None = None,
108
+ **kwargs: ta.Any,
109
+ ) -> 'JinjaTemplater':
110
+ if env is None:
111
+ env = jinja2.Environment()
112
+ tmpl = env.from_string(src)
113
+ return cls(
114
+ tmpl,
115
+ **kwargs,
116
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev285
3
+ Version: 0.0.0.dev287
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -42,6 +42,7 @@ Requires-Dist: asyncpg~=0.30; extra == "all"
42
42
  Requires-Dist: apsw~=3.49; extra == "all"
43
43
  Requires-Dist: sqlean.py~=3.47; extra == "all"
44
44
  Requires-Dist: duckdb~=1.2; extra == "all"
45
+ Requires-Dist: jinja2~=3.1; extra == "all"
45
46
  Requires-Dist: pytest~=8.3; extra == "all"
46
47
  Requires-Dist: anyio~=4.9; extra == "all"
47
48
  Requires-Dist: sniffio~=1.3; extra == "all"
@@ -88,6 +89,8 @@ Requires-Dist: asyncpg~=0.30; extra == "sqldrivers"
88
89
  Requires-Dist: apsw~=3.49; extra == "sqldrivers"
89
90
  Requires-Dist: sqlean.py~=3.47; extra == "sqldrivers"
90
91
  Requires-Dist: duckdb~=1.2; extra == "sqldrivers"
92
+ Provides-Extra: templates
93
+ Requires-Dist: jinja2~=3.1; extra == "templates"
91
94
  Provides-Extra: testing
92
95
  Requires-Dist: pytest~=8.3; extra == "testing"
93
96
  Provides-Extra: plus
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=pjGUyLHaoWpPqRP3jz2u1fC1qoRc2lvrEcpU_Ax2tdg,8253
2
- omlish/__about__.py,sha256=HdBvgJvGybfoC0HT12EdWHXOYyK8r8Y4UmpMPI4FqC0,3380
2
+ omlish/__about__.py,sha256=i-F-KstjC74KdNV4E5DhZUVUlbu5XeNyaWrDZaDrQtw,3444
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
@@ -153,7 +153,7 @@ omlish/collections/hasheq.py,sha256=swOBPEnU_C0SU3VqWJX9mr0BfZLD0A-4Ke9Vahj3fE4,
153
153
  omlish/collections/identity.py,sha256=xtoczgBPYzr6r2lJS-eti2kEnN8rVDvNGDCG3TA6vRo,3405
154
154
  omlish/collections/mappings.py,sha256=iXb7oq1rCQak0KgzblgrzWCJLrkfJAYHFvl9lprOVUI,2804
155
155
  omlish/collections/ordered.py,sha256=7zTbrAt12rf6i33XHkQERKar258fJacaw_WbtGEBgWo,2338
156
- omlish/collections/ranked.py,sha256=rg6DL36oOUiG5JQEAkGnT8b6f9mSndQlIovtt8GQj_w,2229
156
+ omlish/collections/ranked.py,sha256=McB8C2UQfUvrbmxGTpBz1-EZuyCLkBFtktzncMdt8_Y,2287
157
157
  omlish/collections/unmodifiable.py,sha256=X7kKhPFdZF4m28SfLDxZL-riWlhbZffdPv35aTP30YM,4753
158
158
  omlish/collections/utils.py,sha256=voqecDPFuqSLN8IwJDKyy-HnecEU4JYOXxd1HAaEuMY,2900
159
159
  omlish/collections/cache/__init__.py,sha256=D1gO71VcwxFTZP9gAc9isHfg_TEdalwhsJcgGLvS9hg,233
@@ -170,10 +170,10 @@ omlish/collections/kv/wrappers.py,sha256=nUtoiArK1Fu2TgxFrqZU69bEoAxKJ_nA7qMeeih
170
170
  omlish/collections/persistent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
171
171
  omlish/collections/persistent/persistent.py,sha256=6CuzeZjZMqBLflU-CuWu32SrtZXFrJOV-H_UUuYoTkg,880
172
172
  omlish/collections/persistent/treap.py,sha256=o7vhnn8kHde_Plk0sojlJZkkKwp8hpj2IekhcvbYqHs,7739
173
- omlish/collections/persistent/treapmap.py,sha256=dBCZHouClUhvUBnSR5k97AAu09EQVRWdUOwwc1CHPmg,5809
173
+ omlish/collections/persistent/treapmap.py,sha256=lLxjHeR5pFFperrlw3tKIpe20xtszETG3_26EMAwhB4,5793
174
174
  omlish/collections/sorted/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
175
- omlish/collections/sorted/skiplist.py,sha256=tybcD8OslDBCc86Z1qKb4GRkbhMY26qorPzPIeqFmKk,6031
176
- omlish/collections/sorted/sorted.py,sha256=UhjJce7ulO_FUB6dFOhKpEg9Bcgxr2m6VXeCNtbLjSM,3324
175
+ omlish/collections/sorted/skiplist.py,sha256=4BqAAxOAb0VM33bny-rdrFM9d2jVbyxsN7yHz3jrqG4,6035
176
+ omlish/collections/sorted/sorted.py,sha256=IWQm5uyxuskCoX1fCtlBcYlGKdyxQQNMwnTj__eoZS8,3356
177
177
  omlish/concurrent/__init__.py,sha256=9p-s8MvBEYDqHIoYU3OYoe-Nni22QdkW7nhZGEukJTM,197
178
178
  omlish/concurrent/executors.py,sha256=mF-rjJWzNFxwB1-_H7rHuwoImpl4FtNM6W3wcqM3NEE,1291
179
179
  omlish/concurrent/futures.py,sha256=870tx8ELI8THvMnTrQoYIlEFKO9hM4KUrlehckJqKBU,4238
@@ -206,7 +206,7 @@ omlish/dataclasses/__init__.py,sha256=xo5rJYrQ2JE5jcX4fHv5e9bC6RIXFd-K8yurUP20oO
206
206
  omlish/dataclasses/debug.py,sha256=giBiv6aXvX0IagwNCW64qBzNjfOFr3-VmgDy_KYlb-k,29
207
207
  omlish/dataclasses/errors.py,sha256=tyv3WR6az66uGGiq9FIuCHvy1Ef-G7zeMY7mMG6hy2Y,2527
208
208
  omlish/dataclasses/inspect.py,sha256=BlpPghVCU3w_YDnONEqqE99YHzJM2q3eoqe39YX25Ko,4596
209
- omlish/dataclasses/internals.py,sha256=qFSDQzBtukvYO5QMoZx83nwyNA53S-8jJyB9l1e1oZ0,2975
209
+ omlish/dataclasses/internals.py,sha256=vIGCZnStgD3ef4drYRtVOrxhxmAPa0vJpo4pXcDcQvM,3073
210
210
  omlish/dataclasses/reflection.py,sha256=5N4acL27xwSnQJvoks6ts2JseGfwL_P9D2gM9vqtAFM,2243
211
211
  omlish/dataclasses/specs.py,sha256=h9qD2xAijKAS95Iw3GUPx3mj1peoUOlgh7aXtqVfmDU,6073
212
212
  omlish/dataclasses/utils.py,sha256=_1B6tEkkcxHVoLBEo34ebR_0E-iZW7W0zFTo27EdSqY,1854
@@ -249,7 +249,7 @@ omlish/dataclasses/generation/mangling.py,sha256=AUtTMjAAuQ_kUfE-CypnpRsiM10EAkM
249
249
  omlish/dataclasses/generation/manifests.py,sha256=tRLO59eysC13z_lbPxaErPy-xM3BIBRpj8MhmbYoGnE,309
250
250
  omlish/dataclasses/generation/ops.py,sha256=dWse_SsoXtpDqXzDPJOqBeb9G5F7RrEChfGl4iTL0QE,1718
251
251
  omlish/dataclasses/generation/plans.py,sha256=c3TRwa20quN-DrM-eI2KoTG4XVyL3n12RcwZoJxEgB0,536
252
- omlish/dataclasses/generation/processor.py,sha256=Va-8RV19jIzqreXCUeP4nFMPglAfIxeIaGOzgYTU_IY,4496
252
+ omlish/dataclasses/generation/processor.py,sha256=_rt_GHdx_pi5seRMzIu6DvLEx4MYGsG8CKykxqDiu1s,4689
253
253
  omlish/dataclasses/generation/registry.py,sha256=_Cfvs9QY6f8Pvf9cKUi-OTI-qaDem3z5jcWkSDNk3kQ,1006
254
254
  omlish/dataclasses/generation/utils.py,sha256=u6DWRvzxgqrxqyfAGzNOyDvnOJYdMTYyZ7l1oOOiXrI,2089
255
255
  omlish/dataclasses/metaclass/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -259,7 +259,7 @@ omlish/dataclasses/metaclass/meta.py,sha256=0MQG7Sl1y8QccGRgLOC0VoLDTMBG6VoyT-wZ
259
259
  omlish/dataclasses/metaclass/specs.py,sha256=djzkHK95h06Cgrx8rr3SGkv2gOvL-fPOhWOBWbbN8h4,789
260
260
  omlish/dataclasses/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
261
261
  omlish/dataclasses/processing/base.py,sha256=wkPDfeojZ2mg72yifMW7VNpJBEgEbaC3Qr_bd8_aV5E,1827
262
- omlish/dataclasses/processing/driving.py,sha256=k3SrbY1fAvRegYs_OorOeh3f5j6O7NEmL7yL7RnwVic,1033
262
+ omlish/dataclasses/processing/driving.py,sha256=AwT4kvh_GdSDfYVLBjBdkVmbO7U5LK95A_K3qTK01MY,1031
263
263
  omlish/dataclasses/processing/priority.py,sha256=57jdwUZ29Z9ZtKWIzyar4raNLHNrWd0c29ZLv2hcfjo,260
264
264
  omlish/dataclasses/processing/registry.py,sha256=iNDHwW1gsq8gTMi4XvNBBzab0OYdSKvDLQw9gCBt2FI,1884
265
265
  omlish/dataclasses/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -286,13 +286,13 @@ omlish/diag/replserver/__init__.py,sha256=uLo6V2aQ29v9z3IMELlPDSlG3_2iOT4-_X8Vni
286
286
  omlish/diag/replserver/__main__.py,sha256=LmU41lQ58bm1h4Mx7S8zhE_uEBSC6kPcp9mn5JRpulA,32
287
287
  omlish/diag/replserver/console.py,sha256=XzBDVhYlr8FY6ym4OwoaIHuFOHnGK3dTYlMDIOMUUlA,7410
288
288
  omlish/diag/replserver/server.py,sha256=pqJZXKnD-Cc03hnL_FNqGYaW9kH-IM3e11vCyDAcBD8,5374
289
- omlish/dispatch/__init__.py,sha256=GsiGJ91NKiQptSROtnCSkrZExBkvfDwYvdoTu5dBqF0,117
289
+ omlish/dispatch/__init__.py,sha256=UwVT6SSk5HKk9xF9vc_UJD8BDSIQWSw98Ldx-G-6PC0,166
290
290
  omlish/dispatch/_dispatch2.py,sha256=70k1tKKvuhxtAu6v4skECfHKIKVWrmlt7G_JKLUsKEs,1966
291
291
  omlish/dispatch/_dispatch3.py,sha256=9Zjd7bINAC3keiaBdssc4v5dY0-8OI6XooV2DR9U7Z0,2818
292
292
  omlish/dispatch/dispatch.py,sha256=KA5l49AiGLRjp4J7RDJW9RiDp9WUD1ewR1AOPEF8g38,3062
293
- omlish/dispatch/functions.py,sha256=8Qvj62XKn9SKfiqoVQdBD3wVRE4JUWpZDqs0oouuDIw,1519
294
- omlish/dispatch/impls.py,sha256=JP67zSvDWG4XFOmKPrhWHxRV9WnyAPAsjQHvbmEJh4c,2136
295
- omlish/dispatch/methods.py,sha256=oFtKljPnTMG0DVUBdEkf81-0RzEbgHhS4J_63HYJfKU,5492
293
+ omlish/dispatch/functions.py,sha256=cwNzGIg2ZIalEgn9I03cnJVbMTHjWloyDTaowlO3UPs,1524
294
+ omlish/dispatch/impls.py,sha256=K_okKvpZml4NkTHJmTVyMQSrIaIJcqTEgkreGwukaOw,1895
295
+ omlish/dispatch/methods.py,sha256=7jNKvwGNW9CJRTMDZyedmrWJN7Y9-ToI7YOAQ8VJu5Q,6786
296
296
  omlish/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
297
297
  omlish/docker/all.py,sha256=xXRgJgLGPwAtr7bDMJ_Dp9jTfOwfGvohNhc6LsoELJc,514
298
298
  omlish/docker/cli.py,sha256=gtb9kitVfGnd4cr587NsVVk8D5Ok5y5SAsqD_SwGrSA,2565
@@ -455,12 +455,12 @@ omlish/io/fdio/kqueue.py,sha256=YgGBQibkAUYODYDiGl7Enjtx1oQsJXuDsBLBXgqlLQw,3832
455
455
  omlish/io/fdio/manager.py,sha256=q4wWf7nKrNtjx6yPEvrVnFt4UtK_BTvVlquEGw7poEo,1250
456
456
  omlish/io/fdio/pollers.py,sha256=yNadAt3W5wd90PFmd3vD77bq5QwoVb2A6SM2JjZpKRs,5507
457
457
  omlish/iterators/__init__.py,sha256=yMavf5FofiS1EU4UFuWPXiFZ03W0H-y7MuMxW8FUaEE,358
458
- omlish/iterators/iterators.py,sha256=iTQQwBE6Wzoy36dnbPIws17zbjE3zNN4KwVw4Fzh-gY,3154
459
- omlish/iterators/recipes.py,sha256=53mkexitMhkwXQZbL6DrhpT0WePQ_56uXd5Jaw3DfzI,467
460
- omlish/iterators/tools.py,sha256=Pi4ybXytUXVZ3xwK89xpPImQfYYId9p1vIFQvVqVLqA,2551
461
- omlish/iterators/unique.py,sha256=0jAX3kwzVfRNhe0Tmh7kVP_Q2WBIn8POo_O-rgFV0rQ,1390
462
- omlish/lang/__init__.py,sha256=2lnwSSQ_AFbsi5OJLAAWRWJ5xY2kSLWdIzZMW6mfr4s,5238
463
- omlish/lang/attrs.py,sha256=fofCKN0X8TMu1yGqHpLpNLih9r9HWl3D3Vn3b6O791w,3891
458
+ omlish/iterators/iterators.py,sha256=RxW35yQ5ed8vBQ22IqpDXFx-i5JiLQdp7-pkMZXhJJ8,3159
459
+ omlish/iterators/recipes.py,sha256=wOwOZg-zWG9Zc3wcAxJFSe2rtavVBYwZOfG09qYEx_4,472
460
+ omlish/iterators/tools.py,sha256=c4hArZEVV8y9_dFfmRwakusv1cWJLT4MkTkGRjnGN5U,2556
461
+ omlish/iterators/unique.py,sha256=Nw0pSaNEcHAkve0ugfLPvJcirDOn9ECyC5wIL8JlJKI,1395
462
+ omlish/lang/__init__.py,sha256=hv6BlnJfD7gHFyOGXl1lgbo3vHkmsivqy462JwA04tM,5316
463
+ omlish/lang/attrs.py,sha256=i7euRF81uNF8QDmUVXSK_BtqLGshaMi4VVdUnMjiMwg,5050
464
464
  omlish/lang/clsdct.py,sha256=HAGIvBSbCefzRjXriwYSBLO7QHKRv2UsE78jixOb-fA,1828
465
465
  omlish/lang/collections.py,sha256=aGi0j6VzVe2nz4l357Y4RD5_XNl8OJbmM5qM6BclrrY,1895
466
466
  omlish/lang/comparison.py,sha256=MOwEG0Yny-jBPHO9kQto9FSRyeNpQW24UABsghkrHxY,1356
@@ -468,11 +468,11 @@ omlish/lang/contextmanagers.py,sha256=UPH6daYwSP9cH5AfSVsJyEHk1UURMGhVPM5ZRhp_Hv
468
468
  omlish/lang/datetimes.py,sha256=mrTtA67JYpfQwSlzdPcBtvm6dAyYM_dXNnlxFwFQH0M,228
469
469
  omlish/lang/descriptors.py,sha256=zBtgO9LjdSTGHNUgiIqswh78WOVoGH6KzS0NbgB1Wls,6572
470
470
  omlish/lang/enums.py,sha256=F9tflHfaAoV2MpyuhZzpfX9-H55M3zNa9hCszsngEo8,111
471
- omlish/lang/functions.py,sha256=kSdVUJZmRcncMdU43rcQLZ5E2NMcRz6X6pppwLNHiWk,5762
471
+ omlish/lang/functions.py,sha256=bg2IxTFmd_J22qF3XWIRBvSIpLwPRiH_KxGXcm0QVrc,5944
472
472
  omlish/lang/generators.py,sha256=5tbjVAywiZH6oAdj1sJLRMtIkC9y3rAkecLT7Z3m7_g,5251
473
473
  omlish/lang/imports.py,sha256=aC1u2eTrxHhhxShKbJvXswe3aJ7K76vT4YK8LrsvRWU,10476
474
474
  omlish/lang/iterables.py,sha256=HOjcxOwyI5bBApDLsxRAGGhTTmw7fdZl2kEckxRVl-0,1994
475
- omlish/lang/maybes.py,sha256=IPlfurHCebyDamB2scqP-JmUODN1TWr7ZL1T7qz6Jzo,3421
475
+ omlish/lang/maybes.py,sha256=pb1YrxmpXy-hWKmWR89GxXqZq1MoUD1uuTaTX30peh0,3697
476
476
  omlish/lang/objects.py,sha256=q1T26cxLkejU5XMl5iEVC9IIhjib0VBpe7JCo2bz2Ws,5411
477
477
  omlish/lang/outcomes.py,sha256=mpFy_VoM-b74L1aCFsjsZVUHx_icZ1AHMOKeVesjOp4,8628
478
478
  omlish/lang/params.py,sha256=QmNVBfJsfxjDG5ilDPgHV7sK4UwRztkSQdLTo0umb8I,6648
@@ -508,7 +508,7 @@ omlish/lite/inject.py,sha256=-tTsOqqef-Ix5Tgl2DP_JAsNWJQDFUptERl3lk14Uzs,29007
508
508
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
509
509
  omlish/lite/logs.py,sha256=CWFG0NKGhqNeEgryF5atN2gkPYbUdTINEw_s1phbINM,51
510
510
  omlish/lite/marshal.py,sha256=4DCbLoimLwJakihpvMjJ_kpc7v9aZQY8P3-gkoqEGUE,18471
511
- omlish/lite/maybes.py,sha256=7OlHJ8Q2r4wQ-aRbZSlJY7x0e8gDvufFdlohGEIJ3P4,833
511
+ omlish/lite/maybes.py,sha256=I7b2dJFkDll4B_QDlI6iKJG8fy91L6_Tj_s5eB3KI7Q,1013
512
512
  omlish/lite/pycharm.py,sha256=pUOJevrPClSqTCEOkQBO11LKX2003tfDcp18a03QFrc,1163
513
513
  omlish/lite/reflect.py,sha256=pzOY2PPuHH0omdtglkN6DheXDrGopdL3PtTJnejyLFU,2189
514
514
  omlish/lite/reprs.py,sha256=QI5VBtvq_TW1TojWL25c04QfOABLi8Smt5jc5J-bArc,2008
@@ -816,9 +816,10 @@ omlish/text/glyphsplit.py,sha256=HI8TWDUaF_tJG5RnIdIjtUH_lYnjYZ7KZBANSxOXGZc,380
816
816
  omlish/text/indent.py,sha256=LOQgHskHMLVrRC6HLL9uIWay517dpvPEYQK0Igm-wm8,1341
817
817
  omlish/text/linecache.py,sha256=hRYlEhD63ZfA6_ZOTkQIcnON-3W56QMAhcG3vEJqj9M,1858
818
818
  omlish/text/mangle.py,sha256=d7j2_HPxoxF5VrLD1TShOSfnhBLH_-lQe_OH2kl0yHg,2790
819
- omlish/text/minja.py,sha256=jZC-fp3Xuhx48ppqsf2Sf1pHbC0t8XBB7UpUUoOk2Qw,5751
819
+ omlish/text/minja.py,sha256=e7XiY8IvF4Df-7hxhdC_qizHm9T4SIbFJR7enUg_Zmc,7252
820
820
  omlish/text/parts.py,sha256=Q9NvoyEGQKIWgiPD4D_Qc66cWAuyEKE033dT9m7c3Wk,6662
821
821
  omlish/text/random.py,sha256=8feS5JE_tSjYlMl-lp0j93kCfzBae9AM2cXlRLebXMA,199
822
+ omlish/text/templating.py,sha256=UyYIs0wMigCDws0XfO3mYkJFH0-tO1IBaxWL7v2J3pw,2417
822
823
  omlish/text/go/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
823
824
  omlish/text/go/quoting.py,sha256=N9EYdnFdEX_A8fOviH-1w4jwV3XOQ7VU2WsoUNubYVY,9137
824
825
  omlish/typedvalues/__init__.py,sha256=c3IQmRneMmH6JRcafprqmBILWD89b-IyIll6MgahGCI,562
@@ -829,9 +830,9 @@ omlish/typedvalues/holder.py,sha256=4SwRezsmuDDEO5gENGx8kTm30pblF5UktoEAu02i-Gk,
829
830
  omlish/typedvalues/marshal.py,sha256=Rr-4DZ90BoD5Z9gT18QhZH2yMA3-gPJBK8GUrGkp8VA,4245
830
831
  omlish/typedvalues/reflect.py,sha256=y_7IY8_4cLVRvD3ug-_-cDaO5RtzC1rLVFzkeAPALf8,683
831
832
  omlish/typedvalues/values.py,sha256=Q_5IiQM3Vka4wGVd-scaslb4-oCMjFcIOksIrKE-JIM,1229
832
- omlish-0.0.0.dev285.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
833
- omlish-0.0.0.dev285.dist-info/METADATA,sha256=i6luxJ7Bytyt00Qr_PwO9pmz0PIFJcjUNOnpdl-6g0U,4198
834
- omlish-0.0.0.dev285.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
835
- omlish-0.0.0.dev285.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
836
- omlish-0.0.0.dev285.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
837
- omlish-0.0.0.dev285.dist-info/RECORD,,
833
+ omlish-0.0.0.dev287.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
834
+ omlish-0.0.0.dev287.dist-info/METADATA,sha256=2lOtfXV-sPk2hcxlOFk_TruhqdiAsg_MTdrhMSxI0o0,4316
835
+ omlish-0.0.0.dev287.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
836
+ omlish-0.0.0.dev287.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
837
+ omlish-0.0.0.dev287.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
838
+ omlish-0.0.0.dev287.dist-info/RECORD,,