omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev6__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.

Potentially problematic release.


This version of omlish might be problematic. Click here for more details.

Files changed (143) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/__init__.py +1 -1
  3. omlish/asyncs/__init__.py +10 -4
  4. omlish/asyncs/anyio.py +142 -12
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/flavors.py +27 -1
  9. omlish/asyncs/trio_asyncio.py +28 -18
  10. omlish/c3.py +1 -1
  11. omlish/cached.py +1 -2
  12. omlish/collections/__init__.py +5 -1
  13. omlish/collections/cache/impl.py +1 -1
  14. omlish/collections/identity.py +7 -0
  15. omlish/collections/indexed.py +1 -1
  16. omlish/collections/utils.py +38 -6
  17. omlish/configs/__init__.py +5 -0
  18. omlish/configs/classes.py +53 -0
  19. omlish/configs/strings.py +94 -0
  20. omlish/dataclasses/__init__.py +9 -0
  21. omlish/dataclasses/impl/api.py +1 -1
  22. omlish/dataclasses/impl/as_.py +1 -1
  23. omlish/dataclasses/impl/copy.py +30 -0
  24. omlish/dataclasses/impl/exceptions.py +6 -0
  25. omlish/dataclasses/impl/fields.py +25 -25
  26. omlish/dataclasses/impl/init.py +5 -3
  27. omlish/dataclasses/impl/main.py +3 -0
  28. omlish/dataclasses/impl/metaclass.py +6 -1
  29. omlish/dataclasses/impl/order.py +1 -1
  30. omlish/dataclasses/impl/reflect.py +15 -2
  31. omlish/dataclasses/utils.py +44 -0
  32. omlish/defs.py +1 -1
  33. omlish/diag/__init__.py +4 -0
  34. omlish/diag/procfs.py +31 -3
  35. omlish/diag/procstats.py +32 -0
  36. omlish/{testing → diag}/pydevd.py +35 -0
  37. omlish/diag/replserver/console.py +3 -3
  38. omlish/diag/replserver/server.py +6 -5
  39. omlish/diag/threads.py +86 -0
  40. omlish/dispatch/_dispatch2.py +65 -0
  41. omlish/dispatch/_dispatch3.py +104 -0
  42. omlish/docker.py +20 -1
  43. omlish/fnpairs.py +37 -18
  44. omlish/graphs/dags.py +113 -0
  45. omlish/graphs/domination.py +268 -0
  46. omlish/graphs/trees.py +2 -2
  47. omlish/http/__init__.py +25 -0
  48. omlish/http/asgi.py +132 -0
  49. omlish/http/collections.py +15 -0
  50. omlish/http/consts.py +47 -5
  51. omlish/http/cookies.py +194 -0
  52. omlish/http/dates.py +70 -0
  53. omlish/http/encodings.py +6 -0
  54. omlish/http/json.py +273 -0
  55. omlish/http/sessions.py +204 -0
  56. omlish/inject/__init__.py +51 -17
  57. omlish/inject/binder.py +185 -5
  58. omlish/inject/bindings.py +3 -36
  59. omlish/inject/eagers.py +2 -8
  60. omlish/inject/elements.py +30 -9
  61. omlish/inject/exceptions.py +3 -3
  62. omlish/inject/impl/elements.py +65 -31
  63. omlish/inject/impl/injector.py +20 -2
  64. omlish/inject/impl/inspect.py +33 -5
  65. omlish/inject/impl/multis.py +74 -0
  66. omlish/inject/impl/origins.py +75 -0
  67. omlish/inject/impl/{private.py → privates.py} +2 -2
  68. omlish/inject/impl/providers.py +19 -39
  69. omlish/inject/{proxy.py → impl/proxy.py} +2 -2
  70. omlish/inject/impl/scopes.py +7 -2
  71. omlish/inject/injector.py +9 -4
  72. omlish/inject/inspect.py +18 -0
  73. omlish/inject/keys.py +11 -23
  74. omlish/inject/listeners.py +26 -0
  75. omlish/inject/managed.py +76 -10
  76. omlish/inject/multis.py +120 -0
  77. omlish/inject/origins.py +27 -0
  78. omlish/inject/overrides.py +5 -4
  79. omlish/inject/{private.py → privates.py} +6 -10
  80. omlish/inject/providers.py +12 -85
  81. omlish/inject/scopes.py +20 -9
  82. omlish/inject/types.py +2 -8
  83. omlish/iterators.py +13 -0
  84. omlish/lang/__init__.py +12 -2
  85. omlish/lang/cached.py +2 -2
  86. omlish/lang/classes/restrict.py +3 -2
  87. omlish/lang/classes/simple.py +18 -8
  88. omlish/lang/classes/virtual.py +2 -2
  89. omlish/lang/contextmanagers.py +75 -2
  90. omlish/lang/datetimes.py +6 -5
  91. omlish/lang/descriptors.py +131 -0
  92. omlish/lang/functions.py +18 -28
  93. omlish/lang/imports.py +11 -2
  94. omlish/lang/iterables.py +20 -1
  95. omlish/lang/typing.py +6 -0
  96. omlish/lifecycles/__init__.py +34 -0
  97. omlish/lifecycles/abstract.py +43 -0
  98. omlish/lifecycles/base.py +51 -0
  99. omlish/lifecycles/contextmanagers.py +74 -0
  100. omlish/lifecycles/controller.py +116 -0
  101. omlish/lifecycles/manager.py +161 -0
  102. omlish/lifecycles/states.py +43 -0
  103. omlish/lifecycles/transitions.py +64 -0
  104. omlish/logs/formatters.py +1 -1
  105. omlish/logs/utils.py +1 -1
  106. omlish/marshal/__init__.py +4 -0
  107. omlish/marshal/datetimes.py +1 -1
  108. omlish/marshal/naming.py +4 -0
  109. omlish/marshal/objects.py +1 -0
  110. omlish/marshal/polymorphism.py +4 -4
  111. omlish/reflect.py +139 -18
  112. omlish/secrets/__init__.py +7 -0
  113. omlish/secrets/marshal.py +41 -0
  114. omlish/secrets/passwords.py +120 -0
  115. omlish/secrets/secrets.py +47 -0
  116. omlish/serde/__init__.py +0 -0
  117. omlish/serde/dotenv.py +574 -0
  118. omlish/{json.py → serde/json.py} +4 -2
  119. omlish/serde/props.py +604 -0
  120. omlish/serde/yaml.py +223 -0
  121. omlish/sql/dbs.py +1 -1
  122. omlish/sql/duckdb.py +136 -0
  123. omlish/sql/sqlean.py +17 -0
  124. omlish/sync.py +70 -0
  125. omlish/term.py +7 -2
  126. omlish/testing/pytest/__init__.py +8 -2
  127. omlish/testing/pytest/helpers.py +0 -24
  128. omlish/testing/pytest/inject/harness.py +4 -4
  129. omlish/testing/pytest/marks.py +45 -0
  130. omlish/testing/pytest/plugins/__init__.py +3 -0
  131. omlish/testing/pytest/plugins/asyncs.py +136 -0
  132. omlish/testing/pytest/plugins/managermarks.py +60 -0
  133. omlish/testing/pytest/plugins/pydevd.py +1 -1
  134. omlish/testing/testing.py +10 -0
  135. omlish/text/delimit.py +4 -0
  136. omlish/text/glyphsplit.py +92 -0
  137. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
  138. omlish-0.0.0.dev6.dist-info/RECORD +240 -0
  139. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
  140. omlish/configs/props.py +0 -64
  141. omlish-0.0.0.dev4.dist-info/RECORD +0 -195
  142. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
  143. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
@@ -3,12 +3,16 @@ import typing as ta
3
3
 
4
4
  from .. import lang
5
5
 
6
+
6
7
  if ta.TYPE_CHECKING:
7
8
  import asyncio
9
+
8
10
  import sniffio
9
11
  import trio_asyncio
12
+
10
13
  else:
11
14
  asyncio = lang.proxy_import('asyncio')
15
+
12
16
  sniffio = lang.proxy_import('sniffio')
13
17
  trio_asyncio = lang.proxy_import('trio_asyncio')
14
18
 
@@ -18,24 +22,30 @@ def check_trio_asyncio() -> None:
18
22
  raise RuntimeError('trio_asyncio loop not running')
19
23
 
20
24
 
21
- def with_trio_asyncio_loop(fn, *, wait=False):
22
- @functools.wraps(fn)
23
- async def inner(*args, **kwargs):
24
- if trio_asyncio.current_loop.get() is not None:
25
- await fn(*args, **kwargs)
26
- return
27
-
28
- if sniffio.current_async_library() != 'trio':
29
- raise RuntimeError('trio loop not running')
25
+ def with_trio_asyncio_loop(*, wait=False, strict=False):
26
+ def outer(fn):
27
+ @functools.wraps(fn)
28
+ async def inner(*args, **kwargs):
29
+ if trio_asyncio.current_loop.get() is not None:
30
+ await fn(*args, **kwargs)
31
+ return
30
32
 
31
- loop: asyncio.BaseEventLoop
32
- async with trio_asyncio.open_loop() as loop:
33
- try:
33
+ if sniffio.current_async_library() != 'trio':
34
+ if strict:
35
+ raise RuntimeError('trio loop not running')
34
36
  await fn(*args, **kwargs)
35
- finally:
36
- if wait:
37
- # FIXME: lol
38
- while asyncio.all_tasks(loop):
39
- await asyncio.sleep(.2)
37
+ return
38
+
39
+ loop: asyncio.BaseEventLoop
40
+ async with trio_asyncio.open_loop() as loop:
41
+ try:
42
+ await fn(*args, **kwargs)
43
+ finally:
44
+ if wait:
45
+ # FIXME: lol
46
+ while asyncio.all_tasks(loop):
47
+ await asyncio.sleep(.1)
48
+
49
+ return inner
40
50
 
41
- return inner
51
+ return outer
omlish/c3.py CHANGED
@@ -144,7 +144,7 @@ def compose_mro(
144
144
 
145
145
  # Remove entries which are strict bases of other entries (they will end up in the MRO anyway.
146
146
  def is_strict_base(typ):
147
- for other in types:
147
+ for other in types: # noqa
148
148
  if typ != other and typ in (get_mro(other) or []):
149
149
  return True
150
150
  return False
omlish/cached.py CHANGED
@@ -1,6 +1,5 @@
1
- from .lang.cached import cached_function
2
1
  from .lang.cached import _CachedProperty # noqa
3
-
2
+ from .lang.cached import cached_function
4
3
 
5
4
  function = cached_function
6
5
 
@@ -37,6 +37,7 @@ from .identity import ( # noqa
37
37
  IdentityKeyDict,
38
38
  IdentitySet,
39
39
  IdentityWrapper,
40
+ IdentityWeakSet,
40
41
  )
41
42
 
42
43
  from .indexed import ( # noqa
@@ -90,9 +91,12 @@ from .utils import ( # noqa
90
91
  all_not_equal,
91
92
  indexes,
92
93
  key_cmp,
94
+ multi_map,
95
+ multi_map_by,
93
96
  mut_toposort,
94
97
  partition,
95
98
  toposort,
96
99
  unique,
97
- unique_dict,
100
+ unique_map,
101
+ unique_map_by,
98
102
  )
@@ -55,7 +55,7 @@ class CacheImpl(Cache[K, V]):
55
55
  if not ta.TYPE_CHECKING:
56
56
  from ..._ext.cy.collections.cache import CacheLink as Link
57
57
  else:
58
- raise ImportError
58
+ raise ImportError # noqa
59
59
 
60
60
  except ImportError:
61
61
  class Link:
@@ -1,6 +1,7 @@
1
1
  import contextlib
2
2
  import operator as op
3
3
  import typing as ta
4
+ import weakref
4
5
 
5
6
  from .. import lang
6
7
  from .mappings import yield_dict_init
@@ -103,3 +104,9 @@ class IdentitySet(ta.MutableSet[T]):
103
104
 
104
105
  def __iter__(self) -> ta.Iterator[T]:
105
106
  return iter(self._dict.values())
107
+
108
+
109
+ class IdentityWeakSet(weakref.WeakSet):
110
+ def __init__(self, init=None):
111
+ super().__init__()
112
+ self.data = IdentitySet(init) # type: ignore
@@ -1,7 +1,7 @@
1
1
  import typing as ta
2
2
 
3
- from .identity import IdentitySet
4
3
  from .identity import IdentityKeyDict
4
+ from .identity import IdentitySet
5
5
 
6
6
 
7
7
  T = ta.TypeVar('T')
@@ -12,6 +12,9 @@ K = ta.TypeVar('K')
12
12
  V = ta.TypeVar('V')
13
13
 
14
14
 
15
+ ##
16
+
17
+
15
18
  def mut_toposort(data: dict[T, set[T]]) -> ta.Iterator[set[T]]:
16
19
  for k, v in data.items():
17
20
  v.discard(k)
@@ -31,6 +34,9 @@ def toposort(data: ta.Mapping[T, ta.AbstractSet[T]]) -> ta.Iterator[set[T]]:
31
34
  return mut_toposort({k: set(v) for k, v in data.items()})
32
35
 
33
36
 
37
+ ##
38
+
39
+
34
40
  def partition(items: ta.Iterable[T], pred: ta.Callable[[T], bool]) -> tuple[list[T], list[T]]:
35
41
  t: list[T] = []
36
42
  f: list[T] = []
@@ -54,13 +60,36 @@ def unique(it: ta.Iterable[T], *, identity: bool = False) -> list[T]:
54
60
  return ret
55
61
 
56
62
 
57
- def unique_dict(items: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, V]:
58
- dct: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
59
- for k, v in items:
60
- if k in dct:
63
+ def unique_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, V]:
64
+ d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
65
+ for k, v in kvs:
66
+ if k in d:
61
67
  raise KeyError(k)
62
- dct[k] = v
63
- return dct
68
+ d[k] = v
69
+ return d
70
+
71
+
72
+ def unique_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, V]:
73
+ return unique_map(((fn(v), v) for v in vs), identity=identity)
74
+
75
+
76
+ def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
77
+ d: ta.MutableMapping[K, list[V]] = IdentityKeyDict() if identity else {}
78
+ l: list[V]
79
+ for k, v in kvs:
80
+ try:
81
+ l = d[k]
82
+ except KeyError:
83
+ l = d[k] = []
84
+ l.append(v)
85
+ return d
86
+
87
+
88
+ def multi_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]: # noqa
89
+ return multi_map(((fn(v), v) for v in vs), identity=identity)
90
+
91
+
92
+ ##
64
93
 
65
94
 
66
95
  def all_equal(it: ta.Iterable[T]) -> bool:
@@ -81,6 +110,9 @@ def all_not_equal(it: ta.Iterable[T]) -> bool:
81
110
  return True
82
111
 
83
112
 
113
+ ##
114
+
115
+
84
116
  def key_cmp(fn: ta.Callable[[K, K], int]) -> ta.Callable[[tuple[K, V], tuple[K, V]], int]:
85
117
  return lambda t0, t1: fn(t0[0], t1[0])
86
118
 
@@ -0,0 +1,5 @@
1
+ from .classes import ( # noqa
2
+ Config,
3
+ Configurable,
4
+ get_impl,
5
+ )
@@ -0,0 +1,53 @@
1
+ import typing as ta
2
+ import weakref
3
+
4
+ from .. import check
5
+ from .. import dataclasses as dc
6
+ from .. import lang
7
+
8
+
9
+ class Config(
10
+ dc.Data,
11
+ lang.Abstract,
12
+ frozen=True,
13
+ reorder=True,
14
+ confer=frozenset([
15
+ 'frozen',
16
+ 'reorder',
17
+ 'confer',
18
+ ]),
19
+ ):
20
+ pass
21
+
22
+
23
+ ConfigT = ta.TypeVar('ConfigT', bound='Config')
24
+
25
+ _CONFIG_CLS_MAP: ta.MutableMapping[type[Config], type['Configurable']] = weakref.WeakValueDictionary()
26
+
27
+
28
+ class Configurable(ta.Generic[ConfigT], lang.Abstract):
29
+
30
+ # FIXME: https://github.com/python/mypy/issues/5144
31
+ Config: ta.ClassVar[type[ConfigT]] # type: ignore # noqa
32
+
33
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
34
+ super().__init_subclass__(**kwargs)
35
+
36
+ cfg_cls = check.issubclass(cls.__dict__['Config'], Config)
37
+ check.not_in(cfg_cls, _CONFIG_CLS_MAP)
38
+ _CONFIG_CLS_MAP[cfg_cls] = cls
39
+
40
+ def __init__(self, config: ConfigT) -> None:
41
+ super().__init__()
42
+
43
+ self._config: ConfigT = check.isinstance(config, self.Config)
44
+
45
+
46
+ def get_impl(cfg: type[Config] | Config) -> type[Configurable]:
47
+ if isinstance(cfg, type):
48
+ cfg_cls = check.issubclass(cfg, Config) # noqa
49
+ elif isinstance(cfg, Config):
50
+ cfg_cls = type(cfg)
51
+ else:
52
+ raise TypeError(cfg)
53
+ return _CONFIG_CLS_MAP[cfg_cls]
@@ -0,0 +1,94 @@
1
+ """
2
+ TODO:
3
+ - reflecty generalized rewriter, obviously..
4
+ """
5
+ import collections.abc
6
+ import typing as ta
7
+
8
+ from .. import dataclasses as dc
9
+ from .. import lang
10
+ from ..text import glyphsplit
11
+
12
+
13
+ T = ta.TypeVar('T')
14
+
15
+
16
+ class InterpolateStringsMetadata(lang.Marker):
17
+ pass
18
+
19
+
20
+ @dc.field_modifier
21
+ def secret_or_key_field(f: dc.Field) -> dc.Field:
22
+ return dc.update_field_metadata(f, {
23
+ InterpolateStringsMetadata: True,
24
+ })
25
+
26
+
27
+ @dc.field_modifier
28
+ def interpolate_field(f: dc.Field) -> dc.Field:
29
+ return dc.update_field_metadata(f, {InterpolateStringsMetadata: True})
30
+
31
+
32
+ class StringRewriter:
33
+ def __init__(self, fn: ta.Callable[[str], str]) -> None:
34
+ super().__init__()
35
+ self._fn = fn
36
+
37
+ def __call__(self, v: T, *, _soft: bool = False) -> T:
38
+ if v is None:
39
+ return None # type: ignore
40
+
41
+ if dc.is_dataclass(v):
42
+ kw = {}
43
+ for f in dc.fields(v):
44
+ fv = getattr(v, f.name)
45
+ nfv = self(fv, _soft=not f.metadata.get(InterpolateStringsMetadata))
46
+ if fv is not nfv:
47
+ kw[f.name] = nfv
48
+ if not kw:
49
+ return v # type: ignore
50
+ return dc.replace(v, **kw)
51
+
52
+ if isinstance(v, str):
53
+ if not _soft:
54
+ v = self._fn(v) # type: ignore
55
+ return v # type: ignore
56
+
57
+ if isinstance(v, lang.BUILTIN_SCALAR_ITERABLE_TYPES):
58
+ return v # type: ignore
59
+
60
+ if isinstance(v, collections.abc.Mapping):
61
+ nm = []
62
+ b = False
63
+ for mk, mv in v.items():
64
+ nk, nv = self(mk, _soft=_soft), self(mv, _soft=_soft)
65
+ nm.append((nk, nv))
66
+ b |= nk is not mk or nv is not mv
67
+ if not b:
68
+ return v # type: ignore
69
+ return v.__class__(nm) # type: ignore
70
+
71
+ if isinstance(v, (collections.abc.Sequence, collections.abc.Set)):
72
+ nl = []
73
+ b = False
74
+ for le in v:
75
+ ne = self(le, _soft=_soft)
76
+ nl.append(ne)
77
+ b |= ne is not le
78
+ if not b:
79
+ return v # type: ignore
80
+ return v.__class__(nl) # type: ignore
81
+
82
+ return v
83
+
84
+
85
+ def interpolate_strings(v: T, rpl: ta.Mapping[str, str]) -> T:
86
+ def fn(v):
87
+ if not v:
88
+ return v
89
+ sps = glyphsplit.split_braces(v)
90
+ if len(sps) == 1 and isinstance(sps[0], str):
91
+ return sps[0]
92
+ return ''.join(rpl[p.s] if isinstance(p, glyphsplit.GlyphMatch) else p for p in sps)
93
+
94
+ return StringRewriter(fn)(v)
@@ -55,6 +55,7 @@ globals()['make_dataclass'] = xmake_dataclass
55
55
 
56
56
  from .impl.exceptions import ( # noqa
57
57
  CheckError,
58
+ FieldCheckError,
58
59
  )
59
60
 
60
61
  from .impl.metaclass import ( # noqa
@@ -81,3 +82,11 @@ from .impl.reflect import ( # noqa
81
82
  ClassInfo,
82
83
  reflect,
83
84
  )
85
+
86
+ from .utils import ( # noqa
87
+ chain_metadata,
88
+ field_modifier,
89
+ maybe_post_init,
90
+ opt_repr,
91
+ update_field_metadata,
92
+ )
@@ -34,7 +34,7 @@ def field( # noqa
34
34
  check: ta.Callable[[ta.Any], bool] | None = None,
35
35
  check_type: bool | None = None,
36
36
  override: bool = False,
37
- repr_fn: ta.Callable[[ta.Any], str | None] | None = None
37
+ repr_fn: ta.Callable[[ta.Any], str | None] | None = None,
38
38
  ): # -> dc.Field
39
39
  if default is not MISSING and default_factory is not MISSING:
40
40
  raise ValueError('cannot specify both default and default_factory')
@@ -1,8 +1,8 @@
1
1
  import copy
2
2
  import dataclasses as dc
3
3
 
4
- from .internals import is_dataclass_instance
5
4
  from .internals import ATOMIC_TYPES
5
+ from .internals import is_dataclass_instance
6
6
 
7
7
 
8
8
  def asdict(obj, *, dict_factory=dict): # noqa
@@ -0,0 +1,30 @@
1
+ """
2
+ TODO:
3
+ - __deepcopy__
4
+ """
5
+ import dataclasses as dc
6
+
7
+ from .fields import field_type
8
+ from .internals import FIELDS_ATTR
9
+ from .internals import FieldType
10
+ from .processing import Processor
11
+ from .utils import set_new_attribute
12
+
13
+
14
+ MISSING = dc.MISSING
15
+
16
+
17
+ def _copy(obj):
18
+ kw = {}
19
+
20
+ for f in getattr(obj, FIELDS_ATTR).values():
21
+ if field_type(f) is FieldType.CLASS:
22
+ continue
23
+ kw[f.name] = getattr(obj, f.name)
24
+
25
+ return obj.__class__(**kw)
26
+
27
+
28
+ class CopyProcessor(Processor):
29
+ def _process(self) -> None:
30
+ set_new_attribute(self._cls, '__copy__', _copy)
@@ -1,2 +1,8 @@
1
1
  class CheckError(Exception):
2
2
  pass
3
+
4
+
5
+ class FieldCheckError(CheckError):
6
+ def __init__(self, field: str) -> None:
7
+ super().__init__(field)
8
+ self.field = field
@@ -9,6 +9,7 @@ from .internals import is_classvar
9
9
  from .internals import is_initvar
10
10
  from .params import get_field_extras
11
11
 
12
+
12
13
  if ta.TYPE_CHECKING:
13
14
  from . import api
14
15
  else:
@@ -97,51 +98,50 @@ def field_init(
97
98
 
98
99
  lines = []
99
100
 
100
- if fx.coerce is not None:
101
- cn = f'__dataclass_coerce__{f.name}__'
102
- locals[cn] = fx.coerce
103
- lines.append(f'{f.name} = {cn}({f.name})')
104
-
105
- if fx.check is not None:
106
- cn = f'__dataclass_check__{f.name}__'
107
- locals[cn] = fx.check
108
- lines.append(f'if not {cn}({f.name}): raise __dataclass_CheckException__')
109
-
110
- if fx.check_type:
111
- cn = f'__dataclass_check_type__{f.name}__'
112
- locals[cn] = f.type
113
- lines.append(
114
- f'if not __dataclass_builtins_isinstance__({f.name}, {cn}): '
115
- f'raise __dataclass_builtins_TypeError__({f.name}, {cn})',
116
- )
117
-
118
101
  value: str | None = None
119
102
  if f.default_factory is not MISSING:
120
103
  if f.init:
121
104
  locals[default_name] = f.default_factory
122
- value = (
123
- f'{default_name}() '
124
- f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ '
125
- f'else {f.name}'
126
- )
105
+ lines.append(f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__: {f.name} = {default_name}()')
106
+ value = f.name
127
107
  else:
128
108
  locals[default_name] = f.default_factory
129
- value = f'{default_name}()'
109
+ lines.append(f'{f.name} = {default_name}()')
110
+ value = f.name
130
111
 
131
112
  elif f.init:
132
113
  if f.default is MISSING:
133
114
  value = f.name
134
115
  elif f.default is not MISSING:
135
- locals[default_name] = f.default
116
+ locals[default_name] = f.default # Not referenced her, just useful / consistent to have in function scope
136
117
  value = f.name
137
118
 
138
119
  elif slots and f.default is not MISSING:
139
120
  locals[default_name] = f.default
121
+ lines.append(f'{f.name} = {default_name}')
140
122
  value = default_name
141
123
 
142
124
  else:
143
125
  pass
144
126
 
127
+ if fx.coerce is not None:
128
+ cn = f'__dataclass_coerce__{f.name}__'
129
+ locals[cn] = fx.coerce
130
+ lines.append(f'{value} = {cn}({value})')
131
+
132
+ if fx.check is not None:
133
+ cn = f'__dataclass_check__{f.name}__'
134
+ locals[cn] = fx.check
135
+ lines.append(f'if not {cn}({value}): raise __dataclass_FieldCheckError__({f.name})')
136
+
137
+ if fx.check_type:
138
+ cn = f'__dataclass_check_type__{f.name}__'
139
+ locals[cn] = f.type
140
+ lines.append(
141
+ f'if not __dataclass_builtins_isinstance__({value}, {cn}): '
142
+ f'raise __dataclass_builtins_TypeError__({value}, {cn})',
143
+ )
144
+
145
145
  if value is not None and field_type(f) is not FieldType.INIT:
146
146
  lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
147
147
 
@@ -4,12 +4,13 @@ import typing as ta
4
4
 
5
5
  from ... import lang
6
6
  from .exceptions import CheckError
7
+ from .exceptions import FieldCheckError
7
8
  from .fields import field_init
8
9
  from .fields import field_type
9
10
  from .fields import has_default
10
- from .internals import FieldType
11
11
  from .internals import HAS_DEFAULT_FACTORY
12
12
  from .internals import POST_INIT_NAME
13
+ from .internals import FieldType
13
14
  from .metadata import Check
14
15
  from .metadata import Init
15
16
  from .processing import Processor
@@ -99,7 +100,8 @@ class InitBuilder:
99
100
  '__dataclass_builtins_object__': object,
100
101
  '__dataclass_builtins_isinstance__': isinstance,
101
102
  '__dataclass_builtins_TypeError__': TypeError,
102
- '__dataclass_CheckException__': CheckError,
103
+ '__dataclass_CheckError__': CheckError,
104
+ '__dataclass_FieldCheckError__': FieldCheckError,
103
105
  })
104
106
 
105
107
  body_lines: list[str] = []
@@ -126,7 +128,7 @@ class InitBuilder:
126
128
  locals[cn] = fn
127
129
  csig = inspect.signature(fn)
128
130
  cas = ', '.join(p.name for p in csig.parameters.values())
129
- body_lines.append(f'if not {cn}({cas}): raise __dataclass_CheckException__')
131
+ body_lines.append(f'if not {cn}({cas}): raise __dataclass_CheckError__')
130
132
 
131
133
  for i, fn in enumerate(self._info.merged_metadata.get(Init, [])):
132
134
  cn = f'__dataclass_init_{i}__'
@@ -4,6 +4,7 @@ import typing as ta
4
4
 
5
5
  from ... import check
6
6
  from ... import lang
7
+ from .copy import CopyProcessor
7
8
  from .fields import preprocess_field
8
9
  from .frozen import FrozenProcessor
9
10
  from .hashing import HashProcessor
@@ -24,6 +25,7 @@ from .simple import MatchArgsProcessor
24
25
  from .simple import OverridesProcessor
25
26
  from .slots import add_slots
26
27
 
28
+
27
29
  if ta.TYPE_CHECKING:
28
30
  from . import metaclass
29
31
  else:
@@ -136,6 +138,7 @@ class MainProcessor:
136
138
  DocProcessor,
137
139
  MatchArgsProcessor,
138
140
  ReplaceProcessor,
141
+ CopyProcessor,
139
142
  ]:
140
143
  pcls(self._info).process()
141
144
 
@@ -39,7 +39,12 @@ def confer_kwargs(
39
39
  for ck in bmp.confer:
40
40
  if ck in kwargs:
41
41
  continue
42
- if ck in ('frozen', 'generic_init', 'kw_only'):
42
+ if ck in (
43
+ 'frozen',
44
+ 'generic_init',
45
+ 'kw_only',
46
+ 'reorder',
47
+ ):
43
48
  confer_kwarg(out, ck, get_params(base).frozen)
44
49
  elif ck == 'confer':
45
50
  confer_kwarg(out, 'confer', bmp.confer)
@@ -1,7 +1,7 @@
1
1
  import typing as ta
2
2
 
3
- from .utils import Namespace
4
3
  from .processing import Processor
4
+ from .utils import Namespace
5
5
  from .utils import create_fn
6
6
  from .utils import set_new_attribute
7
7
  from .utils import tuple_str
@@ -28,9 +28,22 @@ from .params import get_params_extras
28
28
  from .utils import Namespace
29
29
 
30
30
 
31
+ try:
32
+ import annotationlib # noqa
33
+ except ImportError:
34
+ annotationlib = None
35
+
36
+
31
37
  MISSING = dc.MISSING
32
38
 
33
39
 
40
+ def _get_annotations(obj):
41
+ if annotationlib is not None:
42
+ return annotationlib.get_annotations(obj, format=annotationlib.Format.FORWARDREF) # noqa
43
+ else:
44
+ return inspect.get_annotations(obj)
45
+
46
+
34
47
  class ClassInfo:
35
48
 
36
49
  def __init__(self, cls: type, *, _constructing: bool = False) -> None:
@@ -54,7 +67,7 @@ class ClassInfo:
54
67
 
55
68
  @cached.property
56
69
  def cls_annotations(self) -> ta.Mapping[str, ta.Any]:
57
- return inspect.get_annotations(self._cls)
70
+ return _get_annotations(self._cls)
58
71
 
59
72
  ##
60
73
 
@@ -136,7 +149,7 @@ class ClassInfo:
136
149
 
137
150
  @cached.property
138
151
  def generic_mro_lookup(self) -> ta.Mapping[type, rfl.Type]:
139
- return col.unique_dict((check.not_none(rfl.get_concrete_type(g)), g) for g in self.generic_mro)
152
+ return col.unique_map((check.not_none(rfl.get_concrete_type(g)), g) for g in self.generic_mro)
140
153
 
141
154
  @cached.property
142
155
  def generic_replaced_field_types(self) -> ta.Mapping[str, rfl.Type]: