omlish 0.0.0.dev394__py3-none-any.whl → 0.0.0.dev396__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.
Files changed (77) hide show
  1. omlish/__about__.py +3 -3
  2. omlish/collections/__init__.py +4 -0
  3. omlish/collections/trie.py +156 -0
  4. omlish/dataclasses/__init__.py +6 -6
  5. omlish/dataclasses/{api → impl/api}/classes/conversion.py +4 -4
  6. omlish/dataclasses/{api → impl/api}/classes/decorator.py +5 -5
  7. omlish/dataclasses/{api → impl/api}/classes/metadata.py +4 -4
  8. omlish/dataclasses/{api → impl/api}/classes/params.py +4 -4
  9. omlish/dataclasses/{api → impl/api}/fields/building.py +7 -7
  10. omlish/dataclasses/{api → impl/api}/fields/constructor.py +4 -4
  11. omlish/dataclasses/{api → impl/api}/fields/conversion.py +8 -8
  12. omlish/dataclasses/{api → impl/api}/fields/metadata.py +5 -5
  13. omlish/dataclasses/{concerns → impl/concerns}/copy.py +1 -1
  14. omlish/dataclasses/{concerns → impl/concerns}/fields.py +6 -6
  15. omlish/dataclasses/{concerns → impl/concerns}/frozen.py +3 -3
  16. omlish/dataclasses/{concerns → impl/concerns}/hash.py +1 -1
  17. omlish/dataclasses/{concerns → impl/concerns}/init.py +9 -9
  18. omlish/dataclasses/{concerns → impl/concerns}/matchargs.py +1 -1
  19. omlish/dataclasses/{concerns → impl/concerns}/mro.py +1 -1
  20. omlish/dataclasses/{concerns → impl/concerns}/params.py +2 -2
  21. omlish/dataclasses/{concerns → impl/concerns}/replace.py +4 -4
  22. omlish/dataclasses/{concerns → impl/concerns}/repr.py +3 -3
  23. omlish/dataclasses/impl/configs.py +12 -0
  24. omlish/dataclasses/{generation → impl/generation}/compilation.py +1 -1
  25. omlish/dataclasses/{generation → impl/generation}/execution.py +1 -1
  26. omlish/dataclasses/{generation → impl/generation}/globals.py +3 -3
  27. omlish/dataclasses/{generation → impl/generation}/mangling.py +1 -1
  28. omlish/dataclasses/{generation → impl/generation}/plans.py +1 -1
  29. omlish/dataclasses/{generation → impl/generation}/processor.py +41 -14
  30. omlish/dataclasses/{generation → impl/generation}/registry.py +2 -2
  31. omlish/dataclasses/impl/processing/__init__.py +0 -0
  32. omlish/dataclasses/{processing → impl/processing}/base.py +3 -3
  33. omlish/dataclasses/{processing → impl/processing}/driving.py +24 -2
  34. omlish/dataclasses/{processing → impl/processing}/registry.py +2 -2
  35. omlish/dataclasses/{utils.py → impl/utils.py} +1 -1
  36. omlish/dataclasses/inspect.py +4 -4
  37. omlish/dataclasses/metaclass/bases.py +2 -2
  38. omlish/dataclasses/metaclass/confer.py +1 -1
  39. omlish/dataclasses/metaclass/meta.py +1 -1
  40. omlish/dataclasses/metaclass/specs.py +1 -1
  41. omlish/dataclasses/reflection.py +9 -9
  42. omlish/dataclasses/tools/as_.py +2 -2
  43. omlish/dataclasses/tools/static.py +2 -2
  44. omlish/diag/pydevd.py +2 -2
  45. omlish/diag/timers.py +184 -0
  46. omlish/inject/impl/injector.py +1 -1
  47. omlish/inject/listeners.py +1 -1
  48. omlish/lang/__init__.py +1 -0
  49. omlish/lang/cached/function.py +10 -0
  50. omlish/lang/contextmanagers.py +27 -0
  51. omlish/reflect/subst.py +43 -2
  52. {omlish-0.0.0.dev394.dist-info → omlish-0.0.0.dev396.dist-info}/METADATA +3 -3
  53. {omlish-0.0.0.dev394.dist-info → omlish-0.0.0.dev396.dist-info}/RECORD +77 -73
  54. /omlish/dataclasses/{internals.py → _internals.py} +0 -0
  55. /omlish/dataclasses/{api/classes → impl}/__init__.py +0 -0
  56. /omlish/dataclasses/{api → impl/api}/__init__.py +0 -0
  57. /omlish/dataclasses/{api/fields → impl/api/classes}/__init__.py +0 -0
  58. /omlish/dataclasses/{api → impl/api}/classes/make.py +0 -0
  59. /omlish/dataclasses/{generation → impl/api/fields}/__init__.py +0 -0
  60. /omlish/dataclasses/{concerns → impl/concerns}/__init__.py +0 -0
  61. /omlish/dataclasses/{concerns → impl/concerns}/abc.py +0 -0
  62. /omlish/dataclasses/{concerns → impl/concerns}/doc.py +0 -0
  63. /omlish/dataclasses/{concerns → impl/concerns}/eq.py +0 -0
  64. /omlish/dataclasses/{concerns → impl/concerns}/order.py +0 -0
  65. /omlish/dataclasses/{concerns → impl/concerns}/override.py +0 -0
  66. /omlish/dataclasses/{concerns → impl/concerns}/slots.py +0 -0
  67. /omlish/dataclasses/{processing → impl/generation}/__init__.py +0 -0
  68. /omlish/dataclasses/{generation → impl/generation}/base.py +0 -0
  69. /omlish/dataclasses/{generation → impl/generation}/idents.py +0 -0
  70. /omlish/dataclasses/{generation → impl/generation}/manifests.py +0 -0
  71. /omlish/dataclasses/{generation → impl/generation}/ops.py +0 -0
  72. /omlish/dataclasses/{generation → impl/generation}/utils.py +0 -0
  73. /omlish/dataclasses/{processing → impl/processing}/priority.py +0 -0
  74. {omlish-0.0.0.dev394.dist-info → omlish-0.0.0.dev396.dist-info}/WHEEL +0 -0
  75. {omlish-0.0.0.dev394.dist-info → omlish-0.0.0.dev396.dist-info}/entry_points.txt +0 -0
  76. {omlish-0.0.0.dev394.dist-info → omlish-0.0.0.dev396.dist-info}/licenses/LICENSE +0 -0
  77. {omlish-0.0.0.dev394.dist-info → omlish-0.0.0.dev396.dist-info}/top_level.txt +0 -0
@@ -8,8 +8,8 @@ import dataclasses as dc
8
8
  import sys
9
9
  import typing as ta
10
10
 
11
- from ... import check
12
- from ... import lang
11
+ from .... import check
12
+ from .... import lang
13
13
  from ..processing.base import ProcessingContext
14
14
  from ..processing.base import ProcessingOption
15
15
  from ..processing.base import Processor
@@ -43,6 +43,21 @@ class Verbose(ProcessingOption):
43
43
  b: bool
44
44
 
45
45
 
46
+ class CompileCallback(ta.Protocol):
47
+ def __call__(
48
+ self,
49
+ ctx: ProcessingContext,
50
+ prepared: 'GeneratorProcessor.Prepared',
51
+ comp: OpCompiler.CompileResult,
52
+ ) -> None:
53
+ ...
54
+
55
+
56
+ @dc.dataclass(frozen=True)
57
+ class Codegen(ProcessingOption):
58
+ callback: CompileCallback
59
+
60
+
46
61
  @register_processor_type(priority=ProcessorPriority.GENERATION)
47
62
  class GeneratorProcessor(Processor):
48
63
  class Mode(abc.ABC):
@@ -61,6 +76,15 @@ class GeneratorProcessor(Processor):
61
76
  opx.execute(op)
62
77
 
63
78
  class CompilerMode(Mode):
79
+ def __init__(
80
+ self,
81
+ *,
82
+ codegen: Codegen,
83
+ ) -> None:
84
+ super().__init__()
85
+
86
+ self._codegen = codegen
87
+
64
88
  def _process(self, gp: 'GeneratorProcessor', cls: type) -> None:
65
89
  compiler = OpCompiler(
66
90
  OpCompiler.AotStyle(),
@@ -115,18 +139,14 @@ class GeneratorProcessor(Processor):
115
139
 
116
140
  fn(**kw)
117
141
 
118
- #
142
+ if (cg := self._codegen) is not None and (cb := cg.callback) is not None:
143
+ cb(
144
+ gp._ctx, # noqa
145
+ gp.prepare(),
146
+ comp,
147
+ )
119
148
 
120
- def __init__(
121
- self,
122
- ctx: ProcessingContext,
123
- *,
124
- mode: Mode = ExecutorMode(),
125
- # mode: Mode = CompilerMode(),
126
- ) -> None:
127
- super().__init__(ctx)
128
-
129
- self._mode = mode
149
+ #
130
150
 
131
151
  @dc.dataclass(frozen=True)
132
152
  class Prepared:
@@ -177,6 +197,13 @@ class GeneratorProcessor(Processor):
177
197
  def process(self, cls: type) -> type:
178
198
  if (po := self._ctx.option(PlanOnly)) is not None and po.b:
179
199
  self.prepare()
200
+ return cls
201
+
202
+ mode: GeneratorProcessor.Mode
203
+ if (cg := self._ctx.option(Codegen)) is not None: # noqa
204
+ mode = GeneratorProcessor.CompilerMode(codegen=cg)
180
205
  else:
181
- self._mode._process(self, cls) # noqa
206
+ mode = GeneratorProcessor.ExecutorMode()
207
+
208
+ mode._process(self, cls) # noqa
182
209
  return cls
@@ -1,7 +1,7 @@
1
1
  import typing as ta
2
2
 
3
- from ... import check
4
- from ... import lang
3
+ from .... import check
4
+ from .... import lang
5
5
  from ..utils import SealableRegistry
6
6
  from .base import Generator
7
7
  from .base import Plan
File without changes
@@ -7,9 +7,9 @@ TODO:
7
7
  """
8
8
  import typing as ta
9
9
 
10
- from ... import check
11
- from ... import lang
12
- from ..specs import ClassSpec
10
+ from .... import check
11
+ from .... import lang
12
+ from ...specs import ClassSpec
13
13
 
14
14
 
15
15
  T = ta.TypeVar('T')
@@ -1,7 +1,11 @@
1
+ import contextlib
2
+ import contextvars
3
+ import typing as ta
1
4
 
5
+ from .... import lang
6
+ from ...specs import ClassSpec
2
7
  from .. import concerns as _concerns # noqa # imported for registration
3
8
  from ..generation import processor as gp
4
- from ..specs import ClassSpec
5
9
  from .base import ProcessingContext
6
10
  from .base import ProcessingOption
7
11
  from .base import Processor
@@ -12,6 +16,24 @@ from .registry import ordered_processor_types
12
16
  ##
13
17
 
14
18
 
19
+ _OPTIONS_CONTEXT_VAR: contextvars.ContextVar[ta.Sequence[ProcessingOption]] = contextvars.ContextVar(
20
+ f'{__name__}._OPTIONS_CONTEXT_VAR',
21
+ default=(),
22
+ )
23
+
24
+
25
+ @contextlib.contextmanager
26
+ def processing_options_context(*opts: ProcessingOption) -> ta.Iterator[None]:
27
+ with lang.context_var_setting(
28
+ _OPTIONS_CONTEXT_VAR,
29
+ (*_OPTIONS_CONTEXT_VAR.get(), *opts),
30
+ ):
31
+ yield
32
+
33
+
34
+ ##
35
+
36
+
15
37
  def drive_cls_processing(
16
38
  cls: type,
17
39
  cs: ClassSpec,
@@ -19,7 +41,7 @@ def drive_cls_processing(
19
41
  plan_only: bool = False,
20
42
  verbose: bool = False,
21
43
  ) -> type:
22
- options: list[ProcessingOption] = []
44
+ options: list[ProcessingOption] = list(_OPTIONS_CONTEXT_VAR.get())
23
45
  if plan_only:
24
46
  options.append(gp.PlanOnly(True))
25
47
  if verbose:
@@ -1,8 +1,8 @@
1
1
  import dataclasses as dc
2
2
  import typing as ta
3
3
 
4
- from ... import check
5
- from ... import lang
4
+ from .... import check
5
+ from .... import lang
6
6
  from ..utils import SealableRegistry
7
7
  from .base import ProcessingContextItemFactory
8
8
  from .base import Processor
@@ -4,7 +4,7 @@ import functools
4
4
  import types
5
5
  import typing as ta
6
6
 
7
- from .. import check
7
+ from ... import check
8
8
 
9
9
 
10
10
  T = ta.TypeVar('T')
@@ -7,10 +7,10 @@ from .. import check
7
7
  from .. import collections as col
8
8
  from .. import lang
9
9
  from .. import reflect as rfl
10
- from .internals import STD_FIELDS_ATTR
11
- from .internals import StdFieldType
12
- from .internals import std_field_type
13
- from .internals import std_is_kw_only
10
+ from ._internals import STD_FIELDS_ATTR
11
+ from ._internals import StdFieldType
12
+ from ._internals import std_field_type
13
+ from ._internals import std_is_kw_only
14
14
 
15
15
 
16
16
  ClassAnnotations: ta.TypeAlias = ta.Mapping[str, ta.Any]
@@ -1,7 +1,7 @@
1
1
  import typing as ta
2
2
 
3
- from ..api.classes.decorator import dataclass
4
- from ..concerns.frozen import unchecked_frozen_base
3
+ from ..impl.api.classes.decorator import dataclass
4
+ from ..impl.concerns.frozen import unchecked_frozen_base
5
5
  from .meta import DataMeta
6
6
  from .specs import get_metaclass_spec
7
7
 
@@ -1,7 +1,7 @@
1
1
  import dataclasses as dc
2
2
  import typing as ta
3
3
 
4
- from ..api.classes.params import get_class_spec
4
+ from ..impl.api.classes.params import get_class_spec
5
5
  from .specs import get_metaclass_spec
6
6
 
7
7
 
@@ -8,7 +8,7 @@ import dataclasses as dc
8
8
  import typing as ta
9
9
 
10
10
  from ... import lang
11
- from ..api.classes.decorator import dataclass
11
+ from ..impl.api.classes.decorator import dataclass
12
12
  from .confer import CONFER_METACLASS_PARAMS
13
13
  from .confer import confer_kwargs
14
14
  from .specs import MetaclassSpec
@@ -1,7 +1,7 @@
1
1
  import dataclasses as dc
2
2
 
3
3
  from ... import lang
4
- from ..api.classes.params import get_class_spec
4
+ from ..impl.api.classes.params import get_class_spec
5
5
  from ..specs import ClassSpec
6
6
 
7
7
 
@@ -2,17 +2,17 @@ import dataclasses as dc
2
2
  import typing as ta
3
3
 
4
4
  from .. import lang
5
- from .api.classes.conversion import std_params_to_class_spec
6
- from .api.classes.metadata import extract_cls_metadata
7
- from .api.classes.params import get_class_spec
8
- from .api.fields.conversion import std_field_to_field_spec
9
- from .concerns.fields import InitFields
10
- from .concerns.fields import calc_init_fields
5
+ from ._internals import STD_PARAMS_ATTR
6
+ from ._internals import StdFieldType
7
+ from ._internals import std_field_type
8
+ from .impl.api.classes.conversion import std_params_to_class_spec
9
+ from .impl.api.classes.metadata import extract_cls_metadata
10
+ from .impl.api.classes.params import get_class_spec
11
+ from .impl.api.fields.conversion import std_field_to_field_spec
12
+ from .impl.concerns.fields import InitFields
13
+ from .impl.concerns.fields import calc_init_fields
11
14
  from .inspect import FieldsInspection
12
15
  from .inspect import inspect_fields
13
- from .internals import STD_PARAMS_ATTR
14
- from .internals import StdFieldType
15
- from .internals import std_field_type
16
16
  from .specs import ClassSpec
17
17
 
18
18
 
@@ -2,8 +2,8 @@ import copy
2
2
  import dataclasses as dc
3
3
  import typing as ta
4
4
 
5
- from ..internals import STD_ATOMIC_TYPES
6
- from ..internals import std_is_dataclass_instance
5
+ from .._internals import STD_ATOMIC_TYPES
6
+ from .._internals import std_is_dataclass_instance
7
7
 
8
8
 
9
9
  ##
@@ -10,7 +10,7 @@ import typing as ta
10
10
 
11
11
  from ... import lang
12
12
  from ...lite.dataclasses import is_immediate_dataclass
13
- from ..api.classes.decorator import dataclass
13
+ from ..impl.api.classes.decorator import dataclass
14
14
 
15
15
 
16
16
  ##
@@ -123,7 +123,7 @@ class Static(lang.Abstract):
123
123
  new_fld.default_factory = (lambda v2: lambda: v2)(v) # noqa
124
124
 
125
125
  # FIXME
126
- from ..api.fields.metadata import _ExtraFieldParamsMetadata # noqa
126
+ from ..impl.api.fields.metadata import _ExtraFieldParamsMetadata # noqa
127
127
  from ..specs import FieldSpec
128
128
  try:
129
129
  x_fs = fld.metadata[FieldSpec]
omlish/diag/pydevd.py CHANGED
@@ -124,7 +124,7 @@ def is_present() -> bool:
124
124
 
125
125
  def get_setup() -> dict | None:
126
126
  if is_present():
127
- return _pydevd().SetupHolder.setup
127
+ return check.not_none(_pydevd()).SetupHolder.setup
128
128
  else:
129
129
  return None
130
130
 
@@ -160,7 +160,7 @@ ARGS_ENV_VAR = 'PYDEVD_ARGS'
160
160
  def get_args() -> list[str]:
161
161
  check.state(is_present())
162
162
  setup: ta.Mapping[ta.Any, ta.Any] = check.isinstance(get_setup(), dict)
163
- args = [_pydevd().__file__]
163
+ args = [check.not_none(check.not_none(_pydevd()).__file__)]
164
164
 
165
165
  for k in [
166
166
  'port',
omlish/diag/timers.py ADDED
@@ -0,0 +1,184 @@
1
+ import atexit
2
+ import contextlib
3
+ import functools
4
+ import sys
5
+ import threading
6
+ import time
7
+ import typing as ta
8
+
9
+ from .. import check
10
+ from .. import lang
11
+
12
+
13
+ T = ta.TypeVar('T')
14
+
15
+
16
+ ##
17
+
18
+
19
+ class _GlobalTimer:
20
+ def __init__(
21
+ self,
22
+ registry: '_GlobalTimerRegistry',
23
+ name: str,
24
+ *,
25
+ clock: ta.Callable[[], float] | None = None,
26
+ report_at_exit: bool = False,
27
+ report_out: ta.Any | None = None,
28
+ ) -> None:
29
+ super().__init__()
30
+
31
+ self._registry = registry
32
+ self._name = name
33
+
34
+ if clock is None:
35
+ clock = time.monotonic
36
+ self._clock = clock
37
+ self._report_out = report_out
38
+
39
+ self._lock = threading.Lock()
40
+
41
+ self._c = 0
42
+ self._t = 0.
43
+
44
+ if report_at_exit:
45
+ atexit.register(self.report)
46
+
47
+ def report(self) -> None:
48
+ msg = (
49
+ f'{self._registry.module_name}::{self._name}: '
50
+ f'{self._c} calls, '
51
+ f'{self._t:.3f}s total'
52
+ )
53
+
54
+ print(
55
+ msg,
56
+ file=self._report_out if self._report_out is not None else sys.stderr,
57
+ )
58
+
59
+ @contextlib.contextmanager
60
+ def __call__(self) -> ta.Iterator[None]:
61
+ start = self._clock()
62
+
63
+ try:
64
+ yield
65
+
66
+ finally:
67
+ end = self._clock()
68
+ took = end - start
69
+
70
+ with self._lock:
71
+ self._c += 1
72
+ self._t += took
73
+
74
+
75
+ #
76
+
77
+
78
+ class _GlobalTimerRegistry:
79
+ def __init__(
80
+ self,
81
+ module_name: str,
82
+ ) -> None:
83
+ super().__init__()
84
+
85
+ self._module_name = module_name
86
+
87
+ self._lock = threading.Lock()
88
+
89
+ self._timers: dict[str, _GlobalTimer] = {}
90
+
91
+ @property
92
+ def module_name(self) -> str:
93
+ return self._module_name
94
+
95
+ def get_timer(
96
+ self,
97
+ name: str,
98
+ **kwargs: ta.Any,
99
+ ) -> _GlobalTimer:
100
+ return lang.double_check_setdefault(
101
+ self._lock,
102
+ self._timers,
103
+ name,
104
+ lambda: _GlobalTimer(
105
+ self,
106
+ name,
107
+ **kwargs,
108
+ ),
109
+ )
110
+
111
+
112
+ _GLOBAL_LOCK = threading.Lock()
113
+ _GLOBAL_ATTR = '__global_timers_registry__'
114
+
115
+
116
+ def _get_global_registry(
117
+ globals: ta.MutableMapping[str, ta.Any], # noqa
118
+ ) -> _GlobalTimerRegistry:
119
+ reg = lang.double_check_setdefault(
120
+ _GLOBAL_LOCK,
121
+ globals,
122
+ _GLOBAL_ATTR,
123
+ lambda: _GlobalTimerRegistry(
124
+ globals['__name__'],
125
+ ),
126
+ )
127
+
128
+ return check.isinstance(reg, _GlobalTimerRegistry)
129
+
130
+
131
+ #
132
+
133
+
134
+ @contextlib.contextmanager
135
+ def global_timer_context(
136
+ globals: ta.MutableMapping[str, ta.Any], # noqa
137
+ name: str,
138
+ **kwargs: ta.Any,
139
+ ) -> ta.Iterator[None]:
140
+ reg = _get_global_registry(globals)
141
+ timer = reg.get_timer(name, **kwargs)
142
+ with timer():
143
+ yield
144
+
145
+
146
+ #
147
+
148
+
149
+ class _GlobalTimerContextWrapped:
150
+ def __init__(
151
+ self,
152
+ fn: ta.Any,
153
+ timer: _GlobalTimer,
154
+ ) -> None:
155
+ super().__init__()
156
+
157
+ self._fn = fn
158
+ self._timer = timer
159
+
160
+ functools.update_wrapper(self, fn)
161
+
162
+ def __get__(self, instance, owner=None):
163
+ return self.__class__(
164
+ self._fn.__get__(instance, owner),
165
+ self._timer,
166
+ )
167
+
168
+ def __call__(self, *args, **kwargs):
169
+ with self._timer():
170
+ return self._fn(*args, **kwargs)
171
+
172
+
173
+ def global_timer_wrap(
174
+ globals: ta.MutableMapping[str, ta.Any], # noqa
175
+ name: str,
176
+ **kwargs: ta.Any,
177
+ ) -> ta.Callable[[T], T]:
178
+ reg = _get_global_registry(globals)
179
+ timer = reg.get_timer(name, **kwargs)
180
+
181
+ def inner(fn):
182
+ return _GlobalTimerContextWrapped(fn, timer)
183
+
184
+ return inner
@@ -69,7 +69,7 @@ class InjectorImpl(Injector, lang.Final):
69
69
  self._bim = ec.binding_impl_map()
70
70
  self._ekbs = ec.eager_keys_by_scope()
71
71
  self._pls: tuple[ProvisionListener, ...] = tuple(
72
- b.listener
72
+ b.listener # type: ignore[attr-defined]
73
73
  for b in itertools.chain(
74
74
  ec.elements_of_type(ProvisionListenerBinding),
75
75
  (p._pls if p is not None else ()), # noqa
@@ -14,7 +14,7 @@ from .keys import Key
14
14
  ProvisionListener: ta.TypeAlias = ta.Callable[[
15
15
  Injector,
16
16
  Key,
17
- Binding,
17
+ Binding | None,
18
18
  ta.Callable[[], ta.Any],
19
19
  ], ta.Callable[[], ta.Any]]
20
20
 
omlish/lang/__init__.py CHANGED
@@ -142,6 +142,7 @@ from .contextmanagers import ( # noqa
142
142
  context_wrapped,
143
143
  default_lock,
144
144
  disposing,
145
+ double_check_setdefault,
145
146
  maybe_managing,
146
147
  )
147
148
 
@@ -415,6 +415,16 @@ class _DescriptorCachedFunction(_CachedFunction[T]):
415
415
  #
416
416
 
417
417
 
418
+ @ta.overload
419
+ def cached_function(fn: None = None, **kwargs: ta.Any) -> ta.Callable[[CallableT], CallableT]:
420
+ ...
421
+
422
+
423
+ @ta.overload
424
+ def cached_function(fn: CallableT, **kwargs: ta.Any) -> CallableT:
425
+ ...
426
+
427
+
418
428
  def cached_function(fn=None, **kwargs): # noqa
419
429
  if fn is None:
420
430
  return functools.partial(cached_function, **kwargs)
@@ -13,6 +13,8 @@ import typing as ta
13
13
 
14
14
 
15
15
  T = ta.TypeVar('T')
16
+ K = ta.TypeVar('K')
17
+ V = ta.TypeVar('V')
16
18
 
17
19
 
18
20
  ##
@@ -286,3 +288,28 @@ class Timer:
286
288
 
287
289
  def __exit__(self, exc_type, exc_val, exc_tb):
288
290
  self._end = self._clock()
291
+
292
+
293
+ ##
294
+
295
+
296
+ def double_check_setdefault(
297
+ cm: ta.ContextManager,
298
+ dct: ta.MutableMapping[K, V],
299
+ k: K,
300
+ fn: ta.Callable[[], V],
301
+ ) -> V:
302
+ try:
303
+ return dct[k]
304
+ except KeyError:
305
+ pass
306
+
307
+ with cm:
308
+ try:
309
+ return dct[k]
310
+ except KeyError:
311
+ pass
312
+
313
+ v = fn()
314
+ dct[k] = v
315
+ return v
omlish/reflect/subst.py CHANGED
@@ -1,3 +1,4 @@
1
+ import abc
1
2
  import dataclasses as dc
2
3
  import types
3
4
  import typing as ta
@@ -22,6 +23,9 @@ else:
22
23
  cache = lang.proxy_import('.collections.cache', __package__)
23
24
 
24
25
 
26
+ GenericBasesMap: ta.TypeAlias = ta.Mapping[Type, tuple[Type, ...]]
27
+
28
+
25
29
  ##
26
30
 
27
31
 
@@ -69,22 +73,35 @@ def replace_type_vars(
69
73
  return rec(ty)
70
74
 
71
75
 
76
+ ##
77
+
78
+
79
+ _DEFAULT_SIMPLE_GENERIC_BASES: dict[Type, tuple[Type, ...]] = {}
80
+ DEFAULT_SIMPLE_GENERIC_BASES: GenericBasesMap = _DEFAULT_SIMPLE_GENERIC_BASES
81
+
82
+
72
83
  class GenericSubstitution:
73
84
  def __init__(
74
85
  self,
75
86
  *,
76
87
  update_aliases: bool = False,
77
88
  cache_size: int = 0, # FIXME: ta.Generic isn't weakrefable..
89
+ simple_generic_bases: GenericBasesMap | None = None,
78
90
  ) -> None:
79
91
  super().__init__()
80
92
 
81
93
  self._update_aliases = update_aliases
94
+ self._simple_generic_bases = simple_generic_bases
82
95
 
83
96
  if cache_size > 0:
84
97
  self.get_generic_bases = cache.cache(weak_keys=True, max_size=cache_size)(self.get_generic_bases) # type: ignore # noqa
85
98
  self.generic_mro = cache.cache(weak_keys=True, max_size=cache_size)(self.generic_mro) # type: ignore
86
99
 
87
100
  def get_generic_bases(self, ty: Type) -> tuple[Type, ...]:
101
+ if (sgm := self._simple_generic_bases) is not None:
102
+ if (sgb := sgm.get(ty)) is not None:
103
+ return sgb
104
+
88
105
  if (cty := get_concrete_type(ty)) is not None:
89
106
  rpl = get_type_var_replacements(ty)
90
107
  ret: list[Type] = []
@@ -97,6 +114,7 @@ class GenericSubstitution:
97
114
  rty = replace_type_vars(bty, rpl, update_aliases=self._update_aliases)
98
115
  ret.append(rty)
99
116
  return tuple(ret)
117
+
100
118
  return ()
101
119
 
102
120
  def generic_mro(self, obj: ta.Any) -> list[Type]:
@@ -108,9 +126,32 @@ class GenericSubstitution:
108
126
  return [ty for ty in mro if get_concrete_type(ty) is not ta.Generic]
109
127
 
110
128
 
111
- DEFAULT_GENERIC_SUBSTITUTION = GenericSubstitution()
129
+ DEFAULT_GENERIC_SUBSTITUTION = GenericSubstitution(
130
+ simple_generic_bases=DEFAULT_SIMPLE_GENERIC_BASES,
131
+ )
112
132
 
113
133
  get_generic_bases = DEFAULT_GENERIC_SUBSTITUTION.get_generic_bases
114
134
  generic_mro = DEFAULT_GENERIC_SUBSTITUTION.generic_mro
115
135
 
116
- ALIAS_UPDATING_GENERIC_SUBSTITUTION = GenericSubstitution(update_aliases=True)
136
+ ALIAS_UPDATING_GENERIC_SUBSTITUTION = GenericSubstitution(
137
+ update_aliases=True,
138
+ simple_generic_bases=DEFAULT_SIMPLE_GENERIC_BASES,
139
+ )
140
+
141
+
142
+ ##
143
+
144
+
145
+ _DEFAULT_SIMPLE_GENERIC_BASE_LIST: ta.Sequence[Type] = [
146
+ object,
147
+ abc.ABC,
148
+ lang.Abstract,
149
+ lang.Sealed,
150
+ lang.PackageSealed,
151
+ lang.Final,
152
+ ]
153
+
154
+ _DEFAULT_SIMPLE_GENERIC_BASES.update({
155
+ b: get_generic_bases(b)
156
+ for b in _DEFAULT_SIMPLE_GENERIC_BASE_LIST
157
+ })