ominfra 0.0.0.dev438__py3-none-any.whl → 0.0.0.dev483__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 ominfra might be problematic. Click here for more details.

ominfra/scripts/manage.py CHANGED
@@ -34,6 +34,7 @@ import io
34
34
  import itertools
35
35
  import json
36
36
  import logging
37
+ import operator
37
38
  import os
38
39
  import os.path
39
40
  import platform
@@ -65,6 +66,142 @@ if sys.version_info < (3, 8):
65
66
  raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
66
67
 
67
68
 
69
+ def __omlish_amalg__(): # noqa
70
+ return dict(
71
+ src_files=[
72
+ dict(path='../../omdev/packaging/versions.py', sha1='71627ad600b3529b829b0e227b0952f2c63c7271'),
73
+ dict(path='config.py', sha1='6ff640634488fa142d9aadee5aec95db462ce46f'),
74
+ dict(path='deploy/config.py', sha1='b11f480014b42206531ea897e76dd0220eb59969'),
75
+ dict(path='deploy/paths/types.py', sha1='4364179744afb2344f2b44d188e37f786c955970'),
76
+ dict(path='deploy/types.py', sha1='41b2becf7a9d009e18235a8b49cfbe0419785190'),
77
+ dict(path='../pyremote.py', sha1='2131faabec13af3d29747d9430286394603445ca'),
78
+ dict(path='../../omlish/asyncs/asyncio/channels.py', sha1='36cec6ea48887baaf536ae6301ec6ebc70f9f19b'),
79
+ dict(path='../../omlish/asyncs/asyncio/streams.py', sha1='78a498b78b51805d3b44ba7fe8c10c575389c6a9'),
80
+ dict(path='../../omlish/configs/types.py', sha1='f7a5584cd6eccb77d18d729796072a162e9a8790'),
81
+ dict(path='../../omlish/formats/ini/sections.py', sha1='731c92cce82e183d1d4bdc23fc781fad62187394'),
82
+ dict(path='../../omlish/formats/toml/parser.py', sha1='73dac82289350ab951c4bcdbfe61167fa221f26f'),
83
+ dict(path='../../omlish/formats/toml/writer.py', sha1='6ea41d7e724bb1dcf6bd84b88993ff4e8798e021'),
84
+ dict(path='../../omlish/lite/abstract.py', sha1='a2fc3f3697fa8de5247761e9d554e70176f37aac'),
85
+ dict(path='../../omlish/lite/attrops.py', sha1='c1ebfb8573d766d34593c452a2377208d02726dc'),
86
+ dict(path='../../omlish/lite/cached.py', sha1='0c33cf961ac8f0727284303c7a30c5ea98f714f2'),
87
+ dict(path='../../omlish/lite/check.py', sha1='bb6b6b63333699b84462951a854d99ae83195b94'),
88
+ dict(path='../../omlish/lite/contextmanagers.py', sha1='993f5ed96d3410f739a20363f55670d5e5267fa3'),
89
+ dict(path='../../omlish/lite/json.py', sha1='57eeddc4d23a17931e00284ffa5cb6e3ce089486'),
90
+ dict(path='../../omlish/lite/objects.py', sha1='9566bbf3530fd71fcc56321485216b592fae21e9'),
91
+ dict(path='../../omlish/lite/pycharm.py', sha1='6f84e57f02e2f1075918002f89e4201910d2a15e'),
92
+ dict(path='../../omlish/lite/reflect.py', sha1='c4fec44bf144e9d93293c996af06f6c65fc5e63d'),
93
+ dict(path='../../omlish/lite/resources.py', sha1='1365cb6046eb929358e7c86a3fda20d95fd4a296'),
94
+ dict(path='../../omlish/lite/strings.py', sha1='89831ecbc34ad80e118a865eceb390ed399dc4d6'),
95
+ dict(path='../../omlish/lite/typing.py', sha1='deaaa560b63d9a0e40991ec0006451f5f0df04c1'),
96
+ dict(path='../../omlish/logs/levels.py', sha1='91405563d082a5eba874da82aac89d83ce7b6152'),
97
+ dict(path='../../omlish/logs/std/filters.py', sha1='f36aab646d84d31e295b33aaaaa6f8b67ff38b3d'),
98
+ dict(path='../../omlish/logs/std/proxy.py', sha1='3e7301a2aa351127f9c85f61b2f85dcc3f15aafb'),
99
+ dict(path='../../omlish/logs/warnings.py', sha1='c4eb694b24773351107fcc058f3620f1dbfb6799'),
100
+ dict(path='../../omlish/os/deathsig.py', sha1='5d3f1a22132b7029d32e29b13c1cc20497c8f7a8'),
101
+ dict(path='../../omlish/os/environ.py', sha1='5e9ed4817af65683b496af49fef996630c3113b1'),
102
+ dict(path='../../omlish/os/linux.py', sha1='b6433d321eba7afab353b04107819fdc72f1d836'),
103
+ dict(path='../../omlish/os/paths.py', sha1='56c40b7c2aa84d1778d60ee4cda498f8c380cc8d'),
104
+ dict(path='../../omlish/shlex.py', sha1='a69721913bcd4f4008600e390fb7822637c2a8ec'),
105
+ dict(path='../../omdev/home/paths.py', sha1='a83516c97a2e99e79153a414db3d23091186bb23'),
106
+ dict(path='../../omdev/packaging/specifiers.py', sha1='a56ab4e8c9b174adb523921f6280ac41e0fce749'),
107
+ dict(path='deploy/paths/specs.py', sha1='023167da1ad9fcf09d9d44963177175591a97377'),
108
+ dict(path='remote/config.py', sha1='48f9367e9db4b23166657ff34eb644c9869d48a8'),
109
+ dict(path='remote/payload.py', sha1='acacf4c2901b7708224af5d4414ecb823947297a'),
110
+ dict(path='targets/bestpython.py', sha1='75c16ab86397a8e81017f148a2ef711567b6ab27'),
111
+ dict(path='targets/targets.py', sha1='d07f2d30c31bad89bd4a3b44bb6a5b6c95c05888'),
112
+ dict(path='../../omlish/argparse/cli.py', sha1='f4dc3cd353d14386b5da0306768700e396afd2b3'),
113
+ dict(path='../../omlish/configs/formats.py', sha1='9bc4f953b4b8700f6f109e6f49e2d70f8e48ce7c'),
114
+ dict(path='../../omlish/lite/marshal.py', sha1='96348f5f2a26dc27d842d33cc3927e9da163436b'),
115
+ dict(path='../../omlish/lite/maybes.py', sha1='bdf5136654ccd14b6a072588cad228925bdfbabd'),
116
+ dict(path='../../omlish/lite/runtime.py', sha1='2e752a27ae2bf89b1bb79b4a2da522a3ec360c70'),
117
+ dict(path='../../omlish/lite/timeouts.py', sha1='a0f673033a6943f242e35848d78a41892b9c62a1'),
118
+ dict(path='../../omlish/logs/infos.py', sha1='4dd104bd468a8c438601dd0bbda619b47d2f1620'),
119
+ dict(path='../../omlish/logs/protocols.py', sha1='05ca4d1d7feb50c4e3b9f22ee371aa7bf4b3dbd1'),
120
+ dict(path='../../omlish/logs/std/json.py', sha1='2a75553131e4d5331bb0cedde42aa183f403fc3b'),
121
+ dict(path='../../omlish/os/atomics.py', sha1='ccb62620b95f60ac50561c283d50e5fcfdccb215'),
122
+ dict(path='../../omlish/text/indent.py', sha1='cc23647bdcd8d26c8afe9e36a0aefb32da58cbb8'),
123
+ dict(path='../../omdev/interp/types.py', sha1='cfc14929777fb19f723c875bcafc8f7c66593d6d'),
124
+ dict(path='commands/base.py', sha1='17310f7272b6ac7b6438e32bfd7b24004d284399'),
125
+ dict(path='deploy/conf/specs.py', sha1='d191fa887c59198f5eff5c62414031204a76fa65'),
126
+ dict(path='deploy/tags.py', sha1='e6e7a1f4fcee9f5764acbe7f307b31e26f81bbc3'),
127
+ dict(path='marshal.py', sha1='175f59215a92afd42468f6258f1202b9ec3362cb'),
128
+ dict(path='remote/channel.py', sha1='2b6498da48ff89901b88a6a1ef84c58b37ddb410'),
129
+ dict(path='../../omlish/asyncs/asyncio/timeouts.py', sha1='4d31b02b3c39b8f2fa7e94db36552fde6942e36a'),
130
+ dict(path='../../omlish/configs/nginx.py', sha1='d0ad33c21674fc02f23281247517c827f455eb0b'),
131
+ dict(path='../../omlish/lite/configs.py', sha1='c8602e0e197ef1133e7e8e248935ac745bfd46cb'),
132
+ dict(path='../../omlish/lite/inject.py', sha1='6f097e3170019a34ff6834d36fcc9cbeed3a7ab4'),
133
+ dict(path='../../omlish/logs/contexts.py', sha1='7456964ade9ac66460e9ade4e242dbdc24b39501'),
134
+ dict(path='../../omlish/logs/standard.py', sha1='818b674f7d15012f25b79f52f6e8e7368b633038'),
135
+ dict(path='../../omlish/subprocesses/run.py', sha1='8200e48f0c49d164df3503cd0143038d0c4d30aa'),
136
+ dict(path='../../omlish/subprocesses/wrap.py', sha1='8a9b7d2255481fae15c05f5624b0cdc0766f4b3f'),
137
+ dict(path='../../omdev/interp/providers/base.py', sha1='f5d068c21f230d742e9015b033cd6320f4c68898'),
138
+ dict(path='commands/injection.py', sha1='f7d8aec3c33efc61da1f0c6700bdfbe7bcc10e56'),
139
+ dict(path='commands/marshal.py', sha1='a21c3a75fe17bb80d32d10a3d5524d67a96ea210'),
140
+ dict(path='commands/ping.py', sha1='af4c34e9b1811269c954cf502b336a6446639a2a'),
141
+ dict(path='commands/types.py', sha1='10b88571981b9964287f30f27abf6d09400b51c6'),
142
+ dict(path='deploy/paths/paths.py', sha1='bf7794e998caa1611277ac5809eb7ec91a76d1e8'),
143
+ dict(path='deploy/specs.py', sha1='b3a411b32b47f81f5ad673d8b0338970a6eb6ff9'),
144
+ dict(path='../../omlish/logs/base.py', sha1='a376460b11b9dc0555fd4ead5437af62c2109a4b'),
145
+ dict(path='../../omlish/logs/std/records.py', sha1='8bbf6ef9eccb3a012c6ca416ddf3969450fd8fc9'),
146
+ dict(path='../../omlish/subprocesses/base.py', sha1='cb9f668be5422fecb27222caabb67daac6c1bab9'),
147
+ dict(path='../../omdev/interp/resolvers.py', sha1='817b8e76401cd7a19eb43ca54d65272e4c8a4b0e'),
148
+ dict(path='commands/local.py', sha1='db3c5b0a1f067f54e2133234e36e7db393e4dec3'),
149
+ dict(path='deploy/conf/manager.py', sha1='7450a8616dbc46f6c68387192035a6ea258aebe2'),
150
+ dict(path='deploy/paths/owners.py', sha1='382bcec4824f0fc71dddf083c6e88748b5c62ef2'),
151
+ dict(path='../../omlish/logs/std/loggers.py', sha1='daa35bdc4adea5006e442688017f0de3392579b7'),
152
+ dict(path='../../omlish/subprocesses/asyncs.py', sha1='bba44d524c24c6ac73168aee6343488414e5bf48'),
153
+ dict(path='../../omlish/subprocesses/sync.py', sha1='8434919eba4da67825773d56918fdc0cb2f1883b'),
154
+ dict(path='../../omdev/git/shallow.py', sha1='7b5f9d77b7a01df5828ca61a2adc6dae54cf676b'),
155
+ dict(path='deploy/injection.py', sha1='7d641dd20ff0c75de5679079c52647653849d6cc'),
156
+ dict(path='deploy/paths/manager.py', sha1='eb1c84e0ca03083f69b53cee25bf7ffd752cc7a9'),
157
+ dict(path='deploy/tmp.py', sha1='d8b7aeaa26ab58e64aba371d46bc661462d47c5e'),
158
+ dict(path='../../omlish/asyncs/asyncio/subprocesses.py', sha1='b6b5f9ae3fd0b9c83593bad2e04a08f726e5904d'),
159
+ dict(path='../../omlish/logs/modules.py', sha1='99e73cde6872fd5eda6af3dbf0fc9322bdeb641a'),
160
+ dict(path='../../omdev/interp/inspect.py', sha1='736287b4ec8d14a8c30afa0ba23996fdc0662caa'),
161
+ dict(path='../../omdev/interp/pyenv/pyenv.py', sha1='d1f6e657c671c1b1a5b0e627284df656fe2d10d3'),
162
+ dict(path='../../omdev/interp/uv/uv.py', sha1='8c6515cd6755efab3972da92a285e94ccb255515'),
163
+ dict(path='commands/subprocess.py', sha1='788bd859701fce066bd00c820919f826b43b8b57'),
164
+ dict(path='deploy/conf/inject.py', sha1='d006b45d92f3b5f30a797b65fbed23f90c3db490'),
165
+ dict(path='deploy/git.py', sha1='5ee2e816e18fef493cb2ccc33f33ad673175ad7a'),
166
+ dict(path='deploy/paths/inject.py', sha1='1c501d086fcbde9c2b9ead21fc3c7b175bbf4f76'),
167
+ dict(path='deploy/systemd.py', sha1='773c4482e85a974443bb26237a86b1dfbcd9936c'),
168
+ dict(path='remote/execution.py', sha1='005da809e58790a0e5255df8e57afd5cd6268d7d'),
169
+ dict(path='remote/spawning.py', sha1='9cb6b5da1ba6daabb35a5742be647748c01d40d3'),
170
+ dict(path='system/packages.py', sha1='9988fc93dbca9336c378bf5fad6f68f5b8c0260e'),
171
+ dict(path='system/platforms.py', sha1='f3fc312318cff15f97dd9b10fa5f2408abc45a1b'),
172
+ dict(path='../../omdev/interp/providers/running.py', sha1='85c9cc69ff6fbd6c8cf78ed6262619a30856c2f1'),
173
+ dict(path='../../omdev/interp/providers/system.py', sha1='9638a154475ca98775159d27739563ac7fb2eb16'),
174
+ dict(path='../../omdev/interp/pyenv/install.py', sha1='4a10a19717364b4ba9f3b8bf1d12621cf21ba8b8'),
175
+ dict(path='../../omdev/interp/uv/provider.py', sha1='149f19f95eb701ffad5dc74392f4692c21b5f8c0'),
176
+ dict(path='commands/inject.py', sha1='7a95b6487b01230dd2fe0d9f67382d8889039e7b'),
177
+ dict(path='system/commands.py', sha1='17bbaa945b6ded0a88d31c52b410cbce8fc324a0'),
178
+ dict(path='system/config.py', sha1='fd1ebc2cf36fd312ff69d1af100a7e9c638f1fcc'),
179
+ dict(path='../../omdev/interp/providers/inject.py', sha1='7cc9ebf58cf2ec09545321456bd9da9f9a3a79fb'),
180
+ dict(path='../../omdev/interp/pyenv/provider.py', sha1='377542ce01a35849e2a5b4a4dbafedc26882f983'),
181
+ dict(path='../../omdev/interp/uv/inject.py', sha1='e95d058c2340baa5a3155ec3440f311d1daa10a8'),
182
+ dict(path='bootstrap.py', sha1='e66138947a41e8a49576885cf4b1390315d44f88'),
183
+ dict(path='system/inject.py', sha1='8a34be1b982cb42981c08306f12994a0fff258bd'),
184
+ dict(path='../../omdev/interp/pyenv/inject.py', sha1='b8fb68f5a7cae86c70fe1bad6c29a8b2dfc985c3'),
185
+ dict(path='remote/_main.py', sha1='5ae1dc673ce22f2d40612c66b3a5c3d01ebb6718'),
186
+ dict(path='../../omdev/interp/inject.py', sha1='b039abbadf0b096d2724182af2e0ebda2a230852'),
187
+ dict(path='remote/connection.py', sha1='18bc6c4a446a9bef3c54b861a01418b1b7ba39ff'),
188
+ dict(path='../../omdev/interp/default.py', sha1='a799969a0d3f4b57538587b13ceb08f6334ebc16'),
189
+ dict(path='remote/inject.py', sha1='8713a421b18ff7625c95017616380f0500c3c39c'),
190
+ dict(path='targets/connection.py', sha1='891f1d35ee3814bed32e6de71e1ca47574635da1'),
191
+ dict(path='deploy/interp.py', sha1='89371a87a275fea2e8566a0983e4906cda46a105'),
192
+ dict(path='deploy/venvs.py', sha1='7ad41a11098dd68d83a0804d2ea95779d0be4de0'),
193
+ dict(path='targets/inject.py', sha1='e4bfba31b044da9545d4c00965e7e15b97a40cce'),
194
+ dict(path='deploy/apps.py', sha1='6df5d728b6715583a792d1e92268c0c07502509f'),
195
+ dict(path='deploy/deploy.py', sha1='635f84ad370b22797406d2ae55d78621ea1f7b2b'),
196
+ dict(path='deploy/commands.py', sha1='0e9fdd122fe3a4028efede0862b059c091cc13cb'),
197
+ dict(path='deploy/inject.py', sha1='d84e9c3e980c5a1ec62d18628d8911f5b6bb125f'),
198
+ dict(path='inject.py', sha1='b1c173df021f190d3631f3828470fa1564e0b4b4'),
199
+ dict(path='bootstrap_.py', sha1='c7d7c3e88703200a836df63e395f7154bf871f36'),
200
+ dict(path='main.py', sha1='ddddcdb54aabe67188dce6a5f65e04ce296a75b0'),
201
+ ],
202
+ )
203
+
204
+
68
205
  ########################################
69
206
 
70
207
 
@@ -90,7 +227,7 @@ TomlParseFloat = ta.Callable[[str], ta.Any] # ta.TypeAlias
90
227
  TomlKey = ta.Tuple[str, ...] # ta.TypeAlias
91
228
  TomlPos = int # ta.TypeAlias
92
229
 
93
- # ../../omlish/lite/attrops.py
230
+ # ../../omlish/lite/abstract.py
94
231
  T = ta.TypeVar('T')
95
232
 
96
233
  # ../../omlish/lite/cached.py
@@ -281,12 +418,12 @@ class _BaseVersion:
281
418
 
282
419
  def __lt__(self, other: '_BaseVersion') -> bool:
283
420
  if not isinstance(other, _BaseVersion):
284
- return NotImplemented # type: ignore
421
+ return NotImplemented
285
422
  return self._key < other._key
286
423
 
287
424
  def __le__(self, other: '_BaseVersion') -> bool:
288
425
  if not isinstance(other, _BaseVersion):
289
- return NotImplemented # type: ignore
426
+ return NotImplemented
290
427
  return self._key <= other._key
291
428
 
292
429
  def __eq__(self, other: object) -> bool:
@@ -296,12 +433,12 @@ class _BaseVersion:
296
433
 
297
434
  def __ge__(self, other: '_BaseVersion') -> bool:
298
435
  if not isinstance(other, _BaseVersion):
299
- return NotImplemented # type: ignore
436
+ return NotImplemented
300
437
  return self._key >= other._key
301
438
 
302
439
  def __gt__(self, other: '_BaseVersion') -> bool:
303
440
  if not isinstance(other, _BaseVersion):
304
- return NotImplemented # type: ignore
441
+ return NotImplemented
305
442
  return self._key > other._key
306
443
 
307
444
  def __ne__(self, other: object) -> bool:
@@ -2394,25 +2531,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
2394
2531
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
2395
2532
 
2396
2533
 
2397
- def update_abstracts(cls, *, force=False):
2534
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
2535
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
2536
+
2537
+ # Stage 1: direct abstract methods
2538
+
2539
+ abstracts = {
2540
+ a
2541
+ # Get items as a list to avoid mutation issues during iteration
2542
+ for a, v in list(cls.__dict__.items())
2543
+ if is_abstract_method(v)
2544
+ }
2545
+
2546
+ # Stage 2: inherited abstract methods
2547
+
2548
+ for base in cls.__bases__:
2549
+ # Get __abstractmethods__ from base if it exists
2550
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
2551
+ continue
2552
+
2553
+ # Iterate over abstract methods in base
2554
+ for key in base_abstracts:
2555
+ # Check if this class has an attribute with this name
2556
+ try:
2557
+ value = getattr(cls, key)
2558
+ except AttributeError:
2559
+ # Attribute not found in this class, skip
2560
+ continue
2561
+
2562
+ # Check if it's still abstract
2563
+ if is_abstract_method(value):
2564
+ abstracts.add(key)
2565
+
2566
+ return frozenset(abstracts)
2567
+
2568
+
2569
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
2398
2570
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
2399
2571
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
2400
2572
  # implementation (especially during testing), and we want to handle both cases.
2401
2573
  return cls
2402
2574
 
2403
- abstracts: ta.Set[str] = set()
2404
-
2405
- for scls in cls.__bases__:
2406
- for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
2407
- value = getattr(cls, name, None)
2408
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
2409
- abstracts.add(name)
2410
-
2411
- for name, value in cls.__dict__.items():
2412
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
2413
- abstracts.add(name)
2414
-
2415
- setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
2575
+ abstracts = compute_abstract_methods(cls)
2576
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
2416
2577
  return cls
2417
2578
 
2418
2579
 
@@ -2466,23 +2627,26 @@ class Abstract:
2466
2627
  super().__init_subclass__(**kwargs)
2467
2628
 
2468
2629
  if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
2469
- ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
2470
-
2471
- seen = set(cls.__dict__)
2472
- for b in cls.__bases__:
2473
- ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
2474
- seen.update(dir(b))
2630
+ if ams := compute_abstract_methods(cls):
2631
+ amd = {
2632
+ a: mcls
2633
+ for mcls in cls.__mro__[::-1]
2634
+ for a in ams
2635
+ if a in mcls.__dict__
2636
+ }
2475
2637
 
2476
- if ams:
2477
2638
  raise AbstractTypeError(
2478
2639
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
2479
2640
  ', '.join(sorted([
2480
2641
  '.'.join([
2481
- *([m] if (m := getattr(c, '__module__')) else []),
2482
- getattr(c, '__qualname__', getattr(c, '__name__')),
2642
+ *([
2643
+ *([m] if (m := getattr(c, '__module__')) else []),
2644
+ getattr(c, '__qualname__', getattr(c, '__name__')),
2645
+ ] if c is not None else '?'),
2483
2646
  a,
2484
2647
  ])
2485
- for a, c in ams.items()
2648
+ for a in ams
2649
+ for c in [amd.get(a)]
2486
2650
  ])),
2487
2651
  )
2488
2652
 
@@ -2507,6 +2671,8 @@ TODO:
2507
2671
  - per-attr repr transform / filter
2508
2672
  - __ne__ ? cases where it still matters
2509
2673
  - ordering ?
2674
+ - repr_filter: ta.Union[ta.Callable[[ta.Any], ta.Optional[str]], ta.Literal['not_none', 'truthy']]] ?
2675
+ - unify repr/repr_fn/repr_filter
2510
2676
  """
2511
2677
 
2512
2678
 
@@ -2524,6 +2690,8 @@ class AttrOps(ta.Generic[T]):
2524
2690
  display: ta.Optional[str] = None,
2525
2691
 
2526
2692
  repr: bool = True, # noqa
2693
+ repr_fn: ta.Optional[ta.Callable[[ta.Any], ta.Optional[str]]] = None,
2694
+
2527
2695
  hash: bool = True, # noqa
2528
2696
  eq: bool = True,
2529
2697
  ) -> None:
@@ -2538,6 +2706,8 @@ class AttrOps(ta.Generic[T]):
2538
2706
  self._display = display
2539
2707
 
2540
2708
  self._repr = repr
2709
+ self._repr_fn = repr_fn
2710
+
2541
2711
  self._hash = hash
2542
2712
  self._eq = eq
2543
2713
 
@@ -2545,21 +2715,30 @@ class AttrOps(ta.Generic[T]):
2545
2715
  def of(
2546
2716
  cls,
2547
2717
  o: ta.Union[
2548
- str,
2549
- ta.Tuple[str, str],
2550
2718
  'AttrOps.Attr',
2719
+ str,
2720
+ ta.Tuple[str, ta.Union[str, ta.Mapping[str, ta.Any]]],
2721
+ ta.Mapping[str, ta.Any],
2551
2722
  ],
2552
2723
  ) -> 'AttrOps.Attr':
2553
2724
  if isinstance(o, AttrOps.Attr):
2554
2725
  return o
2555
2726
  elif isinstance(o, str):
2556
2727
  return cls(o)
2728
+ elif isinstance(o, tuple):
2729
+ name, x = o
2730
+ kw: ta.Mapping[str, ta.Any]
2731
+ if isinstance(x, str):
2732
+ kw = dict(display=x)
2733
+ elif isinstance(x, ta.Mapping):
2734
+ kw = x
2735
+ else:
2736
+ raise TypeError(x)
2737
+ return cls(name, **kw)
2738
+ elif isinstance(o, ta.Mapping):
2739
+ return cls(**o)
2557
2740
  else:
2558
- name, disp = o
2559
- return cls(
2560
- name,
2561
- display=disp,
2562
- )
2741
+ raise TypeError(o)
2563
2742
 
2564
2743
  @property
2565
2744
  def name(self) -> str:
@@ -2577,19 +2756,34 @@ class AttrOps(ta.Generic[T]):
2577
2756
  def eq(self) -> bool:
2578
2757
  return self._eq
2579
2758
 
2759
+ @staticmethod
2760
+ def opt_repr(o: ta.Any) -> ta.Optional[str]:
2761
+ return repr(o) if o is not None else None
2762
+
2763
+ @staticmethod
2764
+ def truthy_repr(o: ta.Any) -> ta.Optional[str]:
2765
+ return repr(o) if o else None
2766
+
2767
+ #
2768
+
2580
2769
  @ta.overload
2581
2770
  def __init__(
2582
2771
  self,
2583
2772
  *attrs: ta.Sequence[ta.Union[
2584
2773
  str,
2585
- ta.Tuple[str, str],
2774
+ ta.Tuple[str, ta.Union[str, ta.Mapping[str, ta.Any]]],
2775
+ ta.Mapping[str, ta.Any],
2586
2776
  Attr,
2587
2777
  ]],
2778
+
2588
2779
  with_module: bool = False,
2589
2780
  use_qualname: bool = False,
2590
2781
  with_id: bool = False,
2782
+ terse: bool = False,
2591
2783
  repr_filter: ta.Optional[ta.Callable[[ta.Any], bool]] = None,
2592
2784
  recursive: bool = False,
2785
+
2786
+ cache_hash: ta.Union[bool, str] = False,
2593
2787
  subtypes_eq: bool = False,
2594
2788
  ) -> None:
2595
2789
  ...
@@ -2599,16 +2793,20 @@ class AttrOps(ta.Generic[T]):
2599
2793
  self,
2600
2794
  attrs_fn: ta.Callable[[T], ta.Tuple[ta.Union[
2601
2795
  ta.Any,
2602
- ta.Tuple[str, ta.Any],
2796
+ ta.Tuple[ta.Any, ta.Union[str, ta.Mapping[str, ta.Any]]],
2603
2797
  Attr,
2604
2798
  ], ...]],
2605
2799
  /,
2606
2800
  *,
2801
+
2607
2802
  with_module: bool = False,
2608
2803
  use_qualname: bool = False,
2609
2804
  with_id: bool = False,
2805
+ terse: bool = False,
2610
2806
  repr_filter: ta.Optional[ta.Callable[[ta.Any], bool]] = None,
2611
2807
  recursive: bool = False,
2808
+
2809
+ cache_hash: ta.Union[bool, str] = False,
2612
2810
  subtypes_eq: bool = False,
2613
2811
  ) -> None:
2614
2812
  ...
@@ -2616,11 +2814,15 @@ class AttrOps(ta.Generic[T]):
2616
2814
  def __init__(
2617
2815
  self,
2618
2816
  *args,
2817
+
2619
2818
  with_module=False,
2620
2819
  use_qualname=False,
2621
2820
  with_id=False,
2821
+ terse=False,
2622
2822
  repr_filter=None,
2623
2823
  recursive=False,
2824
+
2825
+ cache_hash=False,
2624
2826
  subtypes_eq=False,
2625
2827
  ) -> None:
2626
2828
  if args and len(args) == 1 and callable(args[0]):
@@ -2631,8 +2833,11 @@ class AttrOps(ta.Generic[T]):
2631
2833
  self._with_module: bool = with_module
2632
2834
  self._use_qualname: bool = use_qualname
2633
2835
  self._with_id: bool = with_id
2836
+ self._terse: bool = terse
2634
2837
  self._repr_filter: ta.Optional[ta.Callable[[ta.Any], bool]] = repr_filter
2635
2838
  self._recursive: bool = recursive
2839
+
2840
+ self._cache_hash: ta.Union[bool, str] = cache_hash
2636
2841
  self._subtypes_eq: bool = subtypes_eq
2637
2842
 
2638
2843
  @property
@@ -2667,20 +2872,27 @@ class AttrOps(ta.Generic[T]):
2667
2872
 
2668
2873
  attrs: ta.List[AttrOps.Attr] = []
2669
2874
  for o in raw:
2670
- if isinstance(o, AttrOps.Attr):
2671
- attrs.append(o)
2875
+ if isinstance(o, (AttrOps.Attr, ta.Mapping)):
2876
+ attrs.append(AttrOps.Attr.of(o))
2672
2877
  continue
2673
2878
 
2879
+ kw: ta.Mapping[str, ta.Any]
2674
2880
  if isinstance(o, tuple):
2675
- disp, cap, = o
2881
+ cap, x = o
2882
+ if isinstance(x, str):
2883
+ kw = dict(display=x)
2884
+ elif isinstance(x, ta.Mapping):
2885
+ kw = x
2886
+ else:
2887
+ raise TypeError(x)
2676
2888
  else:
2677
- disp, cap = None, o
2889
+ cap, kw = o, {}
2678
2890
 
2679
2891
  path = tuple(rec(cap))
2680
2892
 
2681
2893
  attrs.append(AttrOps.Attr(
2682
2894
  '.'.join(path),
2683
- display=disp,
2895
+ **kw,
2684
2896
  ))
2685
2897
 
2686
2898
  return attrs
@@ -2697,19 +2909,27 @@ class AttrOps(ta.Generic[T]):
2697
2909
  pass
2698
2910
 
2699
2911
  def _repr(o: T) -> str:
2700
- vs = ', '.join(
2701
- f'{a._display}={v!r}' # noqa
2702
- for a in self._attrs
2703
- if a._repr # noqa
2704
- for v in [getattr(o, a._name)] # noqa
2705
- if self._repr_filter is None or self._repr_filter(v)
2706
- )
2912
+ vs: ta.List[str] = []
2913
+ for a in self._attrs:
2914
+ if not a._repr: # noqa
2915
+ continue
2916
+ v = getattr(o, a._name) # noqa
2917
+ if self._repr_filter is not None and not self._repr_filter(v):
2918
+ continue
2919
+ if (rfn := a._repr_fn) is None: # noqa
2920
+ rfn = repr
2921
+ if (vr := rfn(v)) is None:
2922
+ continue
2923
+ if self._terse:
2924
+ vs.append(vr)
2925
+ else:
2926
+ vs.append(f'{a._display}={vr}') # noqa
2707
2927
 
2708
2928
  return (
2709
2929
  f'{o.__class__.__module__ + "." if self._with_module else ""}'
2710
2930
  f'{o.__class__.__qualname__ if self._use_qualname else o.__class__.__name__}'
2711
2931
  f'{("@" + hex(id(o))[2:]) if self._with_id else ""}' # noqa
2712
- f'({vs})'
2932
+ f'({", ".join(vs)})'
2713
2933
  )
2714
2934
 
2715
2935
  if self._recursive:
@@ -2734,6 +2954,8 @@ class AttrOps(ta.Generic[T]):
2734
2954
 
2735
2955
  #
2736
2956
 
2957
+ _DEFAULT_CACHED_HASH_ATTR: ta.ClassVar[str] = '__cached_hash__'
2958
+
2737
2959
  _hash: ta.Callable[[T], int]
2738
2960
 
2739
2961
  @property
@@ -2743,13 +2965,33 @@ class AttrOps(ta.Generic[T]):
2743
2965
  except AttributeError:
2744
2966
  pass
2745
2967
 
2746
- def _hash(o: T) -> int:
2968
+ def _calc_hash(o: T) -> int:
2747
2969
  return hash(tuple(
2748
2970
  getattr(o, a._name) # noqa
2749
2971
  for a in self._attrs
2750
2972
  if a._hash # noqa
2751
2973
  ))
2752
2974
 
2975
+ if (ch := self._cache_hash) is not False:
2976
+ if ch is True:
2977
+ cha = self._DEFAULT_CACHED_HASH_ATTR
2978
+ elif isinstance(ch, str):
2979
+ cha = ch
2980
+ else:
2981
+ raise TypeError(ch)
2982
+
2983
+ def _cached_hash(o: T) -> int:
2984
+ try:
2985
+ return object.__getattribute__(o, cha)
2986
+ except AttributeError:
2987
+ object.__setattr__(o, cha, h := _calc_hash(o))
2988
+ return h
2989
+
2990
+ _hash = _cached_hash
2991
+
2992
+ else:
2993
+ _hash = _calc_hash
2994
+
2753
2995
  self._hash = _hash
2754
2996
  return _hash
2755
2997
 
@@ -2890,6 +3132,62 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
2890
3132
  return _AsyncCachedNullary(fn)
2891
3133
 
2892
3134
 
3135
+ ##
3136
+
3137
+
3138
+ cached_property = functools.cached_property
3139
+
3140
+
3141
+ class _cached_property: # noqa
3142
+ """Backported to pick up https://github.com/python/cpython/commit/056dfc71dce15f81887f0bd6da09d6099d71f979 ."""
3143
+
3144
+ def __init__(self, func):
3145
+ self.func = func
3146
+ self.attrname = None # noqa
3147
+ self.__doc__ = func.__doc__
3148
+ self.__module__ = func.__module__
3149
+
3150
+ _NOT_FOUND = object()
3151
+
3152
+ def __set_name__(self, owner, name):
3153
+ if self.attrname is None:
3154
+ self.attrname = name # noqa
3155
+ elif name != self.attrname:
3156
+ raise TypeError(
3157
+ f'Cannot assign the same cached_property to two different names ({self.attrname!r} and {name!r}).',
3158
+ )
3159
+
3160
+ def __get__(self, instance, owner=None):
3161
+ if instance is None:
3162
+ return self
3163
+ if self.attrname is None:
3164
+ raise TypeError('Cannot use cached_property instance without calling __set_name__ on it.')
3165
+
3166
+ try:
3167
+ cache = instance.__dict__
3168
+ except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
3169
+ raise TypeError(
3170
+ f"No '__dict__' attribute on {type(instance).__name__!r} instance to cache {self.attrname!r} property.",
3171
+ ) from None
3172
+
3173
+ val = cache.get(self.attrname, self._NOT_FOUND)
3174
+
3175
+ if val is self._NOT_FOUND:
3176
+ val = self.func(instance)
3177
+ try:
3178
+ cache[self.attrname] = val
3179
+ except TypeError:
3180
+ raise TypeError(
3181
+ f"The '__dict__' attribute on {type(instance).__name__!r} instance does not support item "
3182
+ f"assignment for caching {self.attrname!r} property.",
3183
+ ) from None
3184
+
3185
+ return val
3186
+
3187
+
3188
+ globals()['cached_property'] = _cached_property
3189
+
3190
+
2893
3191
  ########################################
2894
3192
  # ../../../omlish/lite/check.py
2895
3193
  """
@@ -3429,7 +3727,7 @@ class ExitStacked:
3429
3727
  es.__enter__()
3430
3728
  try:
3431
3729
  self._enter_contexts()
3432
- except Exception: # noqa
3730
+ except BaseException: # noqa
3433
3731
  es.__exit__(*sys.exc_info())
3434
3732
  raise
3435
3733
  return self
@@ -3440,7 +3738,7 @@ class ExitStacked:
3440
3738
  return None
3441
3739
  try:
3442
3740
  self._exit_contexts()
3443
- except Exception: # noqa
3741
+ except BaseException: # noqa
3444
3742
  es.__exit__(*sys.exc_info())
3445
3743
  raise
3446
3744
  return es.__exit__(exc_type, exc_val, exc_tb)
@@ -3488,7 +3786,7 @@ class AsyncExitStacked:
3488
3786
  await es.__aenter__()
3489
3787
  try:
3490
3788
  await self._async_enter_contexts()
3491
- except Exception: # noqa
3789
+ except BaseException: # noqa
3492
3790
  await es.__aexit__(*sys.exc_info())
3493
3791
  raise
3494
3792
  return self
@@ -3499,7 +3797,7 @@ class AsyncExitStacked:
3499
3797
  return None
3500
3798
  try:
3501
3799
  await self._async_exit_contexts()
3502
- except Exception: # noqa
3800
+ except BaseException: # noqa
3503
3801
  await es.__aexit__(*sys.exc_info())
3504
3802
  raise
3505
3803
  return await es.__aexit__(exc_type, exc_val, exc_tb)
@@ -3697,7 +3995,7 @@ def dir_dict(o: ta.Any) -> ta.Dict[str, ta.Any]:
3697
3995
  ##
3698
3996
 
3699
3997
 
3700
- DEFAULT_PYCHARM_VERSION = '242.23726.102'
3998
+ DEFAULT_PYCHARM_VERSION = '252.26199.168'
3701
3999
 
3702
4000
 
3703
4001
  @dc.dataclass(frozen=True)
@@ -3941,6 +4239,12 @@ def format_num_bytes(num_bytes: int) -> str:
3941
4239
 
3942
4240
  ##
3943
4241
  # A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
4242
+ #
4243
+ # Note that this problem doesn't happen at runtime - it happens in mypy:
4244
+ #
4245
+ # mypy <(echo "import typing as ta; MyCallback = ta.NewType('MyCallback', ta.Callable[[], None])")
4246
+ # /dev/fd/11:1:22: error: Argument 2 to NewType(...) must be subclassable (got "Callable[[], None]") [valid-newtype]
4247
+ #
3944
4248
 
3945
4249
 
3946
4250
  @dc.dataclass(frozen=True)
@@ -5483,7 +5787,7 @@ class SpecifierSet(BaseSpecifier):
5483
5787
  if isinstance(other, str):
5484
5788
  other = SpecifierSet(other)
5485
5789
  elif not isinstance(other, SpecifierSet):
5486
- return NotImplemented # type: ignore
5790
+ return NotImplemented
5487
5791
 
5488
5792
  specifier = SpecifierSet()
5489
5793
  specifier._specs = frozenset(self._specs | other._specs)
@@ -5503,6 +5807,7 @@ class SpecifierSet(BaseSpecifier):
5503
5807
  if isinstance(other, (str, Specifier)):
5504
5808
  other = SpecifierSet(str(other))
5505
5809
  elif not isinstance(other, SpecifierSet):
5810
+
5506
5811
  return NotImplemented
5507
5812
 
5508
5813
  return self._specs == other._specs
@@ -5789,6 +6094,7 @@ TODO:
5789
6094
  - pre-run, post-run hooks
5790
6095
  - exitstack?
5791
6096
  - suggestion - difflib.get_close_matches
6097
+ - add_argument_group - group kw on ArgparseKwarg?
5792
6098
  """
5793
6099
 
5794
6100
 
@@ -5799,6 +6105,7 @@ TODO:
5799
6105
  class ArgparseArg:
5800
6106
  args: ta.Sequence[ta.Any]
5801
6107
  kwargs: ta.Mapping[str, ta.Any]
6108
+ group: ta.Optional[str] = None
5802
6109
  dest: ta.Optional[str] = None
5803
6110
 
5804
6111
  def __get__(self, instance, owner=None):
@@ -5808,7 +6115,11 @@ class ArgparseArg:
5808
6115
 
5809
6116
 
5810
6117
  def argparse_arg(*args, **kwargs) -> ArgparseArg:
5811
- return ArgparseArg(args, kwargs)
6118
+ return ArgparseArg(
6119
+ args=args,
6120
+ group=kwargs.pop('group', None),
6121
+ kwargs=kwargs,
6122
+ )
5812
6123
 
5813
6124
 
5814
6125
  def argparse_arg_(*args, **kwargs) -> ta.Any:
@@ -5978,6 +6289,10 @@ class ArgparseCli:
5978
6289
  subparser.set_defaults(_cmd=obj)
5979
6290
 
5980
6291
  elif isinstance(obj, ArgparseArg):
6292
+ if obj.group is not None:
6293
+ # FIXME: add_argument_group
6294
+ raise NotImplementedError
6295
+
5981
6296
  if att in anns:
5982
6297
  ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
5983
6298
  obj.kwargs = {**ann_kwargs, **obj.kwargs}
@@ -6023,7 +6338,7 @@ class ArgparseCli:
6023
6338
 
6024
6339
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
6025
6340
  msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
6026
- if (parser := self.get_parser()).exit_on_error:
6341
+ if (parser := self.get_parser()).exit_on_error: # noqa
6027
6342
  parser.error(msg)
6028
6343
  else:
6029
6344
  raise argparse.ArgumentError(None, msg)
@@ -6043,7 +6358,10 @@ class ArgparseCli:
6043
6358
  return fn()
6044
6359
 
6045
6360
  def cli_run_and_exit(self) -> ta.NoReturn:
6046
- sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
6361
+ rc = self.cli_run()
6362
+ if not isinstance(rc, int):
6363
+ rc = 0
6364
+ raise SystemExit(rc)
6047
6365
 
6048
6366
  def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
6049
6367
  if exit:
@@ -7228,8 +7546,6 @@ class _JustMaybe(_Maybe[T]):
7228
7546
  __slots__ = ('_v', '_hash')
7229
7547
 
7230
7548
  def __init__(self, v: T) -> None:
7231
- super().__init__()
7232
-
7233
7549
  self._v = v
7234
7550
 
7235
7551
  @property
@@ -7287,6 +7603,13 @@ class _EmptyMaybe(_Maybe[T]):
7287
7603
  Maybe._empty = _EmptyMaybe() # noqa
7288
7604
 
7289
7605
 
7606
+ ##
7607
+
7608
+
7609
+ setattr(Maybe, 'just', _JustMaybe) # noqa
7610
+ setattr(Maybe, 'empty', functools.partial(operator.attrgetter('_empty'), Maybe))
7611
+
7612
+
7290
7613
  ########################################
7291
7614
  # ../../../omlish/lite/runtime.py
7292
7615
 
@@ -11889,6 +12212,10 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
11889
12212
  class BaseSubprocesses(Abstract):
11890
12213
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[LoggerLike]] = None
11891
12214
 
12215
+ PIPE: ta.ClassVar[int] = subprocess.PIPE
12216
+ STDOUT: ta.ClassVar[int] = subprocess.STDOUT
12217
+ DEVNULL: ta.ClassVar[int] = subprocess.DEVNULL
12218
+
11892
12219
  def __init__(
11893
12220
  self,
11894
12221
  *,
@@ -16826,7 +17153,7 @@ class MainCli(ArgparseCli):
16826
17153
 
16827
17154
 
16828
17155
  def _main() -> None:
16829
- sys.exit(asyncio.run(MainCli().async_cli_run()))
17156
+ raise SystemExit(asyncio.run(MainCli().async_cli_run()))
16830
17157
 
16831
17158
 
16832
17159
  if __name__ == '__main__':