omlish 0.0.0.dev452__py3-none-any.whl → 0.0.0.dev453__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.dev452'
2
- __revision__ = '85534912bc13f26aa289d4d012b6bf27695609c5'
1
+ __version__ = '0.0.0.dev453'
2
+ __revision__ = '66cf981e193780bd726e2a5ea06dd1ac590f7298'
3
3
 
4
4
 
5
5
  #
@@ -6,6 +6,11 @@ from .. import lang as _lang
6
6
  with _lang.auto_proxy_init(globals()):
7
7
  ##
8
8
 
9
+ from .attrregistry import ( # noqa
10
+ AttrRegistry,
11
+ AttrRegistryCache,
12
+ )
13
+
9
14
  from .bimap import ( # noqa
10
15
  BiMap,
11
16
 
@@ -64,9 +69,10 @@ with _lang.auto_proxy_init(globals()):
64
69
  from . import kv # noqa
65
70
 
66
71
  from .mappings import ( # noqa
72
+ DynamicTypeMap,
67
73
  MissingDict,
68
74
  TypeMap,
69
- DynamicTypeMap,
75
+ dict_factory,
70
76
  guarded_map_update,
71
77
  multikey_dict,
72
78
  )
@@ -0,0 +1,182 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+ import weakref
4
+
5
+ from .. import check
6
+ from .. import lang
7
+ from .mappings import dict_factory
8
+
9
+
10
+ T = ta.TypeVar('T')
11
+ K = ta.TypeVar('K')
12
+ V = ta.TypeVar('V')
13
+
14
+
15
+ ##
16
+
17
+
18
+ class AttrRegistry(ta.Generic[K, V]):
19
+ """
20
+ MRO-honoring class member registry. There are many ways to do this, and this one is attr name based: a class is
21
+ considered to have objects registered to it based on whether or not they are accessible by a non-shadowed,
22
+ MRO-resolved named attribute on that class.
23
+
24
+ Care must be taken when overriding registered objects from superclasses in subclasses - shadowing the attribute name
25
+ of the superclass member will not automatically register the new member with the same name to the registry - it must
26
+ be explicitly registered itself. This is a feature, allowing for selective de-registration of objects in subclasses
27
+ via name shadowing.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ *,
33
+ requires_override: bool = False,
34
+ is_override: ta.Callable[[ta.Any], bool] | None = None,
35
+ forbid_duplicates: bool = False,
36
+ identity: bool = False,
37
+ weak: bool = False,
38
+ ) -> None:
39
+ super().__init__()
40
+
41
+ self._requires_override = requires_override
42
+ self._is_override = is_override
43
+ self._forbid_duplicates = forbid_duplicates
44
+ self._identity = identity
45
+ self._weak = weak
46
+
47
+ self._objs: ta.MutableMapping[K, V] = dict_factory(identity=identity, weak=weak)()
48
+ self._invalidate_callbacks: list[ta.Callable[[], None]] = []
49
+
50
+ def add_invalidate_callback(self, callback: ta.Callable[[], None]) -> None:
51
+ self._invalidate_callbacks.append(callback)
52
+
53
+ def register(self, obj: K, val: V) -> None:
54
+ check.not_in(obj, self._objs)
55
+
56
+ self._objs[obj] = val
57
+
58
+ for iv in self._invalidate_callbacks:
59
+ iv()
60
+
61
+ def _lookup(self, obj: ta.Any) -> lang.Maybe[V]:
62
+ if not self._identity:
63
+ try:
64
+ hash(obj)
65
+ except TypeError:
66
+ return lang.empty()
67
+
68
+ try:
69
+ val = self._objs[obj]
70
+ except KeyError:
71
+ return lang.empty()
72
+ else:
73
+ return lang.just(val)
74
+
75
+ @dc.dataclass()
76
+ class DuplicatesForbiddenError(Exception):
77
+ owner_cls: type
78
+ instance_cls: type
79
+ att: str
80
+ ex_att: str
81
+
82
+ def collect(self, instance_cls: type, owner_cls: type | None = None) -> dict[str, tuple[K, V]]:
83
+ if owner_cls is None:
84
+ owner_cls = instance_cls
85
+
86
+ mro = instance_cls.__mro__[-2::-1]
87
+ try:
88
+ mro_pos = mro.index(owner_cls)
89
+ except ValueError:
90
+ raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
91
+
92
+ mro_dct: dict[str, list[tuple[type, ta.Any]]] = {}
93
+ for cur_cls in mro[:mro_pos + 1]:
94
+ for att, obj in cur_cls.__dict__.items():
95
+ if att not in mro_dct:
96
+ if not self._lookup(obj).present:
97
+ continue
98
+
99
+ try:
100
+ lst = mro_dct[att]
101
+ except KeyError:
102
+ lst = mro_dct[att] = []
103
+ lst.append((cur_cls, obj))
104
+
105
+ #
106
+
107
+ seen: ta.MutableMapping[ta.Any, str] | None = None
108
+ if self._forbid_duplicates:
109
+ seen = dict_factory(identity=self._identity)()
110
+
111
+ out: dict[str, tuple[K, V]] = {}
112
+ for att, lst in mro_dct.items():
113
+ if not lst:
114
+ raise RuntimeError
115
+ _, obj = lst[-1]
116
+
117
+ if len(lst) > 1:
118
+ if self._requires_override and not (self._is_override or lang.is_override)(obj):
119
+ raise lang.RequiresOverrideError(
120
+ att,
121
+ instance_cls,
122
+ lst[-1][0],
123
+ lst[0][0],
124
+ )
125
+
126
+ if not (mv := self._lookup(obj)).present:
127
+ continue
128
+
129
+ if seen is not None:
130
+ try:
131
+ ex_att = seen[obj]
132
+ except KeyError:
133
+ pass
134
+ else:
135
+ raise AttrRegistry.DuplicatesForbiddenError(owner_cls, instance_cls, att, ex_att) # noqa
136
+ seen[obj] = att
137
+
138
+ out[att] = (obj, mv.must())
139
+
140
+ return out
141
+
142
+
143
+ ##
144
+
145
+
146
+ class AttrRegistryCache(ta.Generic[K, V, T]):
147
+ def __init__(
148
+ self,
149
+ registry: AttrRegistry[K, V],
150
+ prepare: ta.Callable[[type, dict[str, tuple[K, V]]], T],
151
+ ) -> None:
152
+ super().__init__()
153
+
154
+ self._registry = registry
155
+ self._prepare = prepare
156
+
157
+ self._cache: dict[ta.Any, T] = {}
158
+
159
+ def cache_remove(k, self_ref=weakref.ref(self)):
160
+ if (ref_self := self_ref()) is not None:
161
+ cache = ref_self._cache # noqa
162
+ try:
163
+ del cache[k]
164
+ except KeyError:
165
+ pass
166
+
167
+ self._cache_remove = cache_remove
168
+
169
+ registry.add_invalidate_callback(self._cache.clear)
170
+
171
+ def get(self, instance_cls: type) -> T:
172
+ cls_ref = weakref.ref(instance_cls)
173
+ try:
174
+ return self._cache[cls_ref]
175
+ except KeyError:
176
+ pass
177
+ del cls_ref
178
+
179
+ collected = self._registry.collect(instance_cls)
180
+ out = self._prepare(instance_cls, collected)
181
+ self._cache[weakref.ref(instance_cls, self._cache_remove)] = out
182
+ return out
@@ -1,6 +1,12 @@
1
1
  import typing as ta
2
2
  import weakref
3
3
 
4
+ from .. import lang
5
+
6
+
7
+ with lang.auto_proxy_import(globals()):
8
+ from . import identity as _identity
9
+
4
10
 
5
11
  T = ta.TypeVar('T')
6
12
  K = ta.TypeVar('K')
@@ -141,3 +147,22 @@ class MissingDict(dict[K, V]):
141
147
  def __missing__(self, key):
142
148
  v = self[key] = self._missing_fn(key)
143
149
  return v
150
+
151
+
152
+ ##
153
+
154
+
155
+ def dict_factory[K, V](
156
+ *,
157
+ identity: bool = False,
158
+ weak: bool = False,
159
+ ) -> ta.Callable[..., ta.MutableMapping[K, V]]:
160
+ if identity:
161
+ if weak:
162
+ return _identity.IdentityWeakKeyDictionary
163
+ else:
164
+ return _identity.IdentityKeyDict
165
+ elif weak:
166
+ return weakref.WeakKeyDictionary
167
+ else:
168
+ return dict
@@ -4,13 +4,11 @@ TODO:
4
4
  - ALT: A.f(super(), ... ? :/
5
5
  - classmethod/staticmethod
6
6
  """
7
- import contextlib
8
7
  import functools
9
8
  import typing as ta
10
- import weakref
11
9
 
12
10
  from .. import check
13
- from .. import lang
11
+ from .. import collections as col
14
12
  from .dispatch import Dispatcher
15
13
  from .impls import get_impl_func_cls_set
16
14
 
@@ -34,9 +32,8 @@ class Method(ta.Generic[P, R]):
34
32
  it must be explicitly `@register`'ed itself. This is a feature, allowing for selective de-registration of
35
33
  implementations in subclasses via name shadowing.
36
34
 
37
- Methods have ability to choose to allow external installation of implementations outside of direct subclasses. This
38
- is to be used *extremely* rarely - basically only in the rare case of externally extensible type hierarchies with
39
- visitors.
35
+ Methods can choose to allow external installation of implementations outside of direct subclasses. This is to be
36
+ used *extremely* rarely - basically only in the rare case of externally extensible type hierarchies with visitors.
40
37
  """
41
38
 
42
39
  def __init__(
@@ -45,6 +42,7 @@ class Method(ta.Generic[P, R]):
45
42
  *,
46
43
  installable: bool = False,
47
44
  requires_override: bool = False,
45
+ instance_cache: bool = False,
48
46
  ) -> None:
49
47
  super().__init__()
50
48
 
@@ -53,9 +51,16 @@ class Method(ta.Generic[P, R]):
53
51
 
54
52
  self._func = func
55
53
  self._installable = installable
56
- self._requires_override = requires_override
54
+ self._instance_cache = instance_cache
57
55
 
58
- self._impls: ta.MutableMapping[ta.Callable, frozenset[type] | None] = weakref.WeakKeyDictionary()
56
+ self._registry: col.AttrRegistry[ta.Callable, Method._Entry] = col.AttrRegistry(
57
+ requires_override=requires_override,
58
+ )
59
+
60
+ self._cache: col.AttrRegistryCache[ta.Callable, Method._Entry, ta.Callable] = col.AttrRegistryCache(
61
+ self._registry,
62
+ self._prepare,
63
+ )
59
64
 
60
65
  # bpo-45678: special-casing for classmethod/staticmethod in Python <=3.9, as functools.update_wrapper doesn't
61
66
  # work properly in singledispatchmethod.__get__ if it is applied to an unbound classmethod/staticmethod
@@ -68,19 +73,12 @@ class Method(ta.Generic[P, R]):
68
73
  self._is_abstractmethod = getattr(func, '__isabstractmethod__', False) # noqa
69
74
  self.update_wrapper(self)
70
75
 
71
- self._dispatch_func_cache: dict[ta.Any, ta.Callable] = {}
72
-
73
- def dispatch_func_cache_remove(k, self_ref=weakref.ref(self)):
74
- if (ref_self := self_ref()) is not None:
75
- cache = ref_self._dispatch_func_cache # noqa
76
- with contextlib.suppress(KeyError):
77
- del cache[k]
78
-
79
- self._dispatch_func_cache_remove = dispatch_func_cache_remove
80
-
81
76
  self._owner: type | None = None
82
77
  self._name: str | None = None
83
78
 
79
+ class _Entry:
80
+ cls_set: frozenset[type]
81
+
84
82
  def __set_name__(self, owner, name):
85
83
  if self._owner is None:
86
84
  self._owner = owner
@@ -102,83 +100,29 @@ class Method(ta.Generic[P, R]):
102
100
 
103
101
  def register(self, impl: T, cls_set: frozenset[type] | None = None) -> T:
104
102
  check.callable(impl)
105
- if impl not in self._impls:
106
- self._impls[impl] = cls_set # type: ignore
107
- self._dispatch_func_cache.clear()
108
-
109
- return impl
110
-
111
- def _is_impl(self, obj: ta.Any) -> bool:
112
- try:
113
- hash(obj)
114
- except TypeError:
115
- return False
116
-
117
- return obj in self._impls
118
103
 
119
- def build_attr_dispatcher(self, instance_cls: type, owner_cls: type | None = None) -> Dispatcher[str]:
120
- if owner_cls is None:
121
- owner_cls = instance_cls
104
+ entry = Method._Entry()
105
+ if cls_set is not None:
106
+ entry.cls_set = cls_set
122
107
 
123
- mro = instance_cls.__mro__[-2::-1]
124
- try:
125
- mro_pos = mro.index(owner_cls)
126
- except ValueError:
127
- raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
108
+ self._registry.register(ta.cast(ta.Callable, impl), entry)
128
109
 
129
- mro_dct: dict[str, list[tuple[type, ta.Any]]] = {}
130
- for cur_cls in mro[:mro_pos + 1]:
131
- for att, obj in cur_cls.__dict__.items():
132
- if att not in mro_dct:
133
- if not self._is_impl(obj):
134
- continue
135
-
136
- try:
137
- lst = mro_dct[att]
138
- except KeyError:
139
- lst = mro_dct[att] = []
140
- lst.append((cur_cls, obj))
141
-
142
- #
110
+ return impl
143
111
 
112
+ def _build_dispatcher(self, collected: ta.Mapping[str, tuple[ta.Callable, _Entry]]) -> Dispatcher[str]:
144
113
  disp: Dispatcher[str] = Dispatcher()
145
114
 
146
- seen: dict[ta.Any, str] = {}
147
- for att, lst in mro_dct.items():
148
- if not lst:
149
- raise RuntimeError
150
- _, obj = lst[-1]
151
-
152
- if len(lst) > 1:
153
- if self._requires_override and not lang.is_override(obj):
154
- raise lang.RequiresOverrideError(
155
- att,
156
- instance_cls,
157
- lst[-1][0],
158
- lst[0][0],
159
- )
160
-
161
- if not self._is_impl(obj):
162
- continue
163
-
164
- cls_set = self._impls[obj]
165
- if cls_set is None:
166
- cls_set = get_impl_func_cls_set(obj, arg_offset=1)
167
- self._impls[obj] = cls_set
168
-
115
+ for a, (f, e) in collected.items():
169
116
  try:
170
- ex_att = seen[obj]
171
- except KeyError:
172
- pass
173
- else:
174
- raise TypeError(f'Duplicate impl: {owner_cls} {instance_cls} {att} {ex_att}')
175
- seen[obj] = att
117
+ cls_set = e.cls_set
118
+ except AttributeError:
119
+ cls_set = e.cls_set = get_impl_func_cls_set(f, arg_offset=1)
176
120
 
177
- disp.register(att, cls_set)
121
+ disp.register(a, cls_set)
178
122
 
179
123
  return disp
180
124
 
181
- def build_dispatch_func(self, disp: Dispatcher[str]) -> ta.Callable:
125
+ def _build_dispatch_func(self, disp: Dispatcher[str]) -> ta.Callable:
182
126
  dispatch = disp.dispatch
183
127
  type_ = type
184
128
  getattr_ = getattr
@@ -190,25 +134,16 @@ class Method(ta.Generic[P, R]):
190
134
  raise TypeError(f'{func_name} requires at least 1 positional argument')
191
135
 
192
136
  if (impl_att := dispatch(type_(args[0]))) is not None:
193
- fn = getattr_(self, impl_att)
194
- return fn(*args, **kwargs)
137
+ return getattr_(self, impl_att)(*args, **kwargs)
195
138
 
196
139
  return base_func.__get__(self)(*args, **kwargs) # noqa
197
140
 
198
141
  self.update_wrapper(__call__)
199
142
  return __call__
200
143
 
201
- def get_dispatch_func(self, instance_cls: type) -> ta.Callable:
202
- cls_ref = weakref.ref(instance_cls)
203
- try:
204
- return self._dispatch_func_cache[cls_ref]
205
- except KeyError:
206
- pass
207
- del cls_ref
208
-
209
- att_disp = self.build_attr_dispatcher(instance_cls)
210
- func = self.build_dispatch_func(att_disp)
211
- self._dispatch_func_cache[weakref.ref(instance_cls, self._dispatch_func_cache_remove)] = func
144
+ def _prepare(self, instance_cls: type, collected: ta.Mapping[str, tuple[ta.Callable, _Entry]]) -> ta.Callable:
145
+ disp = self._build_dispatcher(collected)
146
+ func = self._build_dispatch_func(disp)
212
147
  return func
213
148
 
214
149
  def __get__(self, instance, owner=None):
@@ -216,16 +151,21 @@ class Method(ta.Generic[P, R]):
216
151
  # FIXME: classmethod/staticmethod
217
152
  return self
218
153
 
219
- instance_cls = type(instance)
220
- try:
221
- func = self._dispatch_func_cache[weakref.ref(instance_cls)]
222
- except KeyError:
223
- func = self.get_dispatch_func(instance_cls)
224
- return func.__get__(instance, owner) # noqa
154
+ if self._instance_cache:
155
+ try:
156
+ return instance.__dict__[self._name]
157
+ except KeyError:
158
+ pass
159
+
160
+ bound = self._cache.get(type(instance)).__get__(instance, owner) # noqa
161
+
162
+ if self._instance_cache:
163
+ instance.__dict__[self._name] = bound
164
+
165
+ return bound
225
166
 
226
167
  def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
227
168
  instance, *rest = args
228
- instance_cls = type(instance)
229
169
 
230
170
  # if instance_cls is super:
231
171
  # owner = instance.__self_class__.__mro__[instance.__self_class__.__mro__.index(instance.__thisclass__) + 1]
@@ -233,55 +173,25 @@ class Method(ta.Generic[P, R]):
233
173
  # func = self.build_dispatch_func(att_disp)
234
174
  # return func.__get__(instance, instance.__thisclass__)(*rest, **kwargs)
235
175
 
236
- try:
237
- func = self._dispatch_func_cache[weakref.ref(instance_cls)]
238
- except KeyError:
239
- func = self.get_dispatch_func(instance_cls)
240
- return func.__get__(instance)(*rest, **kwargs) # noqa
176
+ return self.__get__(instance)(*rest, **kwargs)
241
177
 
242
178
 
243
179
  ##
244
180
 
245
181
 
246
- @ta.overload
247
- def method(
248
- func: ta.Callable[P, R],
249
- /,
250
- *,
251
- installable: bool = False,
252
- requires_override: bool = False,
253
- ) -> Method[P, R]: # noqa
254
- ...
255
-
256
-
257
- @ta.overload
258
182
  def method(
259
- func: None = None,
260
- /,
261
183
  *,
262
184
  installable: bool = False,
263
185
  requires_override: bool = False,
186
+ instance_cache: bool = False,
264
187
  ) -> ta.Callable[[ta.Callable[P, R]], Method[P, R]]: # noqa
265
- ...
266
-
267
-
268
- def method(
269
- func=None,
270
- /,
271
- *,
272
- installable=False,
273
- requires_override=False,
274
- ):
275
- kw = dict(
188
+ return functools.partial(
189
+ Method, # type: ignore[arg-type]
276
190
  installable=installable,
277
191
  requires_override=requires_override,
192
+ instance_cache=instance_cache,
278
193
  )
279
194
 
280
- if func is None:
281
- return functools.partial(Method, **kw)
282
-
283
- return Method(func, **kw)
284
-
285
195
 
286
196
  #
287
197
 
@@ -305,7 +215,7 @@ def install_method(
305
215
  if on is None:
306
216
  cls = owner
307
217
  else:
308
- cls = check.issubclass(on, owner)
218
+ cls = check.issubclass(on, owner) # noqa
309
219
 
310
220
  check.arg(not hasattr(cls, a))
311
221
  setattr(cls, a, fn)
omlish/dom/rendering.py CHANGED
@@ -100,7 +100,7 @@ class Renderer:
100
100
 
101
101
  #
102
102
 
103
- @dispatch.method
103
+ @dispatch.method(instance_cache=True)
104
104
  def render(self, o: ta.Any) -> None:
105
105
  raise TypeError(o)
106
106
 
omlish/funcs/guard.py ADDED
@@ -0,0 +1,214 @@
1
+ import abc
2
+ import functools
3
+ import operator
4
+ import typing as ta
5
+
6
+ from .. import check
7
+ from .. import collections as col
8
+ from .. import lang
9
+
10
+
11
+ T = ta.TypeVar('T')
12
+ T_co = ta.TypeVar('T_co', covariant=True)
13
+ U = ta.TypeVar('U')
14
+ P = ta.ParamSpec('P')
15
+
16
+
17
+ ##
18
+
19
+
20
+ class GuardFn(ta.Protocol[P, T_co]):
21
+ def __get__(self, instance, owner=None) -> ta.Self: ...
22
+
23
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T_co] | None: ...
24
+
25
+
26
+ ##
27
+
28
+
29
+ @ta.final
30
+ class DumbGuardFn(ta.Generic[P, T]):
31
+ def __init__(self, fn: ta.Callable[P, T]) -> None:
32
+ self._fn = fn
33
+
34
+ def __get__(self, instance, owner=None):
35
+ return DumbGuardFn(self._fn.__get__(instance, owner)) # noqa
36
+
37
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T]:
38
+ return functools.partial(self._fn, *args, **kwargs)
39
+
40
+
41
+ dumb = DumbGuardFn
42
+
43
+
44
+ ##
45
+
46
+
47
+ class AmbiguousGuardFnError(Exception):
48
+ pass
49
+
50
+
51
+ @ta.final
52
+ class MultiGuardFn(ta.Generic[P, T]):
53
+ def __init__(
54
+ self,
55
+ *children: GuardFn[P, T],
56
+ strict: bool = False,
57
+ ) -> None:
58
+ self._children, self._strict = children, strict
59
+
60
+ def __get__(self, instance, owner=None):
61
+ return MultiGuardFn(*map(operator.methodcaller('__get__', instance, owner), self._children), strict=self._strict) # noqa
62
+
63
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T] | None:
64
+ matches = []
65
+ for c in self._children:
66
+ if (m := c(*args, **kwargs)) is not None:
67
+ if not self._strict:
68
+ return m
69
+ matches.append(m)
70
+ if not matches:
71
+ return None
72
+ elif len(matches) > 1:
73
+ raise AmbiguousGuardFnError
74
+ else:
75
+ return matches[0]
76
+
77
+
78
+ multi = MultiGuardFn
79
+
80
+
81
+ ##
82
+
83
+
84
+ class _BaseGuardFnMethod(lang.Abstract, ta.Generic[P, T]):
85
+ def __init__(
86
+ self,
87
+ *,
88
+ strict: bool = False,
89
+ requires_override: bool = False,
90
+ instance_cache: bool = False,
91
+ prototype: ta.Callable[P, T] | None = None,
92
+ ) -> None:
93
+ super().__init__()
94
+
95
+ self._strict = strict
96
+ self._instance_cache = instance_cache
97
+ self._prototype = prototype
98
+
99
+ self._registry: col.AttrRegistry[ta.Callable, None] = col.AttrRegistry(
100
+ requires_override=requires_override,
101
+ )
102
+
103
+ self._cache: col.AttrRegistryCache[ta.Callable, None, MultiGuardFn] = col.AttrRegistryCache(
104
+ self._registry,
105
+ self._prepare,
106
+ )
107
+
108
+ _owner: type | None = None
109
+ _name: str | None = None
110
+
111
+ def __set_name__(self, owner, name):
112
+ if self._owner is None:
113
+ self._owner = owner
114
+ if self._name is None:
115
+ self._name = name
116
+
117
+ def register(self, fn: U) -> U:
118
+ check.callable(fn)
119
+ self._registry.register(ta.cast(ta.Callable, fn), None)
120
+ return fn
121
+
122
+ def _prepare(self, instance_cls: type, collected: ta.Mapping[str, tuple[ta.Callable, None]]) -> MultiGuardFn:
123
+ return MultiGuardFn(
124
+ *[getattr(instance_cls, a) for a in collected],
125
+ strict=self._strict,
126
+ )
127
+
128
+ @abc.abstractmethod
129
+ def _bind(self, instance, owner):
130
+ raise NotImplementedError
131
+
132
+ def __get__(self, instance, owner=None):
133
+ if instance is None:
134
+ return self
135
+
136
+ if self._instance_cache:
137
+ try:
138
+ return instance.__dict__[self._name]
139
+ except KeyError:
140
+ pass
141
+
142
+ bound = self._bind(instance, owner)
143
+
144
+ if self._instance_cache:
145
+ instance.__dict__[self._name] = bound
146
+
147
+ return bound
148
+
149
+ def _call(self, *args, **kwargs):
150
+ instance, *rest = args
151
+ return self.__get__(instance)(*rest, **kwargs)
152
+
153
+ #
154
+
155
+
156
+ class GuardFnMethod(_BaseGuardFnMethod[P, T]):
157
+ def _bind(self, instance, owner):
158
+ return self._cache.get(type(instance)).__get__(instance, owner) # noqa
159
+
160
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> ta.Callable[[], T] | None:
161
+ return self._call(*args, **kwargs)
162
+
163
+
164
+ def method(
165
+ *,
166
+ strict: bool = False,
167
+ requires_override: bool = False,
168
+ instance_cache: bool = False,
169
+ ) -> ta.Callable[[ta.Callable[P, T]], GuardFnMethod[P, T]]: # noqa
170
+ def inner(fn):
171
+ return GuardFnMethod(
172
+ strict=strict,
173
+ requires_override=requires_override,
174
+ instance_cache=instance_cache,
175
+ prototype=fn,
176
+ )
177
+
178
+ return inner
179
+
180
+
181
+ #
182
+
183
+
184
+ class DumbGuardFnMethod(_BaseGuardFnMethod[P, T]):
185
+ def _bind(self, instance, owner):
186
+ gf = self._cache.get(type(instance)).__get__(instance, owner) # noqa
187
+ x = self._prototype.__get__(instance, owner) # type: ignore
188
+
189
+ def inner(*args, **kwargs):
190
+ if (m := gf(*args, **kwargs)) is not None:
191
+ return m()
192
+ return x(*args, **kwargs)
193
+
194
+ return inner
195
+
196
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
197
+ return self._call(*args, **kwargs)
198
+
199
+
200
+ def dumb_method(
201
+ *,
202
+ strict: bool = False,
203
+ requires_override: bool = False,
204
+ instance_cache: bool = False,
205
+ ) -> ta.Callable[[ta.Callable[P, T]], DumbGuardFnMethod[P, T]]: # noqa
206
+ def inner(fn):
207
+ return DumbGuardFnMethod(
208
+ strict=strict,
209
+ requires_override=requires_override,
210
+ instance_cache=instance_cache,
211
+ prototype=fn,
212
+ )
213
+
214
+ return inner
@@ -29,7 +29,7 @@ class Renderer:
29
29
 
30
30
  self._out = out
31
31
 
32
- @dispatch.method
32
+ @dispatch.method(instance_cache=True)
33
33
  def render(self, item: Item) -> None:
34
34
  raise TypeError(item)
35
35
 
omlish/lite/maybes.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: UP007 UP045
2
2
  import abc
3
3
  import functools
4
+ import operator
4
5
  import typing as ta
5
6
 
6
7
  from .abstract import Abstract
@@ -208,3 +209,10 @@ class _EmptyMaybe(_Maybe[T]):
208
209
 
209
210
 
210
211
  Maybe._empty = _EmptyMaybe() # noqa
212
+
213
+
214
+ ##
215
+
216
+
217
+ setattr(Maybe, 'just', _JustMaybe) # noqa
218
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
@@ -81,7 +81,7 @@ class Renderer(lang.Abstract):
81
81
  def args(self) -> lang.Args:
82
82
  return self._params_preparer.prepare()
83
83
 
84
- @dispatch.method
84
+ @dispatch.method(instance_cache=True)
85
85
  def render(self, o: ta.Any) -> tp.Part:
86
86
  raise TypeError(o)
87
87
 
omlish/text/parts.py CHANGED
@@ -75,7 +75,7 @@ class PartTransform:
75
75
  def __call__(self, part: Part | None) -> Part:
76
76
  return self._transform(part)
77
77
 
78
- @dispatch.method
78
+ @dispatch.method(instance_cache=True)
79
79
  def _transform(self, part: Part | None) -> Part:
80
80
  raise TypeError(part)
81
81
 
@@ -214,7 +214,7 @@ class PartRenderer:
214
214
  def __call__(self, part: Part | None) -> None:
215
215
  return self._render(part)
216
216
 
217
- @dispatch.method
217
+ @dispatch.method()
218
218
  def _render(self, part: Part | None) -> None:
219
219
  raise TypeError(part)
220
220
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev452
3
+ Version: 0.0.0.dev453
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.omlish-manifests.json,sha256=FLw7xkPiSXuImZgqSP8BwrEib2R1doSzUPLUkc-QUIA,8410
2
- omlish/__about__.py,sha256=Ia-k2mCoU1psSy7-YOmB-XdT8JnxIXK5ICScX_nTpxs,3613
2
+ omlish/__about__.py,sha256=UyBs9jXm7W7VwfekZgY2BWyGcs6fJ0_zJqOMdeCn8aY,3613
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ZNIMl1kwg3qdei4DiUrJPQe5M81S1e76N-GuNSwLBAE,8683
5
5
  omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
@@ -64,14 +64,15 @@ omlish/codecs/funcs.py,sha256=or0Jogczuzk7csDTRl-HURMEjl8LXXqxxXYK45xcM5w,855
64
64
  omlish/codecs/registry.py,sha256=y7gjhBY1tTTHZmaK4X02zL5HD_25AAiZ8k27Op69Ag0,4006
65
65
  omlish/codecs/standard.py,sha256=eiZ4u9ep0XrA4Z_D1zJI0vmWyuN8HLrX4Se_r_Cq_ZM,60
66
66
  omlish/codecs/text.py,sha256=P9-xpdiQE3nbI5CzEentf07FBHPzD7_SwOtUQUJ01So,5710
67
- omlish/collections/__init__.py,sha256=BIc806ri5Eq-kR03Ya2YfYTRI0g1rn_0haQPUqxXqys,2816
67
+ omlish/collections/__init__.py,sha256=cSNZ670-GaNW_sCazmLi41HPx5tfzMvkHCvPoyebTEY,2934
68
68
  omlish/collections/abc.py,sha256=p9zhL5oNV5WPyWmMn34fWfkuxPQAjOtL7WQA-Xsyhwk,2628
69
+ omlish/collections/attrregistry.py,sha256=SPvIt8rT8nWvEBqf35I8wPgBU55m0u2ssSmNcYHm_FM,5563
69
70
  omlish/collections/bimap.py,sha256=3szDCscPJlFRtkpyVQNWneg4s50mr6Rd0jdTzVEIcnE,1661
70
71
  omlish/collections/coerce.py,sha256=tAls15v_7p5bUN33R7Zbko87KW5toWHl9fRialCqyNY,7030
71
72
  omlish/collections/frozen.py,sha256=drarjcMFpwKhBM01b1Gh6XDcaRaUgpyuxEiqppaKRkU,4220
72
73
  omlish/collections/hasheq.py,sha256=uHypfZlHhicQPvx9OOlpT9MSLwfc_mFil-WaxF9dTOo,3732
73
74
  omlish/collections/identity.py,sha256=00UAYIjCRhyj73Es0jVNvOloXjRxHcjRxGQeWo75OEo,4834
74
- omlish/collections/mappings.py,sha256=FWDErWR63y3xcjBfxXH58M1yOAqCdMyFWwzBjGAE7UQ,3346
75
+ omlish/collections/mappings.py,sha256=c3ojUN9Ca3oGfmMXSFkDQYou5J15obDfgjCvzq3Url4,3833
75
76
  omlish/collections/multimaps.py,sha256=azuif-PeLGvLpCE9_XX5zYx7XtVbsulWq0owvEKNy_g,3897
76
77
  omlish/collections/ordered.py,sha256=jBSj5CamdKJAezXM8GlRd7jjaRDk6BHIeOhlxcC9lJI,2468
77
78
  omlish/collections/ranked.py,sha256=McB8C2UQfUvrbmxGTpBz1-EZuyCLkBFtktzncMdt8_Y,2287
@@ -217,7 +218,7 @@ omlish/dispatch/__init__.py,sha256=UwVT6SSk5HKk9xF9vc_UJD8BDSIQWSw98Ldx-G-6PC0,1
217
218
  omlish/dispatch/dispatch.py,sha256=TzWihCt9Zr8wfIwVpKHVOOrYFVKpDXRevxYomtEfqYc,3176
218
219
  omlish/dispatch/functions.py,sha256=cwNzGIg2ZIalEgn9I03cnJVbMTHjWloyDTaowlO3UPs,1524
219
220
  omlish/dispatch/impls.py,sha256=3kyLw5yiYXWCN1DiqQbD1czMVrM8v1mpQ8NyD0tUrnk,1870
220
- omlish/dispatch/methods.py,sha256=RJ-gLF2UdCQKZFhiuV4R1N3lRB_dMH3ItiHwmm-s62o,9873
221
+ omlish/dispatch/methods.py,sha256=aberuaB1ujTwhTdTyZQ2j4azNVkO54nEc28k5ZQ--J4,7337
221
222
  omlish/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
222
223
  omlish/docker/all.py,sha256=t5jBNZAzqCoB05-nwCuSZ6C3PBEBD6T3wsIJvIXJaRg,576
223
224
  omlish/docker/cli.py,sha256=ws3ypayxdnp3nWeLww45jHG0Vwr5e4bUbLTqkpejAhc,2570
@@ -232,7 +233,7 @@ omlish/docker/timebomb.py,sha256=EnFt8pJeXkowF_F5NXnN0ogDmxUmVymV4h1CYPwyJr4,356
232
233
  omlish/dom/__init__.py,sha256=RYkv9inuebWIKsRuPXr3j0njgLFKzIo-ujKc4JEp0wc,301
233
234
  omlish/dom/building.py,sha256=VPMbGxv730dYjS_jlrrp0qiZhuGT8OheFamUAgtQdF4,1022
234
235
  omlish/dom/content.py,sha256=1NkE1eb0ITfps-yJ-Lbtm6kGnvUmQy6HWo01gnvSkiU,2775
235
- omlish/dom/rendering.py,sha256=5cQK3A_uifh3Th5v3c0nx-tW8d1KyGL6C9tASPaNVNI,4336
236
+ omlish/dom/rendering.py,sha256=zypSFpZO0pBfPY_ZJXeywovu0pbEtQzLYzn6TZfpRlk,4357
236
237
  omlish/formats/__init__.py,sha256=T0AG1gFnqQ5JiHN0UPQjQ-7g5tnxMIG-mgOvMYExYAM,21
237
238
  omlish/formats/cbor.py,sha256=o_Hbe4kthO9CeXK-FySrw0dHVlrdyTo2Y8PpGRDfZ3c,514
238
239
  omlish/formats/cloudpickle.py,sha256=16si4yUp_pAdDWGECAWf6HLA2PwSANGGgDoMLGZUem8,579
@@ -287,6 +288,7 @@ omlish/formats/toml/writer.py,sha256=9NT8sRy3I9KubxFx56Qbislvrdtbd23rEuBT-GSdUYA
287
288
  omlish/funcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
288
289
  omlish/funcs/builders.py,sha256=ZSBQS2xqriXp5x0t074EEZvuTmMp4Yue2YGBoTLAioo,4044
289
290
  omlish/funcs/genmachine.py,sha256=BOxO1OTjxZ7ewv_WpqYkY8bwlGQIJIjwjvYMflEFa_M,2571
291
+ omlish/funcs/guard.py,sha256=vrsUtaGJj-eCOkfjxlHV9lw5qDLimiHOW4X0LINDKmk,5495
290
292
  omlish/funcs/match.py,sha256=cBtG7kdpWdxvHwlte5CWlBxXSoJSV48joV4bwJJC3pk,6352
291
293
  omlish/funcs/pairs.py,sha256=XhYTJdqooAJKeoGZmEaiKYeFRq5-Dj2_y92IdBl_C20,4371
292
294
  omlish/funcs/pipes.py,sha256=E1dQZMBmgT2qautG1vEqy5v3QBsO2Nzryv33j4YAngA,2520
@@ -297,7 +299,7 @@ omlish/graphs/trees.py,sha256=r_kZw-83c1KXe7fida-CVWNGDX9quPTmhAjJ5I5uKRc,8198
297
299
  omlish/graphs/dot/__init__.py,sha256=Y1MZRQBZkcYyG1Tn7K2FhL8aYbm4v4tk6f5g9AqEkUw,359
298
300
  omlish/graphs/dot/items.py,sha256=GIzUqLHJPR4AVtF141uA8JnyybkRZx6mFYsNJHu_JVg,4087
299
301
  omlish/graphs/dot/make.py,sha256=e-M1IEdh4kHEjJmBxpaEUPxvFLrm5uIXdGxjQZr2HRo,365
300
- omlish/graphs/dot/rendering.py,sha256=IOCEEOqlsRctoIgnz1I9v0ycHHDAr5zYubTXT7v_YwA,3634
302
+ omlish/graphs/dot/rendering.py,sha256=SGSpwswdFqsjEnznQDyryIsXE8bzPXSUFAJHlB2uT2Y,3655
301
303
  omlish/graphs/dot/utils.py,sha256=8cGKIdcM-w1q4ITUYyC0lnYwLqNWuH2OddmmbLxVgEo,806
302
304
  omlish/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
303
305
  omlish/http/all.py,sha256=ChV0KcPN8CKcwlQiWqYyMvmjHuH9qhBsd2kV7-HrFto,1409
@@ -478,7 +480,7 @@ omlish/lite/imports.py,sha256=GyEDKL-WuHtdOKIL-cc8aFd0-bHwZFDEjAB52ItabX0,1341
478
480
  omlish/lite/inject.py,sha256=BQgjBj2mzJgMimLam-loSpQzcb31-8NYPVRQgHVv3cQ,29159
479
481
  omlish/lite/json.py,sha256=m0Ce9eqUZG23-H7-oOp8n1sf4fzno5vtK4AK_4Vc-Mg,706
480
482
  omlish/lite/marshal.py,sha256=oVqVwqTArFUj9lYhmKg_MVgnqlCAUvOnYgtU3bBu_bk,23020
481
- omlish/lite/maybes.py,sha256=sJ4dawgBxlZ4oB9dZ3bhBb_8AOJlIW0nVIFgFZ1huXE,4384
483
+ omlish/lite/maybes.py,sha256=NCnMZaSbIfvOMqz69aYmmvZQDd0c61QjG3ju7uJc8t8,4531
482
484
  omlish/lite/maysync.py,sha256=Otd-xqWaMzY9xBF5SiLKxu4kG_GaWGk8qTGFDAHXDm4,14803
483
485
  omlish/lite/objects.py,sha256=HzN_4J6w6WDLKDrW8jSNUKgfAR5vUsB42rtSCu04oqQ,1921
484
486
  omlish/lite/pycharm.py,sha256=fdSTwtdqGRL0P9IkCrDAPqQkJsegq1NfYyVG2N6cy4w,1174
@@ -730,7 +732,7 @@ omlish/sql/queries/names.py,sha256=25kPRYse_ivI17lMD9st04k7QQMOGf5plb4b8yZFpUI,2
730
732
  omlish/sql/queries/ops.py,sha256=pDZ_2Jo_Fa8DDbtYkc6-9eehkWsZPI-jh-KFlubcY6Y,134
731
733
  omlish/sql/queries/params.py,sha256=ocjaJbhv_5LWgxdjoZg8vyAWDZxsij4cKH8Lx4hXMT0,1261
732
734
  omlish/sql/queries/relations.py,sha256=P0qBqNICyeig9sEZAYLZcTXcI1YFiBe6REm6jNhpHUI,2630
733
- omlish/sql/queries/rendering.py,sha256=3Ocw52DDDsUavEmb8xH-2Mhp-3pZCXsJVXFuVTOUBqk,7779
735
+ omlish/sql/queries/rendering.py,sha256=Kc7GqLKnuzfeVv4MPTkjYb6-n4fTBLOKo4QjfPc-WBY,7800
734
736
  omlish/sql/queries/selects.py,sha256=XNisb2draHURYhbZsTFw7DJQaN1Zfo2Znm-Ty0lHNTo,1773
735
737
  omlish/sql/queries/std.py,sha256=FdJkXD7oiuj8mtVzvbsL1ZocEEAfaSdOsfTVB1YhtrE,699
736
738
  omlish/sql/queries/stmts.py,sha256=pBqwD7dRlqMu6uh6vR3xaWOEgbZCcFWbOQ9ryYd17T4,441
@@ -808,7 +810,7 @@ omlish/text/indent.py,sha256=BWVVaHs_B1ppwHJJIxKCDG3iCutkYy5e1qr59Z_Suzg,1524
808
810
  omlish/text/linecache.py,sha256=hRYlEhD63ZfA6_ZOTkQIcnON-3W56QMAhcG3vEJqj9M,1858
809
811
  omlish/text/mangle.py,sha256=k7mYavVgxJ2ENV2wfjw3c9u3hqH5NeVpjoxYbyaYC0Y,2796
810
812
  omlish/text/minja.py,sha256=7UKNalkWpTG_364OIo7p5ym--uiNPR2RFBW_W8rrO4I,9194
811
- omlish/text/parts.py,sha256=MpiCUyfpcL4PLb2Etj8V7Yj4qofhy0xVwBrIL6RfNdg,6646
813
+ omlish/text/parts.py,sha256=MFi-ANxXlBiOMqW2y2S0BRg6xDwPYxSXSfrYPM3rtcE,6669
812
814
  omlish/text/random.py,sha256=8feS5JE_tSjYlMl-lp0j93kCfzBae9AM2cXlRLebXMA,199
813
815
  omlish/text/templating.py,sha256=i-HU7W-GtKdnBhEG3mnSey5ig7vV0q02D3pVzMoOzJY,3318
814
816
  omlish/text/go/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -823,9 +825,9 @@ omlish/typedvalues/marshal.py,sha256=TWwqiGROaQwcxglGiBt_t5D9AvlWsy3ELm7do-nPeao
823
825
  omlish/typedvalues/of_.py,sha256=UXkxSj504WI2UrFlqdZJbu2hyDwBhL7XVrc2qdR02GQ,1309
824
826
  omlish/typedvalues/reflect.py,sha256=PAvKW6T4cW7u--iX80w3HWwZUS3SmIZ2_lQjT65uAyk,1026
825
827
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
826
- omlish-0.0.0.dev452.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
827
- omlish-0.0.0.dev452.dist-info/METADATA,sha256=6HeJlCFmWua6YWGrp4v6KNC037N-n4-CEcHqBzCBUqc,19003
828
- omlish-0.0.0.dev452.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
829
- omlish-0.0.0.dev452.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
830
- omlish-0.0.0.dev452.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
831
- omlish-0.0.0.dev452.dist-info/RECORD,,
828
+ omlish-0.0.0.dev453.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
829
+ omlish-0.0.0.dev453.dist-info/METADATA,sha256=2xNQ3KjE39xJl-eAkz-XOpj2tek1YFZnNCNmy_ZaYBk,19003
830
+ omlish-0.0.0.dev453.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
831
+ omlish-0.0.0.dev453.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
832
+ omlish-0.0.0.dev453.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
833
+ omlish-0.0.0.dev453.dist-info/RECORD,,