omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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 (163) hide show
  1. omlish/__about__.py +109 -5
  2. omlish/__init__.py +0 -8
  3. omlish/asyncs/__init__.py +9 -9
  4. omlish/asyncs/anyio.py +123 -19
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/trio_asyncio.py +7 -3
  9. omlish/bootstrap.py +737 -0
  10. omlish/check.py +1 -1
  11. omlish/collections/__init__.py +5 -0
  12. omlish/collections/exceptions.py +2 -0
  13. omlish/collections/identity.py +7 -0
  14. omlish/collections/utils.py +38 -9
  15. omlish/configs/strings.py +96 -0
  16. omlish/dataclasses/__init__.py +16 -0
  17. omlish/dataclasses/impl/copy.py +30 -0
  18. omlish/dataclasses/impl/descriptors.py +95 -0
  19. omlish/dataclasses/impl/exceptions.py +6 -0
  20. omlish/dataclasses/impl/fields.py +24 -25
  21. omlish/dataclasses/impl/init.py +4 -2
  22. omlish/dataclasses/impl/main.py +2 -0
  23. omlish/dataclasses/impl/reflect.py +1 -1
  24. omlish/dataclasses/utils.py +67 -0
  25. omlish/{lang/datetimes.py → datetimes.py} +8 -4
  26. omlish/diag/__init__.py +4 -0
  27. omlish/diag/procfs.py +2 -2
  28. omlish/{testing → diag}/pydevd.py +35 -0
  29. omlish/diag/threads.py +131 -48
  30. omlish/dispatch/_dispatch2.py +65 -0
  31. omlish/dispatch/_dispatch3.py +104 -0
  32. omlish/docker.py +16 -1
  33. omlish/fnpairs.py +11 -4
  34. omlish/formats/__init__.py +0 -0
  35. omlish/{configs → formats}/dotenv.py +15 -24
  36. omlish/{json.py → formats/json.py} +2 -1
  37. omlish/formats/yaml.py +223 -0
  38. omlish/graphs/trees.py +1 -1
  39. omlish/http/asgi.py +2 -1
  40. omlish/http/collections.py +15 -0
  41. omlish/http/consts.py +22 -1
  42. omlish/http/sessions.py +10 -3
  43. omlish/inject/__init__.py +49 -17
  44. omlish/inject/binder.py +185 -5
  45. omlish/inject/bindings.py +3 -36
  46. omlish/inject/eagers.py +2 -8
  47. omlish/inject/elements.py +31 -10
  48. omlish/inject/exceptions.py +1 -1
  49. omlish/inject/impl/elements.py +37 -12
  50. omlish/inject/impl/injector.py +72 -25
  51. omlish/inject/impl/inspect.py +33 -5
  52. omlish/inject/impl/origins.py +77 -0
  53. omlish/inject/impl/{private.py → privates.py} +2 -2
  54. omlish/inject/impl/scopes.py +6 -2
  55. omlish/inject/injector.py +8 -4
  56. omlish/inject/inspect.py +18 -0
  57. omlish/inject/keys.py +8 -14
  58. omlish/inject/listeners.py +26 -0
  59. omlish/inject/managed.py +76 -10
  60. omlish/inject/multis.py +68 -18
  61. omlish/inject/origins.py +30 -0
  62. omlish/inject/overrides.py +5 -4
  63. omlish/inject/{private.py → privates.py} +6 -10
  64. omlish/inject/providers.py +12 -85
  65. omlish/inject/scopes.py +13 -6
  66. omlish/inject/types.py +3 -1
  67. omlish/inject/utils.py +18 -0
  68. omlish/iterators.py +69 -2
  69. omlish/lang/__init__.py +24 -9
  70. omlish/lang/cached.py +2 -2
  71. omlish/lang/classes/restrict.py +12 -1
  72. omlish/lang/classes/simple.py +18 -8
  73. omlish/lang/contextmanagers.py +13 -4
  74. omlish/lang/descriptors.py +132 -1
  75. omlish/lang/functions.py +8 -28
  76. omlish/lang/imports.py +67 -0
  77. omlish/lang/iterables.py +60 -1
  78. omlish/lang/maybes.py +3 -0
  79. omlish/lang/objects.py +38 -0
  80. omlish/lang/strings.py +25 -0
  81. omlish/lang/sys.py +9 -0
  82. omlish/lang/typing.py +42 -0
  83. omlish/lifecycles/__init__.py +34 -0
  84. omlish/lifecycles/abstract.py +43 -0
  85. omlish/lifecycles/base.py +51 -0
  86. omlish/lifecycles/contextmanagers.py +74 -0
  87. omlish/lifecycles/controller.py +116 -0
  88. omlish/lifecycles/manager.py +161 -0
  89. omlish/lifecycles/states.py +43 -0
  90. omlish/lifecycles/transitions.py +64 -0
  91. omlish/lite/__init__.py +1 -0
  92. omlish/lite/cached.py +18 -0
  93. omlish/lite/check.py +29 -0
  94. omlish/lite/contextmanagers.py +18 -0
  95. omlish/lite/json.py +30 -0
  96. omlish/lite/logs.py +52 -0
  97. omlish/lite/marshal.py +316 -0
  98. omlish/lite/reflect.py +49 -0
  99. omlish/lite/runtime.py +18 -0
  100. omlish/lite/secrets.py +19 -0
  101. omlish/lite/strings.py +25 -0
  102. omlish/lite/subprocesses.py +112 -0
  103. omlish/logs/configs.py +15 -2
  104. omlish/logs/formatters.py +7 -2
  105. omlish/marshal/__init__.py +32 -0
  106. omlish/marshal/any.py +5 -5
  107. omlish/marshal/base.py +27 -11
  108. omlish/marshal/base64.py +24 -9
  109. omlish/marshal/dataclasses.py +34 -28
  110. omlish/marshal/datetimes.py +74 -18
  111. omlish/marshal/enums.py +14 -8
  112. omlish/marshal/exceptions.py +11 -1
  113. omlish/marshal/factories.py +59 -74
  114. omlish/marshal/forbidden.py +35 -0
  115. omlish/marshal/global_.py +11 -4
  116. omlish/marshal/iterables.py +21 -24
  117. omlish/marshal/mappings.py +23 -26
  118. omlish/marshal/naming.py +4 -0
  119. omlish/marshal/numbers.py +51 -0
  120. omlish/marshal/objects.py +1 -0
  121. omlish/marshal/optionals.py +11 -12
  122. omlish/marshal/polymorphism.py +86 -21
  123. omlish/marshal/primitives.py +4 -5
  124. omlish/marshal/standard.py +13 -8
  125. omlish/marshal/uuids.py +4 -5
  126. omlish/matchfns.py +218 -0
  127. omlish/os.py +64 -0
  128. omlish/reflect/__init__.py +39 -0
  129. omlish/reflect/isinstance.py +38 -0
  130. omlish/reflect/ops.py +84 -0
  131. omlish/reflect/subst.py +110 -0
  132. omlish/reflect/types.py +275 -0
  133. omlish/secrets/__init__.py +23 -0
  134. omlish/secrets/crypto.py +132 -0
  135. omlish/secrets/marshal.py +70 -0
  136. omlish/secrets/openssl.py +207 -0
  137. omlish/secrets/passwords.py +120 -0
  138. omlish/secrets/secrets.py +299 -0
  139. omlish/secrets/subprocesses.py +42 -0
  140. omlish/sql/dbs.py +7 -6
  141. omlish/sql/duckdb.py +136 -0
  142. omlish/sql/exprs.py +12 -0
  143. omlish/sql/secrets.py +10 -0
  144. omlish/sql/sqlean.py +17 -0
  145. omlish/term.py +2 -2
  146. omlish/testing/pytest/__init__.py +3 -2
  147. omlish/testing/pytest/inject/harness.py +3 -3
  148. omlish/testing/pytest/marks.py +4 -7
  149. omlish/testing/pytest/plugins/__init__.py +1 -0
  150. omlish/testing/pytest/plugins/asyncs.py +136 -0
  151. omlish/testing/pytest/plugins/pydevd.py +1 -1
  152. omlish/testing/pytest/plugins/switches.py +54 -19
  153. omlish/text/glyphsplit.py +97 -0
  154. omlish-0.0.0.dev7.dist-info/METADATA +50 -0
  155. omlish-0.0.0.dev7.dist-info/RECORD +268 -0
  156. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
  157. omlish/reflect.py +0 -355
  158. omlish-0.0.0.dev5.dist-info/METADATA +0 -34
  159. omlish-0.0.0.dev5.dist-info/RECORD +0 -212
  160. /omlish/{asyncs/futures.py → concurrent.py} +0 -0
  161. /omlish/{configs → formats}/props.py +0 -0
  162. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
  163. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
omlish/check.py CHANGED
@@ -58,7 +58,7 @@ def _unpack_isinstance_spec(spec: ta.Any) -> tuple:
58
58
  return spec
59
59
 
60
60
 
61
- def isinstance(v: ta.Any, spec: ta.Union[type[T], tuple], msg: Message = None) -> T: # noqa
61
+ def isinstance(v: ta.Any, spec: type[T] | tuple, msg: Message = None) -> T: # noqa
62
62
  if not _isinstance(v, _unpack_isinstance_spec(spec)):
63
63
  _raise(TypeError, 'Must be instance', msg, v, spec)
64
64
  return v
@@ -25,6 +25,10 @@ from .coerce import ( # noqa
25
25
  seq_or_none,
26
26
  )
27
27
 
28
+ from .exceptions import ( # noqa
29
+ DuplicateKeyError,
30
+ )
31
+
28
32
  from .frozen import ( # noqa
29
33
  Frozen,
30
34
  FrozenDict,
@@ -37,6 +41,7 @@ from .identity import ( # noqa
37
41
  IdentityKeyDict,
38
42
  IdentitySet,
39
43
  IdentityWrapper,
44
+ IdentityWeakSet,
40
45
  )
41
46
 
42
47
  from .indexed import ( # noqa
@@ -0,0 +1,2 @@
1
+ class DuplicateKeyError(KeyError):
2
+ pass
@@ -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
@@ -3,6 +3,8 @@ import itertools
3
3
  import typing as ta
4
4
 
5
5
  from .. import check
6
+ from .. import lang
7
+ from .exceptions import DuplicateKeyError
6
8
  from .identity import IdentityKeyDict
7
9
  from .identity import IdentitySet
8
10
 
@@ -48,29 +50,56 @@ def partition(items: ta.Iterable[T], pred: ta.Callable[[T], bool]) -> tuple[list
48
50
  return t, f
49
51
 
50
52
 
51
- def unique(it: ta.Iterable[T], *, identity: bool = False) -> list[T]:
53
+ def unique(
54
+ it: ta.Iterable[T],
55
+ *,
56
+ key: ta.Callable[[T], ta.Any] = lang.identity,
57
+ identity: bool = False,
58
+ strict: bool = False,
59
+ ) -> list[T]:
52
60
  if isinstance(it, str):
53
61
  raise TypeError(it)
54
62
  ret: list[T] = []
55
- seen: ta.MutableSet[T] = IdentitySet() if identity else set()
63
+ seen: ta.MutableSet = IdentitySet() if identity else set()
56
64
  for e in it:
57
- if e not in seen:
58
- seen.add(e)
65
+ k = key(e)
66
+ if k in seen:
67
+ if strict:
68
+ raise DuplicateKeyError(k, e)
69
+ else:
70
+ seen.add(k)
59
71
  ret.append(e)
60
72
  return ret
61
73
 
62
74
 
63
- def unique_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, V]:
75
+ def unique_map(
76
+ kvs: ta.Iterable[tuple[K, V]],
77
+ *,
78
+ identity: bool = False,
79
+ strict: bool = False,
80
+ ) -> ta.MutableMapping[K, V]:
64
81
  d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
65
82
  for k, v in kvs:
66
83
  if k in d:
67
- raise KeyError(k)
68
- d[k] = v
84
+ if strict:
85
+ raise DuplicateKeyError(k)
86
+ else:
87
+ d[k] = v
69
88
  return d
70
89
 
71
90
 
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)
91
+ def unique_map_by(
92
+ fn: ta.Callable[[V], K],
93
+ vs: ta.Iterable[V],
94
+ *,
95
+ identity: bool = False,
96
+ strict: bool = False,
97
+ ) -> ta.MutableMapping[K, V]:
98
+ return unique_map(
99
+ ((fn(v), v) for v in vs),
100
+ identity=identity,
101
+ strict=strict,
102
+ )
74
103
 
75
104
 
76
105
  def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
@@ -0,0 +1,96 @@
1
+ """
2
+ TODO:
3
+ - reflecty generalized rewriter, obviously..
4
+ - env vars
5
+ - coalescing - {$FOO|$BAR|baz}
6
+ """
7
+ import collections.abc
8
+ import typing as ta
9
+
10
+ from .. import dataclasses as dc
11
+ from .. import lang
12
+ from ..text import glyphsplit
13
+
14
+
15
+ T = ta.TypeVar('T')
16
+
17
+
18
+ class InterpolateStringsMetadata(lang.Marker):
19
+ pass
20
+
21
+
22
+ @dc.field_modifier
23
+ def secret_or_key_field(f: dc.Field) -> dc.Field:
24
+ return dc.update_field_metadata(f, {
25
+ InterpolateStringsMetadata: True,
26
+ })
27
+
28
+
29
+ @dc.field_modifier
30
+ def interpolate_field(f: dc.Field) -> dc.Field:
31
+ return dc.update_field_metadata(f, {InterpolateStringsMetadata: True})
32
+
33
+
34
+ class StringRewriter:
35
+ def __init__(self, fn: ta.Callable[[str], str]) -> None:
36
+ super().__init__()
37
+ self._fn = fn
38
+
39
+ def __call__(self, v: T, *, _soft: bool = False) -> T:
40
+ if v is None:
41
+ return None # type: ignore
42
+
43
+ if dc.is_dataclass(v):
44
+ kw = {}
45
+ for f in dc.fields(v):
46
+ fv = getattr(v, f.name)
47
+ nfv = self(fv, _soft=not f.metadata.get(InterpolateStringsMetadata))
48
+ if fv is not nfv:
49
+ kw[f.name] = nfv
50
+ if not kw:
51
+ return v # type: ignore
52
+ return dc.replace(v, **kw)
53
+
54
+ if isinstance(v, str):
55
+ if not _soft:
56
+ v = self._fn(v) # type: ignore
57
+ return v # type: ignore
58
+
59
+ if isinstance(v, lang.BUILTIN_SCALAR_ITERABLE_TYPES):
60
+ return v # type: ignore
61
+
62
+ if isinstance(v, collections.abc.Mapping):
63
+ nm = []
64
+ b = False
65
+ for mk, mv in v.items():
66
+ nk, nv = self(mk, _soft=_soft), self(mv, _soft=_soft)
67
+ nm.append((nk, nv))
68
+ b |= nk is not mk or nv is not mv
69
+ if not b:
70
+ return v # type: ignore
71
+ return v.__class__(nm) # type: ignore
72
+
73
+ if isinstance(v, (collections.abc.Sequence, collections.abc.Set)):
74
+ nl = []
75
+ b = False
76
+ for le in v:
77
+ ne = self(le, _soft=_soft)
78
+ nl.append(ne)
79
+ b |= ne is not le
80
+ if not b:
81
+ return v # type: ignore
82
+ return v.__class__(nl) # type: ignore
83
+
84
+ return v
85
+
86
+
87
+ def interpolate_strings(v: T, rpl: ta.Mapping[str, str]) -> T:
88
+ def fn(v):
89
+ if not v:
90
+ return v
91
+ sps = glyphsplit.split_braces(v)
92
+ if len(sps) == 1 and isinstance(sps[0], str):
93
+ return sps[0]
94
+ return ''.join(rpl[p.s] if isinstance(p, glyphsplit.GlyphMatch) else p for p in sps)
95
+
96
+ return StringRewriter(fn)(v)
@@ -36,6 +36,11 @@ from .impl.as_ import ( # noqa
36
36
  astuple,
37
37
  )
38
38
 
39
+ from .impl.params import ( # noqa
40
+ FieldExtras,
41
+ get_field_extras,
42
+ )
43
+
39
44
  from .impl.replace import ( # noqa
40
45
  replace,
41
46
  )
@@ -55,6 +60,7 @@ globals()['make_dataclass'] = xmake_dataclass
55
60
 
56
61
  from .impl.exceptions import ( # noqa
57
62
  CheckError,
63
+ FieldCheckError,
58
64
  )
59
65
 
60
66
  from .impl.metaclass import ( # noqa
@@ -81,3 +87,13 @@ from .impl.reflect import ( # noqa
81
87
  ClassInfo,
82
88
  reflect,
83
89
  )
90
+
91
+ from .utils import ( # noqa
92
+ chain_metadata,
93
+ deep_replace,
94
+ field_modifier,
95
+ maybe_post_init,
96
+ opt_repr,
97
+ update_field_extras,
98
+ update_field_metadata,
99
+ )
@@ -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)
@@ -0,0 +1,95 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from ... import defs
6
+
7
+
8
+ class AbstractFieldDescriptor(abc.ABC):
9
+
10
+ def __init__(
11
+ self,
12
+ *,
13
+ default: ta.Any = dc.MISSING,
14
+ frozen: bool = False,
15
+ name: str | None = None,
16
+ pre_set: ta.Callable[[ta.Any, ta.Any], ta.Any] | None = None,
17
+ post_set: ta.Callable[[ta.Any, ta.Any], None] | None = None,
18
+ ) -> None:
19
+ super().__init__()
20
+
21
+ self._default = default
22
+ self._frozen = frozen
23
+ self._name = name
24
+ self._pre_set = pre_set
25
+ self._post_set = post_set
26
+
27
+ defs.repr('name')
28
+ defs.getter('default', 'frozen', 'name', 'pre_set', 'post_set')
29
+
30
+ def __set_name__(self, owner, name):
31
+ if self._name is None:
32
+ self._name = name
33
+
34
+ def __get__(self, instance, owner=None):
35
+ if instance is not None:
36
+ try:
37
+ return self._get(instance)
38
+ except AttributeError:
39
+ pass
40
+ if self._default is not dc.MISSING:
41
+ return self._default
42
+ raise AttributeError(self._name)
43
+
44
+ @abc.abstractmethod
45
+ def _get(self, instance):
46
+ raise NotImplementedError
47
+
48
+ def __set__(self, instance, value):
49
+ if self._frozen:
50
+ raise dc.FrozenInstanceError(f'cannot assign to field {self._name!r}')
51
+ if self._pre_set is not None:
52
+ value = self._pre_set(instance, value)
53
+ self._set(instance, value)
54
+ if self._post_set is not None:
55
+ self._post_set(instance, value)
56
+
57
+ @abc.abstractmethod
58
+ def _set(self, instance, value):
59
+ raise NotImplementedError
60
+
61
+ def __delete__(self, instance):
62
+ if self._frozen:
63
+ raise dc.FrozenInstanceError(f'cannot delete field {self._name!r}')
64
+ self._del(instance)
65
+
66
+ @abc.abstractmethod
67
+ def _del(self, instance):
68
+ raise NotImplementedError
69
+
70
+
71
+ class PyFieldDescriptor(AbstractFieldDescriptor):
72
+
73
+ def __init__(
74
+ self,
75
+ attr: str,
76
+ **kwargs: ta.Any,
77
+ ) -> None:
78
+ super().__init__(**kwargs)
79
+
80
+ self._attr = attr
81
+
82
+ defs.repr('attr', 'name')
83
+ defs.getter('attr')
84
+
85
+ def _get(self, instance):
86
+ return getattr(instance, self._attr)
87
+
88
+ def _set(self, instance, value):
89
+ setattr(instance, self._attr, value)
90
+
91
+ def _del(self, instance):
92
+ delattr(instance, self._attr)
93
+
94
+
95
+ FieldDescriptor = PyFieldDescriptor
@@ -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
@@ -98,51 +98,50 @@ def field_init(
98
98
 
99
99
  lines = []
100
100
 
101
- if fx.coerce is not None:
102
- cn = f'__dataclass_coerce__{f.name}__'
103
- locals[cn] = fx.coerce
104
- lines.append(f'{f.name} = {cn}({f.name})')
105
-
106
- if fx.check is not None:
107
- cn = f'__dataclass_check__{f.name}__'
108
- locals[cn] = fx.check
109
- lines.append(f'if not {cn}({f.name}): raise __dataclass_CheckException__')
110
-
111
- if fx.check_type:
112
- cn = f'__dataclass_check_type__{f.name}__'
113
- locals[cn] = f.type
114
- lines.append(
115
- f'if not __dataclass_builtins_isinstance__({f.name}, {cn}): '
116
- f'raise __dataclass_builtins_TypeError__({f.name}, {cn})',
117
- )
118
-
119
101
  value: str | None = None
120
102
  if f.default_factory is not MISSING:
121
103
  if f.init:
122
104
  locals[default_name] = f.default_factory
123
- value = (
124
- f'{default_name}() '
125
- f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__ '
126
- f'else {f.name}'
127
- )
105
+ lines.append(f'if {f.name} is __dataclass_HAS_DEFAULT_FACTORY__: {f.name} = {default_name}()')
106
+ value = f.name
128
107
  else:
129
108
  locals[default_name] = f.default_factory
130
- value = f'{default_name}()'
109
+ lines.append(f'{f.name} = {default_name}()')
110
+ value = f.name
131
111
 
132
112
  elif f.init:
133
113
  if f.default is MISSING:
134
114
  value = f.name
135
115
  elif f.default is not MISSING:
136
- locals[default_name] = f.default
116
+ locals[default_name] = f.default # Not referenced her, just useful / consistent to have in function scope
137
117
  value = f.name
138
118
 
139
119
  elif slots and f.default is not MISSING:
140
120
  locals[default_name] = f.default
121
+ lines.append(f'{f.name} = {default_name}')
141
122
  value = default_name
142
123
 
143
124
  else:
144
125
  pass
145
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
+
146
145
  if value is not None and field_type(f) is not FieldType.INIT:
147
146
  lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
148
147
 
@@ -4,6 +4,7 @@ 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
@@ -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
@@ -137,6 +138,7 @@ class MainProcessor:
137
138
  DocProcessor,
138
139
  MatchArgsProcessor,
139
140
  ReplaceProcessor,
141
+ CopyProcessor,
140
142
  ]:
141
143
  pcls(self._info).process()
142
144
 
@@ -149,7 +149,7 @@ class ClassInfo:
149
149
 
150
150
  @cached.property
151
151
  def generic_mro_lookup(self) -> ta.Mapping[type, rfl.Type]:
152
- return col.unique_map((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), strict=True)
153
153
 
154
154
  @cached.property
155
155
  def generic_replaced_field_types(self) -> ta.Mapping[str, rfl.Type]:
@@ -0,0 +1,67 @@
1
+ import collections
2
+ import dataclasses as dc
3
+ import types
4
+ import typing as ta
5
+
6
+ from .. import check
7
+ from .impl.params import DEFAULT_FIELD_EXTRAS
8
+ from .impl.params import FieldExtras
9
+ from .impl.params import get_field_extras
10
+
11
+
12
+ T = ta.TypeVar('T')
13
+
14
+
15
+ def maybe_post_init(sup: ta.Any) -> bool:
16
+ try:
17
+ fn = sup.__post_init__
18
+ except AttributeError:
19
+ return False
20
+ fn()
21
+ return True
22
+
23
+
24
+ def opt_repr(o: ta.Any) -> str | None:
25
+ return repr(o) if o is not None else None
26
+
27
+
28
+ class field_modifier: # noqa
29
+ def __init__(self, fn: ta.Callable[[dc.Field], dc.Field]) -> None:
30
+ super().__init__()
31
+ self.fn = fn
32
+
33
+ def __ror__(self, other: T) -> T:
34
+ return self(other)
35
+
36
+ def __call__(self, f: T) -> T:
37
+ return check.isinstance(self.fn(check.isinstance(f, dc.Field)), dc.Field) # type: ignore
38
+
39
+
40
+ def chain_metadata(*mds: ta.Mapping) -> types.MappingProxyType:
41
+ return types.MappingProxyType(collections.ChainMap(*mds)) # type: ignore # noqa
42
+
43
+
44
+ def update_field_metadata(f: dc.Field, nmd: ta.Mapping) -> dc.Field:
45
+ check.isinstance(f, dc.Field)
46
+ f.metadata = chain_metadata(nmd, f.metadata)
47
+ return f
48
+
49
+
50
+ def update_field_extras(f: dc.Field, *, unless_non_default: bool = False, **kwargs: ta.Any) -> dc.Field:
51
+ fe = get_field_extras(f)
52
+ return update_field_metadata(f, {
53
+ FieldExtras: dc.replace(fe, **{
54
+ k: v
55
+ for k, v in kwargs.items()
56
+ if not unless_non_default or v != getattr(DEFAULT_FIELD_EXTRAS, k)
57
+ }),
58
+ })
59
+
60
+
61
+ def deep_replace(o: T, *args: str | ta.Callable[[ta.Any], ta.Mapping[str, ta.Any]]) -> T:
62
+ if not args:
63
+ return o
64
+ elif len(args) == 1:
65
+ return dc.replace(o, **args[0](o)) # type: ignore
66
+ else:
67
+ return dc.replace(o, **{args[0]: deep_replace(getattr(o, args[0]), *args[1:])}) # type: ignore
@@ -1,5 +1,8 @@
1
+ # ruff: noqa: UP007
2
+ # @omlish-lite
1
3
  import datetime
2
4
  import re
5
+ import typing as ta
3
6
 
4
7
 
5
8
  def to_seconds(value: datetime.timedelta) -> float:
@@ -18,8 +21,7 @@ def months_ago(date: datetime.date, num: int) -> datetime.date:
18
21
  return datetime.date(ago_year, ago_month, 1)
19
22
 
20
23
 
21
- def parse_date(s: str, tz: datetime.timezone | None = None) -> datetime.date:
22
-
24
+ def parse_date(s: str, tz: ta.Optional[datetime.timezone] = None) -> datetime.date:
23
25
  if s.lower() in ['today', 'now']:
24
26
  return datetime.datetime.now(tz=tz)
25
27
  elif s.lower() == 'yesterday':
@@ -39,7 +41,8 @@ _TIMEDELTA_STR_RE = re.compile(
39
41
  r'((?P<days>-?\d+)\s*days?,\s*)?'
40
42
  r'(?P<hours>\d?\d):(?P<minutes>\d\d)'
41
43
  r':(?P<seconds>\d\d+(\.\d+)?)'
42
- r'\s*$')
44
+ r'\s*$',
45
+ )
43
46
 
44
47
 
45
48
  _TIMEDELTA_DHMS_RE = re.compile(
@@ -49,7 +52,8 @@ _TIMEDELTA_DHMS_RE = re.compile(
49
52
  r',?\s*((?P<hours>\d+(\.\d+)?)\s*(h|hours?))?'
50
53
  r',?\s*((?P<minutes>\d+(\.\d+)?)\s*(m|minutes?))?'
51
54
  r',?\s*((?P<seconds>\d+(\.\d+)?)\s*(s|secs?|seconds?))?'
52
- r'\s*$')
55
+ r'\s*$',
56
+ )
53
57
 
54
58
 
55
59
  def parse_timedelta(s: str) -> datetime.timedelta:
omlish/diag/__init__.py CHANGED
@@ -0,0 +1,4 @@
1
+ """
2
+ TODO:
3
+ - https://github.com/aristocratos/btop/blob/00c90884c328eb3e44a0ab094e833161092aae9c/src/linux/btop_collect.cpp#L443
4
+ """
omlish/diag/procfs.py CHANGED
@@ -12,9 +12,9 @@ import sys
12
12
  import typing as ta
13
13
 
14
14
  from .. import iterators as it
15
- from .. import json
16
15
  from .. import lang
17
16
  from .. import os as oos
17
+ from ..formats import json
18
18
  from .procstats import ProcStats
19
19
 
20
20
 
@@ -42,7 +42,7 @@ def parse_size(s: str) -> int:
42
42
  return int(v) * us[u]
43
43
 
44
44
 
45
- class ProcStat(lang.Namespace):
45
+ class ProcStat(lang.Namespace, lang.Final):
46
46
  PID = 0
47
47
  COMM = 1
48
48
  STATE = 2