omlish 0.0.0.dev318__py3-none-any.whl → 0.0.0.dev319__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.dev318'
2
- __revision__ = 'b80bceb394abe03974e4312775224ac1dbcb5b22'
1
+ __version__ = '0.0.0.dev319'
2
+ __revision__ = 'db501bf0b4fbb5f1af2b467b004ffec22f3e769c'
3
3
 
4
4
 
5
5
  #
@@ -29,6 +29,7 @@ class Method(ta.Generic[P, R]):
29
29
  func: ta.Callable,
30
30
  *,
31
31
  installable: bool = False,
32
+ requires_override: bool = False,
32
33
  ) -> None:
33
34
  super().__init__()
34
35
 
@@ -37,6 +38,7 @@ class Method(ta.Generic[P, R]):
37
38
 
38
39
  self._func = func
39
40
  self._installable = installable
41
+ self._requires_override = requires_override
40
42
 
41
43
  self._impls: ta.MutableMapping[ta.Callable, frozenset[type] | None] = weakref.WeakKeyDictionary()
42
44
 
@@ -97,33 +99,73 @@ class Method(ta.Generic[P, R]):
97
99
 
98
100
  return impl
99
101
 
102
+ def _is_impl(self, obj: ta.Any) -> bool:
103
+ try:
104
+ hash(obj)
105
+ except TypeError:
106
+ return False
107
+
108
+ return obj in self._impls
109
+
100
110
  def build_attr_dispatcher(self, instance_cls: type, owner_cls: type | None = None) -> Dispatcher[str]:
101
- disp: Dispatcher[str] = Dispatcher()
111
+ if owner_cls is None:
112
+ owner_cls = instance_cls
102
113
 
103
- mro_dct = lang.mro_dict(instance_cls, owner_cls)
104
- seen: ta.Mapping[ta.Any, str] = {}
105
- for nam, att in mro_dct.items():
106
- try:
107
- hash(att)
108
- except TypeError:
109
- continue
114
+ mro = instance_cls.__mro__[-2::-1]
115
+ try:
116
+ mro_pos = mro.index(owner_cls)
117
+ except ValueError:
118
+ raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
119
+
120
+ mro_dct: dict[str, list[tuple[type, ta.Any]]] = {}
121
+ for cur_cls in mro[:mro_pos + 1]:
122
+ for att, obj in cur_cls.__dict__.items():
123
+ if att not in mro_dct:
124
+ if not self._is_impl(obj):
125
+ continue
126
+
127
+ try:
128
+ lst = mro_dct[att]
129
+ except KeyError:
130
+ lst = mro_dct[att] = []
131
+ lst.append((cur_cls, obj))
110
132
 
111
- if att not in self._impls:
133
+ #
134
+
135
+ disp: Dispatcher[str] = Dispatcher()
136
+
137
+ seen: dict[ta.Any, str] = {}
138
+ for att, lst in mro_dct.items():
139
+ if not lst:
140
+ raise RuntimeError
141
+ _, obj = lst[-1]
142
+
143
+ if len(lst) > 1:
144
+ if self._requires_override and not lang.is_override(obj):
145
+ raise lang.RequiresOverrideError(
146
+ att,
147
+ instance_cls,
148
+ lst[-1][0],
149
+ lst[0][0],
150
+ )
151
+
152
+ if not self._is_impl(obj):
112
153
  continue
113
- cls_set = self._impls[att]
114
154
 
155
+ cls_set = self._impls[obj]
115
156
  if cls_set is None:
116
- cls_set = get_impl_func_cls_set(att, arg_offset=1)
117
- self._impls[att] = cls_set
157
+ cls_set = get_impl_func_cls_set(obj, arg_offset=1)
158
+ self._impls[obj] = cls_set
118
159
 
119
160
  try:
120
- ex_nam = seen[att]
161
+ ex_att = seen[obj]
121
162
  except KeyError:
122
163
  pass
123
164
  else:
124
- raise TypeError(f'Duplicate impl: {owner_cls} {instance_cls} {nam} {ex_nam}')
165
+ raise TypeError(f'Duplicate impl: {owner_cls} {instance_cls} {att} {ex_att}')
166
+ seen[obj] = att
125
167
 
126
- disp.register(nam, cls_set)
168
+ disp.register(att, cls_set)
127
169
 
128
170
  return disp
129
171
 
@@ -186,19 +228,42 @@ class Method(ta.Generic[P, R]):
186
228
 
187
229
 
188
230
  @ta.overload
189
- def method(func: ta.Callable[P, R], /, *, installable: bool = False) -> Method[P, R]: # noqa
231
+ def method(
232
+ func: ta.Callable[P, R],
233
+ /,
234
+ *,
235
+ installable: bool = False,
236
+ requires_override: bool = False,
237
+ ) -> Method[P, R]: # noqa
190
238
  ...
191
239
 
192
240
 
193
241
  @ta.overload
194
- def method(func: None = None, /, *, installable: bool = False) -> ta.Callable[[ta.Callable[P, R]], Method[P, R]]: # noqa
242
+ def method(
243
+ func: None = None,
244
+ /,
245
+ *,
246
+ installable: bool = False,
247
+ requires_override: bool = False,
248
+ ) -> ta.Callable[[ta.Callable[P, R]], Method[P, R]]: # noqa
195
249
  ...
196
250
 
197
251
 
198
- def method(func=None, /, *, installable=False): # noqa
199
- kw = dict(installable=installable)
252
+ def method(
253
+ func=None,
254
+ /,
255
+ *,
256
+ installable=False,
257
+ requires_override=False,
258
+ ):
259
+ kw = dict(
260
+ installable=installable,
261
+ requires_override=requires_override,
262
+ )
263
+
200
264
  if func is None:
201
265
  return functools.partial(Method, **kw)
266
+
202
267
  return Method(func, **kw)
203
268
 
204
269
 
omlish/lang/__init__.py CHANGED
@@ -252,6 +252,15 @@ from .outcomes import ( # noqa
252
252
  value,
253
253
  )
254
254
 
255
+ from .overrides import ( # noqa
256
+ needs_override,
257
+
258
+ is_override,
259
+
260
+ RequiresOverrideError,
261
+ RequiresOverride,
262
+ )
263
+
255
264
  from .params import ( # noqa
256
265
  ArgsParam,
257
266
  KwOnlyParam,
@@ -0,0 +1,82 @@
1
+ import types
2
+ import typing as ta
3
+
4
+ from .descriptors import unwrap_func
5
+
6
+
7
+ ##
8
+
9
+
10
+ class _EmptyClass:
11
+ pass
12
+
13
+
14
+ _OVERRIDE_NOT_NEEDED_ATTRS: ta.AbstractSet[str] = {
15
+ *_EmptyClass.__dict__,
16
+ }
17
+
18
+
19
+ def needs_override(attr: str, obj: ta.Any) -> bool:
20
+ if attr in _OVERRIDE_NOT_NEEDED_ATTRS:
21
+ return False
22
+
23
+ return True
24
+
25
+
26
+ ##
27
+
28
+
29
+ def is_override(obj: ta.Any) -> bool:
30
+ if isinstance(obj, types.FunctionType):
31
+ fn = unwrap_func(obj)
32
+ if getattr(fn, '__override__', False):
33
+ return True
34
+
35
+ return False
36
+
37
+
38
+ ##
39
+
40
+
41
+ class RequiresOverrideError(TypeError):
42
+ def __init__(
43
+ self,
44
+ att: str | None = None,
45
+ cls: type | None = None,
46
+ mro_cls: type | None = None,
47
+ owner_cls: type | None = None,
48
+ ) -> None:
49
+ super().__init__(' '.join([
50
+ 'Attribute',
51
+ *([f'{att!r}'] if att is not None else []),
52
+ *([f'on class {cls.__qualname__}'] if cls is not None else []),
53
+ *([f'from mro class {mro_cls.__qualname__}'] if mro_cls is not None and mro_cls is not cls else []),
54
+ f'is not marked as a @typing.override',
55
+ *([f'from owning class {owner_cls.__qualname__}'] if owner_cls is not None else []),
56
+ ]))
57
+
58
+ self.att = att
59
+ self.cls = cls
60
+ self.mro_cls = mro_cls
61
+ self.owner_cls = owner_cls
62
+
63
+
64
+ class RequiresOverride:
65
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
66
+ super().__init_subclass__(**kwargs)
67
+
68
+ req_ovr_dct: dict[str, type] = {}
69
+ for m_cls in reversed(cls.__mro__):
70
+ for a, o in m_cls.__dict__.items():
71
+ if a in req_ovr_dct and not is_override(o):
72
+ raise RequiresOverrideError(
73
+ a,
74
+ cls,
75
+ m_cls,
76
+ req_ovr_dct[a],
77
+ )
78
+
79
+ if RequiresOverride in m_cls.__bases__:
80
+ for a, o in m_cls.__dict__.items():
81
+ if needs_override(a, o):
82
+ req_ovr_dct.setdefault(a, m_cls)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev318
3
+ Version: 0.0.0.dev319
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=orgsRvtpHu8tdhaCvlP9v3P495OJopYYiHKjK68WtWg,8587
2
- omlish/__about__.py,sha256=kisuBtrw4R5wrzm5nluakJkzLD7HYXcYuB7zYWAgrQc,3478
2
+ omlish/__about__.py,sha256=BUm8C0RUF_mH5RRsEW0hH9yf1ygPXOnka9jzpOJkyck,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
@@ -225,7 +225,7 @@ omlish/dispatch/_dispatch3.py,sha256=9Zjd7bINAC3keiaBdssc4v5dY0-8OI6XooV2DR9U7Z0
225
225
  omlish/dispatch/dispatch.py,sha256=KA5l49AiGLRjp4J7RDJW9RiDp9WUD1ewR1AOPEF8g38,3062
226
226
  omlish/dispatch/functions.py,sha256=cwNzGIg2ZIalEgn9I03cnJVbMTHjWloyDTaowlO3UPs,1524
227
227
  omlish/dispatch/impls.py,sha256=K_okKvpZml4NkTHJmTVyMQSrIaIJcqTEgkreGwukaOw,1895
228
- omlish/dispatch/methods.py,sha256=CeGVwiUdeadzyOe0xQVY_jegyj0tdHLQZpaIlLkJNnY,7330
228
+ omlish/dispatch/methods.py,sha256=sSGA52swdrdHEbm_jAPBZVoKP9xMIfVcw2MnTvIJtrI,8913
229
229
  omlish/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
230
  omlish/docker/all.py,sha256=xXRgJgLGPwAtr7bDMJ_Dp9jTfOwfGvohNhc6LsoELJc,514
231
231
  omlish/docker/cli.py,sha256=gtb9kitVfGnd4cr587NsVVk8D5Ok5y5SAsqD_SwGrSA,2565
@@ -403,7 +403,7 @@ omlish/iterators/iterators.py,sha256=RxW35yQ5ed8vBQ22IqpDXFx-i5JiLQdp7-pkMZXhJJ8
403
403
  omlish/iterators/recipes.py,sha256=wOwOZg-zWG9Zc3wcAxJFSe2rtavVBYwZOfG09qYEx_4,472
404
404
  omlish/iterators/tools.py,sha256=c4hArZEVV8y9_dFfmRwakusv1cWJLT4MkTkGRjnGN5U,2556
405
405
  omlish/iterators/unique.py,sha256=Nw0pSaNEcHAkve0ugfLPvJcirDOn9ECyC5wIL8JlJKI,1395
406
- omlish/lang/__init__.py,sha256=Zs5YE5F7rq2cdEqK2e6QuamTQGGwLaM0OP_RNOmcT1A,5921
406
+ omlish/lang/__init__.py,sha256=ZtAmS2HiXPaJexoyPXD2fCl-8DNA4s8zlErlvhU_2o0,6045
407
407
  omlish/lang/attrs.py,sha256=i7euRF81uNF8QDmUVXSK_BtqLGshaMi4VVdUnMjiMwg,5050
408
408
  omlish/lang/casing.py,sha256=cFUlbDdXLhwnWwcYx4qnM5c4zGX7hIRUfcjiZbxUD28,4636
409
409
  omlish/lang/clsdct.py,sha256=HAGIvBSbCefzRjXriwYSBLO7QHKRv2UsE78jixOb-fA,1828
@@ -419,6 +419,7 @@ omlish/lang/imports.py,sha256=y9W9Y-d_cQ35QCLuSIPoa6vnEqSErFCz8b-34IH128U,10552
419
419
  omlish/lang/iterables.py,sha256=StoGp9yaP3njdLKHoWYcEevO3eE8SHEPYl5_avZob24,2149
420
420
  omlish/lang/objects.py,sha256=nbxBHfQHVw0OG4qeSTP2GvIiFIcH2tbbitY8y-mYPPo,5959
421
421
  omlish/lang/outcomes.py,sha256=mpFy_VoM-b74L1aCFsjsZVUHx_icZ1AHMOKeVesjOp4,8628
422
+ omlish/lang/overrides.py,sha256=IBzK6ljfLX6TLgIyKTSjhqTLcuKRkQNVtEOnBLS4nuA,2095
422
423
  omlish/lang/params.py,sha256=sfbNoGrKCsAtubFufj_uh_WKshIgA8fqJ4PmLH1PH00,6639
423
424
  omlish/lang/recursion.py,sha256=1VfSqzKO-8Is3t9LKw0W4jwPfE0aBS70EUlbUxAx4eE,1900
424
425
  omlish/lang/resolving.py,sha256=ei9LDyJexsMMHB9z8diUkNmynWhd_da7h7TqrMYM6lA,1611
@@ -853,9 +854,9 @@ omlish/typedvalues/holder.py,sha256=ZTnHiw-K38ciOBLEdwgrltr7Xp8jjEs_0Lp69DH-G-o,
853
854
  omlish/typedvalues/marshal.py,sha256=hWHRLcrGav7lvXJDtb9bNI0ickl4SKPQ6F4BbTpqw3A,4219
854
855
  omlish/typedvalues/reflect.py,sha256=Ih1YgU-srUjsvBn_P7C66f73_VCvcwqE3ffeBnZBgt4,674
855
856
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
856
- omlish-0.0.0.dev318.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
857
- omlish-0.0.0.dev318.dist-info/METADATA,sha256=-Nc1DXmiJNc5JokC60eHTE9VTgT6oMoXEf496J6i6UE,4416
858
- omlish-0.0.0.dev318.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
859
- omlish-0.0.0.dev318.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
860
- omlish-0.0.0.dev318.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
861
- omlish-0.0.0.dev318.dist-info/RECORD,,
857
+ omlish-0.0.0.dev319.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
858
+ omlish-0.0.0.dev319.dist-info/METADATA,sha256=AJfjWjvSfK3uESd-jYGGUmEgPjkW22rJLSBh3h6hGbk,4416
859
+ omlish-0.0.0.dev319.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
860
+ omlish-0.0.0.dev319.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
861
+ omlish-0.0.0.dev319.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
862
+ omlish-0.0.0.dev319.dist-info/RECORD,,