omlish 0.0.0.dev282__py3-none-any.whl → 0.0.0.dev284__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 (59) hide show
  1. omlish/__about__.py +4 -4
  2. omlish/collections/__init__.py +1 -1
  3. omlish/collections/mappings.py +9 -0
  4. omlish/collections/utils.py +10 -1
  5. omlish/dataclasses/__init__.py +2 -2
  6. omlish/dataclasses/impl/api.py +1 -1
  7. omlish/dataclasses/impl/{exceptions.py → errors.py} +3 -0
  8. omlish/dataclasses/impl/fields.py +1 -1
  9. omlish/dataclasses/impl/init.py +1 -1
  10. omlish/dataclasses/impl/order.py +11 -1
  11. omlish/inject/__init__.py +1 -1
  12. omlish/inject/bindings.py +1 -1
  13. omlish/inject/eagers.py +1 -1
  14. omlish/inject/impl/bindings.py +1 -1
  15. omlish/inject/impl/elements.py +2 -2
  16. omlish/inject/impl/injector.py +2 -2
  17. omlish/inject/impl/inspect.py +1 -1
  18. omlish/inject/impl/scopes.py +2 -2
  19. omlish/inject/keys.py +1 -1
  20. omlish/inject/listeners.py +1 -1
  21. omlish/inject/multis.py +4 -4
  22. omlish/inject/origins.py +2 -2
  23. omlish/inject/overrides.py +1 -1
  24. omlish/inject/privates.py +2 -2
  25. omlish/inject/providers.py +4 -4
  26. omlish/inject/scopes.py +3 -3
  27. omlish/lang/__init__.py +8 -3
  28. omlish/lang/cached/function.py +9 -0
  29. omlish/lang/cached/property.py +3 -1
  30. omlish/lang/comparison.py +3 -0
  31. omlish/lang/enums.py +8 -0
  32. omlish/lang/imports.py +68 -10
  33. omlish/lang/objects.py +0 -17
  34. omlish/lite/reprs.py +84 -0
  35. omlish/marshal/__init__.py +1 -1
  36. omlish/marshal/base.py +1 -1
  37. omlish/marshal/{exceptions.py → errors.py} +3 -0
  38. omlish/marshal/objects/dataclasses.py +4 -4
  39. omlish/marshal/trivial/forbidden.py +1 -1
  40. omlish/specs/jmespath/__init__.py +1 -1
  41. omlish/specs/jmespath/cli.py +5 -5
  42. omlish/specs/jmespath/functions.py +6 -6
  43. omlish/specs/jmespath/lexer.py +2 -2
  44. omlish/specs/jmespath/parser.py +3 -3
  45. omlish/specs/jmespath/visitor.py +1 -1
  46. omlish/sql/queries/base.py +3 -3
  47. omlish/text/mangle.py +66 -7
  48. omlish/typedvalues/values.py +1 -1
  49. {omlish-0.0.0.dev282.dist-info → omlish-0.0.0.dev284.dist-info}/METADATA +3 -3
  50. {omlish-0.0.0.dev282.dist-info → omlish-0.0.0.dev284.dist-info}/RECORD +57 -57
  51. omlish/dataclasses/impl/descriptors.py +0 -93
  52. omlish/lang/exceptions.py +0 -2
  53. /omlish/collections/{exceptions.py → errors.py} +0 -0
  54. /omlish/inject/{exceptions.py → errors.py} +0 -0
  55. /omlish/specs/jmespath/{exceptions.py → errors.py} +0 -0
  56. {omlish-0.0.0.dev282.dist-info → omlish-0.0.0.dev284.dist-info}/WHEEL +0 -0
  57. {omlish-0.0.0.dev282.dist-info → omlish-0.0.0.dev284.dist-info}/entry_points.txt +0 -0
  58. {omlish-0.0.0.dev282.dist-info → omlish-0.0.0.dev284.dist-info}/licenses/LICENSE +0 -0
  59. {omlish-0.0.0.dev282.dist-info → omlish-0.0.0.dev284.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev282'
2
- __revision__ = '7838e4a0dc1a1434797f27f330d5d3efdd410927'
1
+ __version__ = '0.0.0.dev284'
2
+ __revision__ = 'e10caaddb37a1fedcbe1bf3e711062e82a8cc7cb'
3
3
 
4
4
 
5
5
  #
@@ -35,7 +35,7 @@ class Project(ProjectBase):
35
35
  'anyio ~= 4.9',
36
36
  'sniffio ~= 1.3',
37
37
 
38
- 'greenlet ~= 3.1',
38
+ 'greenlet ~= 3.2',
39
39
 
40
40
  'trio ~= 0.29',
41
41
  'trio-asyncio ~= 0.15',
@@ -92,7 +92,7 @@ class Project(ProjectBase):
92
92
  # 'psycopg ~= 3.2',
93
93
 
94
94
  'pymysql ~= 1.1',
95
- # 'mysql-connector-python ~= 9.1',
95
+ # 'mysql-connector-python ~= 9.3',
96
96
  # 'mysqlclient ~= 2.2',
97
97
 
98
98
  'aiomysql ~= 0.2',
@@ -31,7 +31,7 @@ from .coerce import ( # noqa
31
31
  seq_or_none,
32
32
  )
33
33
 
34
- from .exceptions import ( # noqa
34
+ from .errors import ( # noqa
35
35
  DuplicateKeyError,
36
36
  )
37
37
 
@@ -23,6 +23,9 @@ def multikey_dict(dct: ta.Mapping[ta.Iterable[K] | K, V], *, deep: bool = False)
23
23
  return ret
24
24
 
25
25
 
26
+ ##
27
+
28
+
26
29
  def guarded_map_update(
27
30
  dst: ta.MutableMapping[ta.Any, ta.Any],
28
31
  *srcs: ta.Mapping[ta.Any, ta.Any],
@@ -35,6 +38,9 @@ def guarded_map_update(
35
38
  return dst
36
39
 
37
40
 
41
+ ##
42
+
43
+
38
44
  class TypeMap(ta.Generic[T]):
39
45
  def __init__(self, items: ta.Iterable[T] = ()) -> None:
40
46
  super().__init__()
@@ -99,6 +105,9 @@ class DynamicTypeMap(ta.Generic[V]):
99
105
  return ret
100
106
 
101
107
 
108
+ ##
109
+
110
+
102
111
  class MissingDict(dict[K, V]):
103
112
  def __init__(self, missing_fn: ta.Callable[[K], V]) -> None:
104
113
  if not callable(missing_fn):
@@ -1,7 +1,7 @@
1
1
  import typing as ta
2
2
 
3
3
  from .. import lang
4
- from .exceptions import DuplicateKeyError
4
+ from .errors import DuplicateKeyError
5
5
  from .identity import IdentityKeyDict
6
6
  from .identity import IdentitySet
7
7
 
@@ -30,6 +30,9 @@ def partition(items: ta.Iterable[T], pred: ta.Callable[[T], bool]) -> PartitionR
30
30
  return PartitionResult(t, f)
31
31
 
32
32
 
33
+ ##
34
+
35
+
33
36
  def unique(
34
37
  it: ta.Iterable[T],
35
38
  *,
@@ -52,6 +55,9 @@ def unique(
52
55
  return ret
53
56
 
54
57
 
58
+ ##
59
+
60
+
55
61
  def make_map(
56
62
  kvs: ta.Iterable[tuple[K, V]],
57
63
  *,
@@ -82,6 +88,9 @@ def make_map_by(
82
88
  )
83
89
 
84
90
 
91
+ ##
92
+
93
+
85
94
  def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
86
95
  d: ta.MutableMapping[K, list[V]] = IdentityKeyDict() if identity else {}
87
96
  l: list[V]
@@ -28,7 +28,7 @@ from .impl.api import ( # noqa
28
28
  dataclass as xdataclass,
29
29
  make_dataclass as xmake_dataclass,
30
30
 
31
- extra_params,
31
+ extra_class_params,
32
32
  )
33
33
 
34
34
  from .impl.as_ import ( # noqa
@@ -65,7 +65,7 @@ globals()['make_dataclass'] = xmake_dataclass
65
65
  ##
66
66
 
67
67
 
68
- from .impl.exceptions import ( # noqa
68
+ from .impl.errors import ( # noqa
69
69
  FieldValidationError,
70
70
  ValidationError,
71
71
  )
@@ -247,7 +247,7 @@ class _ExtraParamsKwargs:
247
247
  pass
248
248
 
249
249
 
250
- def extra_params( # noqa
250
+ def extra_class_params( # noqa
251
251
  *,
252
252
  reorder=MISSING,
253
253
  cache_hash=MISSING,
@@ -2,6 +2,9 @@ import types
2
2
  import typing as ta
3
3
 
4
4
 
5
+ ##
6
+
7
+
5
8
  class ValidationError(Exception):
6
9
  pass
7
10
 
@@ -8,7 +8,7 @@ import typing as ta
8
8
 
9
9
  from ... import check as check_
10
10
  from ... import lang
11
- from .exceptions import FieldValidationError
11
+ from .errors import FieldValidationError
12
12
  from .internals import FIELDS_ATTR
13
13
  from .internals import FieldType
14
14
  from .internals import is_classvar
@@ -3,7 +3,7 @@ import inspect
3
3
  import typing as ta
4
4
 
5
5
  from ... import lang
6
- from .exceptions import ValidationError
6
+ from .errors import ValidationError
7
7
  from .fields import field_init
8
8
  from .fields import field_type
9
9
  from .fields import has_default
@@ -40,7 +40,17 @@ class OrderProcessor(Processor):
40
40
  ('__gt__', '>'),
41
41
  ('__ge__', '>='),
42
42
  ]:
43
- if set_new_attribute(self._cls, name, cmp_fn(name, op, self_tuple, other_tuple, globals=self._info.globals)): # noqa
43
+ if set_new_attribute(
44
+ self._cls, # noqa
45
+ name,
46
+ cmp_fn(
47
+ name,
48
+ op,
49
+ self_tuple,
50
+ other_tuple,
51
+ globals=self._info.globals,
52
+ ),
53
+ ):
44
54
  raise TypeError(
45
55
  f'Cannot overwrite attribute {name} in class {self._cls.__name__}. '
46
56
  f'Consider using functools.total_ordering',
omlish/inject/__init__.py CHANGED
@@ -21,7 +21,7 @@ from .elements import ( # noqa
21
21
  as_elements,
22
22
  )
23
23
 
24
- from .exceptions import ( # noqa
24
+ from .errors import ( # noqa
25
25
  BaseKeyError,
26
26
  ConflictingKeyError,
27
27
  CyclicDependencyError,
omlish/inject/bindings.py CHANGED
@@ -12,7 +12,7 @@ from .types import Unscoped
12
12
 
13
13
 
14
14
  @dc.dataclass(frozen=True)
15
- @dc.extra_params(cache_hash=True)
15
+ @dc.extra_class_params(cache_hash=True)
16
16
  class Binding(Element, lang.Final):
17
17
  key: Key = dc.xfield(coerce=check.of_isinstance(Key))
18
18
  provider: Provider = dc.xfield(coerce=check.of_isinstance(Provider))
omlish/inject/eagers.py CHANGED
@@ -13,6 +13,6 @@ from .keys import Key
13
13
 
14
14
 
15
15
  @dc.dataclass(frozen=True)
16
- @dc.extra_params(cache_hash=True)
16
+ @dc.extra_class_params(cache_hash=True)
17
17
  class Eager(Element, lang.Final):
18
18
  key: Key = dc.xfield(coerce=check.of_isinstance(Key))
@@ -11,7 +11,7 @@ from .providers import ProviderImpl
11
11
 
12
12
 
13
13
  @dc.dataclass(frozen=True, eq=False)
14
- @dc.extra_params(cache_hash=True)
14
+ @dc.extra_class_params(cache_hash=True)
15
15
  class BindingImpl(lang.Final):
16
16
  key: Key
17
17
  provider: ProviderImpl
@@ -29,8 +29,8 @@ from ..bindings import Binding
29
29
  from ..eagers import Eager
30
30
  from ..elements import Element
31
31
  from ..elements import Elements
32
- from ..exceptions import ConflictingKeyError
33
- from ..exceptions import UnboundKeyError
32
+ from ..errors import ConflictingKeyError
33
+ from ..errors import UnboundKeyError
34
34
  from ..keys import Key
35
35
  from ..listeners import ProvisionListenerBinding
36
36
  from ..multis import MapBinding
@@ -23,8 +23,8 @@ import weakref
23
23
  from ... import check
24
24
  from ... import lang
25
25
  from ..elements import Elements
26
- from ..exceptions import CyclicDependencyError
27
- from ..exceptions import UnboundKeyError
26
+ from ..errors import CyclicDependencyError
27
+ from ..errors import UnboundKeyError
28
28
  from ..injector import Injector
29
29
  from ..inspect import KwargsTarget
30
30
  from ..keys import Key
@@ -12,7 +12,7 @@ import weakref
12
12
 
13
13
  from ... import check
14
14
  from ... import reflect as rfl
15
- from ..exceptions import ConflictingKeyError
15
+ from ..errors import ConflictingKeyError
16
16
  from ..inspect import Kwarg
17
17
  from ..inspect import KwargsTarget
18
18
  from ..keys import Key
@@ -15,8 +15,8 @@ from ... import lang
15
15
  from ..bindings import Binding
16
16
  from ..elements import Elements
17
17
  from ..elements import as_elements
18
- from ..exceptions import ScopeAlreadyOpenError
19
- from ..exceptions import ScopeNotOpenError
18
+ from ..errors import ScopeAlreadyOpenError
19
+ from ..errors import ScopeNotOpenError
20
20
  from ..injector import Injector
21
21
  from ..keys import Key
22
22
  from ..providers import FnProvider
omlish/inject/keys.py CHANGED
@@ -14,7 +14,7 @@ T = ta.TypeVar('T')
14
14
 
15
15
 
16
16
  @dc.dataclass(frozen=True)
17
- @dc.extra_params(cache_hash=True)
17
+ @dc.extra_class_params(cache_hash=True)
18
18
  class Key(lang.Final, ta.Generic[T]):
19
19
  ty: rfl.Type = dc.xfield(coerce=rfl.type_)
20
20
 
@@ -20,7 +20,7 @@ ProvisionListener: ta.TypeAlias = ta.Callable[[
20
20
 
21
21
 
22
22
  @dc.dataclass(frozen=True)
23
- @dc.extra_params(cache_hash=True)
23
+ @dc.extra_class_params(cache_hash=True)
24
24
  class ProvisionListenerBinding(Element, lang.Final):
25
25
  listener: ProvisionListener
26
26
 
omlish/inject/multis.py CHANGED
@@ -30,14 +30,14 @@ def _check_set_multi_key(mk: Key) -> bool:
30
30
 
31
31
 
32
32
  @dc.dataclass(frozen=True)
33
- @dc.extra_params(cache_hash=True)
33
+ @dc.extra_class_params(cache_hash=True)
34
34
  class SetBinding(Element, lang.Final):
35
35
  multi_key: Key = dc.xfield(validate=_check_set_multi_key)
36
36
  dst: Key = dc.xfield(coerce=check.of_isinstance(Key))
37
37
 
38
38
 
39
39
  @dc.dataclass(frozen=True)
40
- @dc.extra_params(cache_hash=True)
40
+ @dc.extra_class_params(cache_hash=True)
41
41
  class SetProvider(Provider):
42
42
  multi_key: Key = dc.xfield(validate=_check_set_multi_key)
43
43
 
@@ -50,7 +50,7 @@ def _check_map_multi_key(mk: Key) -> bool:
50
50
 
51
51
 
52
52
  @dc.dataclass(frozen=True)
53
- @dc.extra_params(cache_hash=True)
53
+ @dc.extra_class_params(cache_hash=True)
54
54
  class MapBinding(Element, lang.Final):
55
55
  multi_key: Key = dc.xfield(validate=_check_map_multi_key)
56
56
  map_key: ta.Any = dc.xfield()
@@ -58,7 +58,7 @@ class MapBinding(Element, lang.Final):
58
58
 
59
59
 
60
60
  @dc.dataclass(frozen=True)
61
- @dc.extra_params(cache_hash=True)
61
+ @dc.extra_class_params(cache_hash=True)
62
62
  class MapProvider(Provider):
63
63
  multi_key: Key = dc.xfield(validate=_check_map_multi_key)
64
64
 
omlish/inject/origins.py CHANGED
@@ -12,13 +12,13 @@ T = ta.TypeVar('T')
12
12
 
13
13
 
14
14
  @dc.dataclass(frozen=True)
15
- @dc.extra_params(cache_hash=True)
15
+ @dc.extra_class_params(cache_hash=True)
16
16
  class Origin:
17
17
  lst: ta.Sequence[str]
18
18
 
19
19
 
20
20
  @dc.dataclass(frozen=True)
21
- @dc.extra_params(cache_hash=True)
21
+ @dc.extra_class_params(cache_hash=True)
22
22
  class Origins:
23
23
  lst: ta.Sequence[Origin]
24
24
 
@@ -12,7 +12,7 @@ from .elements import as_elements
12
12
 
13
13
 
14
14
  @dc.dataclass(frozen=True)
15
- @dc.extra_params(cache_hash=True)
15
+ @dc.extra_class_params(cache_hash=True)
16
16
  class Overrides(Element, lang.Final):
17
17
  ovr: Elements = dc.xfield(coerce=check.of_isinstance(Elements))
18
18
  src: Elements = dc.xfield(coerce=check.of_isinstance(Elements))
omlish/inject/privates.py CHANGED
@@ -13,13 +13,13 @@ from .keys import as_key
13
13
 
14
14
 
15
15
  @dc.dataclass(frozen=True)
16
- @dc.extra_params(cache_hash=True)
16
+ @dc.extra_class_params(cache_hash=True)
17
17
  class Expose(Element, lang.Final):
18
18
  key: Key = dc.xfield(coerce=as_key)
19
19
 
20
20
 
21
21
  @dc.dataclass(frozen=True)
22
- @dc.extra_params(cache_hash=True)
22
+ @dc.extra_class_params(cache_hash=True)
23
23
  class Private(Element, lang.Final):
24
24
  elements: Elements = dc.xfield(coerce=check.of_isinstance(Elements))
25
25
 
@@ -18,24 +18,24 @@ class Provider(lang.Abstract):
18
18
 
19
19
 
20
20
  @dc.dataclass(frozen=True)
21
- @dc.extra_params(cache_hash=True)
21
+ @dc.extra_class_params(cache_hash=True)
22
22
  class FnProvider(Provider):
23
23
  fn: ta.Any = dc.xfield(validate=callable)
24
24
 
25
25
 
26
26
  @dc.dataclass(frozen=True)
27
- @dc.extra_params(cache_hash=True)
27
+ @dc.extra_class_params(cache_hash=True)
28
28
  class CtorProvider(Provider):
29
29
  ty: type = dc.xfield(coerce=check.of_isinstance(type))
30
30
 
31
31
 
32
32
  @dc.dataclass(frozen=True)
33
- @dc.extra_params(cache_hash=True)
33
+ @dc.extra_class_params(cache_hash=True)
34
34
  class ConstProvider(Provider):
35
35
  v: ta.Any
36
36
 
37
37
 
38
38
  @dc.dataclass(frozen=True)
39
- @dc.extra_params(cache_hash=True)
39
+ @dc.extra_class_params(cache_hash=True)
40
40
  class LinkProvider(Provider):
41
41
  k: Key = dc.xfield(coerce=check.of_isinstance(Key))
omlish/inject/scopes.py CHANGED
@@ -27,7 +27,7 @@ SCOPE_ALIASES: dict[str, Scope] = {}
27
27
 
28
28
 
29
29
  @dc.dataclass(frozen=True)
30
- @dc.extra_params(cache_hash=True)
30
+ @dc.extra_class_params(cache_hash=True)
31
31
  class ScopeBinding(Element, lang.Final):
32
32
  scope: Scope = dc.xfield(coerce=check.of_isinstance(Scope))
33
33
 
@@ -60,7 +60,7 @@ SCOPE_ALIASES['thread'] = ThreadScope()
60
60
 
61
61
 
62
62
  @dc.dataclass(frozen=True)
63
- @dc.extra_params(cache_hash=True)
63
+ @dc.extra_class_params(cache_hash=True)
64
64
  class SeededScope(Scope, lang.Final):
65
65
  tag: ta.Any = dc.xfield(coerce=check.not_none)
66
66
 
@@ -71,7 +71,7 @@ class SeededScope(Scope, lang.Final):
71
71
 
72
72
 
73
73
  @dc.dataclass(frozen=True)
74
- @dc.extra_params(cache_hash=True)
74
+ @dc.extra_class_params(cache_hash=True)
75
75
  class ScopeSeededProvider(Provider):
76
76
  ss: SeededScope = dc.xfield(coerce=check.of_isinstance(SeededScope))
77
77
  key: Key = dc.xfield(coerce=check.of_isinstance(Key))
omlish/lang/__init__.py CHANGED
@@ -122,8 +122,8 @@ from .descriptors import ( # noqa
122
122
  update_wrapper,
123
123
  )
124
124
 
125
- from .exceptions import ( # noqa
126
- Unreachable,
125
+ from .enums import ( # noqa
126
+ enum_name_repr,
127
127
  )
128
128
 
129
129
  from .functions import ( # noqa
@@ -166,6 +166,7 @@ from .generators import ( # noqa
166
166
  )
167
167
 
168
168
  from .imports import ( # noqa
169
+ LazyGlobals,
169
170
  can_import,
170
171
  get_real_module_name,
171
172
  import_all,
@@ -204,7 +205,6 @@ from .objects import ( # noqa
204
205
  SimpleProxy,
205
206
  anon_object,
206
207
  arg_repr,
207
- attr_repr,
208
208
  can_weakref,
209
209
  deep_subclasses,
210
210
  dir_dict,
@@ -303,6 +303,11 @@ from ..lite.imports import ( # noqa
303
303
  import_module_attr,
304
304
  )
305
305
 
306
+ from ..lite.reprs import ( # noqa
307
+ AttrRepr,
308
+ attr_repr,
309
+ )
310
+
306
311
  from ..lite.timeouts import ( # noqa
307
312
  DeadlineTimeout,
308
313
  InfiniteTimeout,
@@ -1,6 +1,10 @@
1
1
  """
2
2
  TODO:
3
+ - !!! lighter weight bound methods
4
+ - keymaker overhead less important than not rebuilding a whole dc every __get__ on a new instance
3
5
  - !! specialize nullary, explicit kwarg
6
+ - !! use c-backed functools.cache if possible
7
+ - also just riic
4
8
  - !! reconcile A().f() with A.f(A())
5
9
  - unbound descriptor *should* still hit instance cache
6
10
  - integrate / expose with collections.cache
@@ -10,6 +14,7 @@ TODO:
10
14
  - 'staticmethod' or effective equiv - which must resolve to the shared instance
11
15
  - and must be transient?
12
16
  - use __transient_dict__ to support common state nuking
17
+ - use __set_name__ ?
13
18
  """
14
19
  import dataclasses as dc
15
20
  import functools
@@ -353,10 +358,14 @@ class _DescriptorCachedFunction(_CachedFunction[T]):
353
358
  def cached_function(fn=None, **kwargs): # noqa
354
359
  if fn is None:
355
360
  return functools.partial(cached_function, **kwargs)
361
+
356
362
  opts = _CachedFunction.Opts(**kwargs)
363
+
357
364
  if isinstance(fn, staticmethod):
358
365
  return _FreeCachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
366
+
359
367
  scope = classmethod if isinstance(fn, classmethod) else None
368
+
360
369
  return _DescriptorCachedFunction(fn, scope, opts=opts)
361
370
 
362
371
 
@@ -112,7 +112,9 @@ class _TransientCachedProperty(_CachedProperty):
112
112
  def cached_property(fn=None, *, transient=False, **kwargs): # noqa
113
113
  if fn is None:
114
114
  return functools.partial(cached_property, transient=transient, **kwargs)
115
- if transient:
115
+
116
+ elif transient:
116
117
  return _TransientCachedProperty(fn, **kwargs)
118
+
117
119
  else:
118
120
  return _DictCachedProperty(fn, **kwargs)
omlish/lang/comparison.py CHANGED
@@ -8,6 +8,9 @@ def cmp(l: ta.Any, r: ta.Any) -> int:
8
8
  return int(l > r) - int(l < r)
9
9
 
10
10
 
11
+ ##
12
+
13
+
11
14
  class InfinityType:
12
15
  def __repr__(self) -> str:
13
16
  return 'Infinity'
omlish/lang/enums.py ADDED
@@ -0,0 +1,8 @@
1
+ import typing as ta
2
+
3
+
4
+ ##
5
+
6
+
7
+ def enum_name_repr(e: ta.Any) -> str:
8
+ return f'{e.__class__.__name__}.{e.name}'
omlish/lang/imports.py CHANGED
@@ -3,6 +3,7 @@ TODO:
3
3
  - proxy_init 'as' alias support - attrs of (src, dst)
4
4
  """
5
5
  import contextlib
6
+ import functools
6
7
  import importlib.util
7
8
  import sys
8
9
  import types
@@ -254,6 +255,64 @@ def _trigger_conditional_imports(package: str) -> None:
254
255
  ##
255
256
 
256
257
 
258
+ class LazyGlobals:
259
+ def __init__(
260
+ self,
261
+ *,
262
+ globals: ta.MutableMapping[str, ta.Any] | None = None, # noqa
263
+ update_globals: bool = False,
264
+ ) -> None:
265
+ super().__init__()
266
+
267
+ self._globals = globals
268
+ self._update_globals = update_globals
269
+
270
+ self._attr_fns: dict[str, ta.Callable[[], ta.Any]] = {}
271
+
272
+ @classmethod
273
+ def install(cls, globals: ta.MutableMapping[str, ta.Any]) -> 'LazyGlobals': # noqa
274
+ try:
275
+ xga = globals['__getattr__']
276
+ except KeyError:
277
+ pass
278
+ else:
279
+ if not isinstance(xga, cls):
280
+ raise RuntimeError(f'Module already has __getattr__ hook: {xga}') # noqa
281
+ return xga
282
+
283
+ lm = cls(
284
+ globals=globals,
285
+ update_globals=True,
286
+ )
287
+
288
+ globals['__getattr__'] = lm
289
+
290
+ return lm
291
+
292
+ def set_fn(self, attr: str, fn: ta.Callable[[], ta.Any]) -> 'LazyGlobals':
293
+ self._attr_fns[attr] = fn
294
+ return self
295
+
296
+ def get(self, attr: str) -> ta.Any:
297
+ try:
298
+ fn = self._attr_fns[attr]
299
+ except KeyError:
300
+ raise AttributeError(attr) from None
301
+
302
+ val = fn()
303
+
304
+ if self._update_globals and self._globals is not None:
305
+ self._globals[attr] = val
306
+
307
+ return val
308
+
309
+ def __call__(self, attr: str) -> ta.Any:
310
+ return self.get(attr)
311
+
312
+
313
+ ##
314
+
315
+
257
316
  class NamePackage(ta.NamedTuple):
258
317
  name: str
259
318
  package: str
@@ -266,16 +325,13 @@ class _ProxyInit:
266
325
 
267
326
  def __init__(
268
327
  self,
328
+ lazy_globals: LazyGlobals,
269
329
  name_package: NamePackage,
270
- *,
271
- globals: ta.MutableMapping[str, ta.Any] | None = None, # noqa
272
- update_globals: bool = False,
273
330
  ) -> None:
274
331
  super().__init__()
275
332
 
333
+ self._lazy_globals = lazy_globals
276
334
  self._name_package = name_package
277
- self._globals = globals
278
- self._update_globals = update_globals
279
335
 
280
336
  self._imps_by_attr: dict[str, _ProxyInit._Import] = {}
281
337
  self._mods_by_pkgs: dict[str, ta.Any] = {}
@@ -287,13 +343,17 @@ class _ProxyInit:
287
343
  def add(self, package: str, attrs: ta.Iterable[str | tuple[str, str]]) -> None:
288
344
  if isinstance(attrs, str):
289
345
  raise TypeError(attrs)
346
+
290
347
  for attr in attrs:
291
348
  if isinstance(attr, tuple):
292
349
  imp_attr, attr = attr
293
350
  else:
294
351
  imp_attr = attr
352
+
295
353
  self._imps_by_attr[attr] = self._Import(package, imp_attr)
296
354
 
355
+ self._lazy_globals.set_fn(attr, functools.partial(self.get, attr))
356
+
297
357
  def get(self, attr: str) -> ta.Any:
298
358
  try:
299
359
  imp = self._imps_by_attr[attr]
@@ -307,9 +367,6 @@ class _ProxyInit:
307
367
 
308
368
  val = getattr(mod, imp.attr)
309
369
 
310
- if self._update_globals and self._globals is not None:
311
- self._globals[attr] = val
312
-
313
370
  return val
314
371
 
315
372
 
@@ -329,13 +386,14 @@ def proxy_init(
329
386
  pi: _ProxyInit
330
387
  try:
331
388
  pi = globals['__proxy_init__']
389
+
332
390
  except KeyError:
333
391
  pi = _ProxyInit(
392
+ LazyGlobals.install(globals),
334
393
  init_name_package,
335
- globals=globals,
336
394
  )
337
395
  globals['__proxy_init__'] = pi
338
- globals['__getattr__'] = pi.get
396
+
339
397
  else:
340
398
  if pi.name_package != init_name_package:
341
399
  raise Exception(f'Wrong init name: {pi.name_package=} != {init_name_package=}')