omdev 0.0.0.dev420__py3-none-any.whl → 0.0.0.dev422__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.
- omdev/cache/compute/storage.py +3 -1
- omdev/ci/cache.py +3 -2
- omdev/ci/docker/buildcaching.py +3 -1
- omdev/ci/docker/cache.py +2 -1
- omdev/ci/docker/cacheserved/manifests.py +2 -2
- omdev/ci/docker/imagepulling.py +2 -1
- omdev/ci/docker/repositories.py +2 -1
- omdev/ci/github/api/clients.py +4 -3
- omdev/clipboard/clipboard.py +1 -1
- omdev/cmake.py +2 -1
- omdev/dataserver/handlers.py +3 -2
- omdev/dataserver/targets.py +2 -2
- omdev/interp/providers/base.py +3 -2
- omdev/interp/pyenv/install.py +2 -1
- omdev/manifests/_dumping.py +427 -127
- omdev/oci/data.py +2 -2
- omdev/oci/datarefs.py +2 -2
- omdev/oci/media.py +2 -2
- omdev/oci/repositories.py +3 -2
- omdev/pyproject/pkg.py +2 -1
- omdev/scripts/ci.py +3969 -3667
- omdev/scripts/interp.py +1000 -896
- omdev/scripts/pyproject.py +2384 -2085
- omdev/tools/git/messages.py +2 -2
- {omdev-0.0.0.dev420.dist-info → omdev-0.0.0.dev422.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev420.dist-info → omdev-0.0.0.dev422.dist-info}/RECORD +30 -30
- {omdev-0.0.0.dev420.dist-info → omdev-0.0.0.dev422.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev420.dist-info → omdev-0.0.0.dev422.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev420.dist-info → omdev-0.0.0.dev422.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev420.dist-info → omdev-0.0.0.dev422.dist-info}/top_level.txt +0 -0
omdev/scripts/interp.py
CHANGED
@@ -72,12 +72,6 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
|
|
72
72
|
CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
|
73
73
|
CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
|
74
74
|
|
75
|
-
# ../../omlish/lite/maybes.py
|
76
|
-
U = ta.TypeVar('U')
|
77
|
-
|
78
|
-
# ../../omlish/lite/timeouts.py
|
79
|
-
TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
|
80
|
-
|
81
75
|
# ../packaging/specifiers.py
|
82
76
|
UnparsedVersion = ta.Union['Version', str]
|
83
77
|
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
@@ -86,6 +80,12 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
|
|
86
80
|
# ../../omlish/argparse/cli.py
|
87
81
|
ArgparseCmdFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
|
88
82
|
|
83
|
+
# ../../omlish/lite/maybes.py
|
84
|
+
U = ta.TypeVar('U')
|
85
|
+
|
86
|
+
# ../../omlish/lite/timeouts.py
|
87
|
+
TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
|
88
|
+
|
89
89
|
# ../../omlish/asyncs/asyncio/timeouts.py
|
90
90
|
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
91
91
|
|
@@ -506,6 +506,126 @@ def canonicalize_version(
|
|
506
506
|
return ''.join(parts)
|
507
507
|
|
508
508
|
|
509
|
+
########################################
|
510
|
+
# ../../../omlish/lite/abstract.py
|
511
|
+
|
512
|
+
|
513
|
+
##
|
514
|
+
|
515
|
+
|
516
|
+
_ABSTRACT_METHODS_ATTR = '__abstractmethods__'
|
517
|
+
_IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
|
518
|
+
|
519
|
+
|
520
|
+
def is_abstract_method(obj: ta.Any) -> bool:
|
521
|
+
return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
|
522
|
+
|
523
|
+
|
524
|
+
def update_abstracts(cls, *, force=False):
|
525
|
+
if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
|
526
|
+
# Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
|
527
|
+
# implementation (especially during testing), and we want to handle both cases.
|
528
|
+
return cls
|
529
|
+
|
530
|
+
abstracts: ta.Set[str] = set()
|
531
|
+
|
532
|
+
for scls in cls.__bases__:
|
533
|
+
for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
|
534
|
+
value = getattr(cls, name, None)
|
535
|
+
if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
|
536
|
+
abstracts.add(name)
|
537
|
+
|
538
|
+
for name, value in cls.__dict__.items():
|
539
|
+
if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
|
540
|
+
abstracts.add(name)
|
541
|
+
|
542
|
+
setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
|
543
|
+
return cls
|
544
|
+
|
545
|
+
|
546
|
+
#
|
547
|
+
|
548
|
+
|
549
|
+
class AbstractTypeError(TypeError):
|
550
|
+
pass
|
551
|
+
|
552
|
+
|
553
|
+
_FORCE_ABSTRACT_ATTR = '__forceabstract__'
|
554
|
+
|
555
|
+
|
556
|
+
class Abstract:
|
557
|
+
"""
|
558
|
+
Different from, but interoperable with, abc.ABC / abc.ABCMeta:
|
559
|
+
|
560
|
+
- This raises AbstractTypeError during class creation, not instance instantiation - unless Abstract or abc.ABC are
|
561
|
+
explicitly present in the class's direct bases.
|
562
|
+
- This will forbid instantiation of classes with Abstract in their direct bases even if there are no
|
563
|
+
abstractmethods left on the class.
|
564
|
+
- This is a mixin, not a metaclass.
|
565
|
+
- As it is not an ABCMeta, this does not support virtual base classes. As a result, operations like `isinstance`
|
566
|
+
and `issubclass` are ~7x faster.
|
567
|
+
- It additionally enforces a base class order of (Abstract, abc.ABC) to preemptively prevent common mro conflicts.
|
568
|
+
|
569
|
+
If not mixed-in with an ABCMeta, it will update __abstractmethods__ itself.
|
570
|
+
"""
|
571
|
+
|
572
|
+
__slots__ = ()
|
573
|
+
|
574
|
+
__abstractmethods__: ta.ClassVar[ta.FrozenSet[str]] = frozenset()
|
575
|
+
|
576
|
+
#
|
577
|
+
|
578
|
+
def __forceabstract__(self):
|
579
|
+
raise TypeError
|
580
|
+
|
581
|
+
# This is done manually, rather than through @abc.abstractmethod, to mask it from static analysis.
|
582
|
+
setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
|
583
|
+
|
584
|
+
#
|
585
|
+
|
586
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
587
|
+
setattr(
|
588
|
+
cls,
|
589
|
+
_FORCE_ABSTRACT_ATTR,
|
590
|
+
getattr(Abstract, _FORCE_ABSTRACT_ATTR) if Abstract in cls.__bases__ else False,
|
591
|
+
)
|
592
|
+
|
593
|
+
super().__init_subclass__(**kwargs)
|
594
|
+
|
595
|
+
if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
|
596
|
+
ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
|
597
|
+
|
598
|
+
seen = set(cls.__dict__)
|
599
|
+
for b in cls.__bases__:
|
600
|
+
ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
|
601
|
+
seen.update(dir(b))
|
602
|
+
|
603
|
+
if ams:
|
604
|
+
raise AbstractTypeError(
|
605
|
+
f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
|
606
|
+
', '.join(sorted([
|
607
|
+
'.'.join([
|
608
|
+
*([m] if (m := getattr(c, '__module__')) else []),
|
609
|
+
getattr(c, '__qualname__', getattr(c, '__name__')),
|
610
|
+
a,
|
611
|
+
])
|
612
|
+
for a, c in ams.items()
|
613
|
+
])),
|
614
|
+
)
|
615
|
+
|
616
|
+
xbi = (Abstract, abc.ABC) # , ta.Generic ?
|
617
|
+
bis = [(cls.__bases__.index(b), b) for b in xbi if b in cls.__bases__]
|
618
|
+
if bis != sorted(bis):
|
619
|
+
raise TypeError(
|
620
|
+
f'Abstract subclass {cls.__name__} must have proper base class order of '
|
621
|
+
f'({", ".join(getattr(b, "__name__") for b in xbi)}), got: '
|
622
|
+
f'({", ".join(getattr(b, "__name__") for _, b in sorted(bis))})',
|
623
|
+
)
|
624
|
+
|
625
|
+
if not isinstance(cls, abc.ABCMeta):
|
626
|
+
update_abstracts(cls, force=True)
|
627
|
+
|
628
|
+
|
509
629
|
########################################
|
510
630
|
# ../../../omlish/lite/cached.py
|
511
631
|
|
@@ -1086,335 +1206,116 @@ json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON
|
|
1086
1206
|
|
1087
1207
|
|
1088
1208
|
########################################
|
1089
|
-
# ../../../omlish/lite/
|
1209
|
+
# ../../../omlish/lite/reflect.py
|
1090
1210
|
|
1091
1211
|
|
1092
1212
|
##
|
1093
1213
|
|
1094
1214
|
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1215
|
+
_GENERIC_ALIAS_TYPES = (
|
1216
|
+
ta._GenericAlias, # type: ignore # noqa
|
1217
|
+
*([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
|
1218
|
+
)
|
1099
1219
|
|
1100
|
-
#
|
1101
1220
|
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1221
|
+
def is_generic_alias(obj: ta.Any, *, origin: ta.Any = None) -> bool:
|
1222
|
+
return (
|
1223
|
+
isinstance(obj, _GENERIC_ALIAS_TYPES) and
|
1224
|
+
(origin is None or ta.get_origin(obj) is origin)
|
1225
|
+
)
|
1106
1226
|
|
1107
|
-
@abc.abstractmethod
|
1108
|
-
def must(self) -> T:
|
1109
|
-
raise NotImplementedError
|
1110
1227
|
|
1111
|
-
|
1228
|
+
is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
|
1112
1229
|
|
1113
|
-
@abc.abstractmethod
|
1114
|
-
def __repr__(self) -> str:
|
1115
|
-
raise NotImplementedError
|
1116
1230
|
|
1117
|
-
|
1118
|
-
def __hash__(self) -> int:
|
1119
|
-
raise NotImplementedError
|
1231
|
+
##
|
1120
1232
|
|
1121
|
-
@abc.abstractmethod
|
1122
|
-
def __eq__(self, other) -> bool:
|
1123
|
-
raise NotImplementedError
|
1124
1233
|
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1234
|
+
_UNION_ALIAS_ORIGINS = frozenset([
|
1235
|
+
ta.get_origin(ta.Optional[int]),
|
1236
|
+
*(
|
1237
|
+
[
|
1238
|
+
ta.get_origin(int | None),
|
1239
|
+
ta.get_origin(getattr(ta, 'TypeVar')('_T') | None),
|
1240
|
+
] if sys.version_info >= (3, 10) else ()
|
1241
|
+
),
|
1242
|
+
])
|
1128
1243
|
|
1129
|
-
#
|
1130
1244
|
|
1131
|
-
|
1132
|
-
|
1133
|
-
return not (self == other)
|
1245
|
+
def is_union_alias(obj: ta.Any) -> bool:
|
1246
|
+
return ta.get_origin(obj) in _UNION_ALIAS_ORIGINS
|
1134
1247
|
|
1135
|
-
@ta.final
|
1136
|
-
def __iter__(self) -> ta.Iterator[T]:
|
1137
|
-
if self.present:
|
1138
|
-
yield self.must()
|
1139
1248
|
|
1140
|
-
|
1141
|
-
def __bool__(self) -> ta.NoReturn:
|
1142
|
-
raise TypeError
|
1249
|
+
#
|
1143
1250
|
|
1144
|
-
#
|
1145
1251
|
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1252
|
+
def is_optional_alias(spec: ta.Any) -> bool:
|
1253
|
+
return (
|
1254
|
+
is_union_alias(spec) and
|
1255
|
+
len(ta.get_args(spec)) == 2 and
|
1256
|
+
any(a in (None, type(None)) for a in ta.get_args(spec))
|
1257
|
+
)
|
1150
1258
|
|
1151
|
-
@ta.final
|
1152
|
-
def filter(self, predicate: ta.Callable[[T], bool]) -> 'Maybe[T]':
|
1153
|
-
if self.present and predicate(self.must()):
|
1154
|
-
return self
|
1155
|
-
else:
|
1156
|
-
return Maybe.empty()
|
1157
1259
|
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
return Maybe.just(mapper(self.must()))
|
1162
|
-
else:
|
1163
|
-
return Maybe.empty()
|
1260
|
+
def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
|
1261
|
+
[it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
|
1262
|
+
return it
|
1164
1263
|
|
1165
|
-
@ta.final
|
1166
|
-
def flat_map(self, mapper: ta.Callable[[T], 'Maybe[U]']) -> 'Maybe[U]':
|
1167
|
-
if self.present:
|
1168
|
-
if not isinstance(v := mapper(self.must()), Maybe):
|
1169
|
-
raise TypeError(v)
|
1170
|
-
return v
|
1171
|
-
else:
|
1172
|
-
return Maybe.empty()
|
1173
1264
|
|
1174
|
-
|
1175
|
-
def or_else(self, other: ta.Union[T, U]) -> ta.Union[T, U]:
|
1176
|
-
if self.present:
|
1177
|
-
return self.must()
|
1178
|
-
else:
|
1179
|
-
return other
|
1265
|
+
##
|
1180
1266
|
|
1181
|
-
@ta.final
|
1182
|
-
def or_else_get(self, supplier: ta.Callable[[], ta.Union[T, U]]) -> ta.Union[T, U]:
|
1183
|
-
if self.present:
|
1184
|
-
return self.must()
|
1185
|
-
else:
|
1186
|
-
return supplier()
|
1187
1267
|
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1268
|
+
def is_new_type(spec: ta.Any) -> bool:
|
1269
|
+
if isinstance(ta.NewType, type):
|
1270
|
+
return isinstance(spec, ta.NewType)
|
1271
|
+
else:
|
1272
|
+
# Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
|
1273
|
+
return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
|
1194
1274
|
|
1195
|
-
#
|
1196
1275
|
|
1197
|
-
|
1198
|
-
|
1199
|
-
if v is not None:
|
1200
|
-
return cls.just(v)
|
1201
|
-
else:
|
1202
|
-
return cls.empty()
|
1276
|
+
def get_new_type_supertype(spec: ta.Any) -> ta.Any:
|
1277
|
+
return spec.__supertype__
|
1203
1278
|
|
1204
|
-
@classmethod
|
1205
|
-
def just(cls, v: T) -> 'Maybe[T]':
|
1206
|
-
return _JustMaybe(v)
|
1207
1279
|
|
1208
|
-
|
1280
|
+
##
|
1209
1281
|
|
1210
|
-
@classmethod
|
1211
|
-
def empty(cls) -> 'Maybe[T]':
|
1212
|
-
return Maybe._empty
|
1213
1282
|
|
1283
|
+
def is_literal_type(spec: ta.Any) -> bool:
|
1284
|
+
if hasattr(ta, '_LiteralGenericAlias'):
|
1285
|
+
return isinstance(spec, ta._LiteralGenericAlias) # noqa
|
1286
|
+
else:
|
1287
|
+
return (
|
1288
|
+
isinstance(spec, ta._GenericAlias) and # type: ignore # noqa
|
1289
|
+
spec.__origin__ is ta.Literal
|
1290
|
+
)
|
1214
1291
|
|
1215
|
-
##
|
1216
1292
|
|
1293
|
+
def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
|
1294
|
+
return spec.__args__
|
1217
1295
|
|
1218
|
-
class _Maybe(Maybe[T], abc.ABC):
|
1219
|
-
def __lt__(self, other):
|
1220
|
-
if not isinstance(other, _Maybe):
|
1221
|
-
return NotImplemented
|
1222
|
-
sp = self.present
|
1223
|
-
op = other.present
|
1224
|
-
if self.present and other.present:
|
1225
|
-
return self.must() < other.must()
|
1226
|
-
else:
|
1227
|
-
return op and not sp
|
1228
1296
|
|
1297
|
+
########################################
|
1298
|
+
# ../../../omlish/lite/strings.py
|
1229
1299
|
|
1230
|
-
class _JustMaybe(_Maybe[T]):
|
1231
|
-
__slots__ = ('_v', '_hash')
|
1232
1300
|
|
1233
|
-
|
1234
|
-
super().__init__()
|
1301
|
+
##
|
1235
1302
|
|
1236
|
-
self._v = v
|
1237
1303
|
|
1238
|
-
|
1239
|
-
|
1240
|
-
return
|
1304
|
+
def camel_case(name: str, *, lower: bool = False) -> str:
|
1305
|
+
if not name:
|
1306
|
+
return ''
|
1307
|
+
s = ''.join(map(str.capitalize, name.split('_'))) # noqa
|
1308
|
+
if lower:
|
1309
|
+
s = s[0].lower() + s[1:]
|
1310
|
+
return s
|
1241
1311
|
|
1242
|
-
def must(self) -> T:
|
1243
|
-
return self._v
|
1244
1312
|
|
1245
|
-
|
1313
|
+
def snake_case(name: str) -> str:
|
1314
|
+
uppers: list[int | None] = [i for i, c in enumerate(name) if c.isupper()]
|
1315
|
+
return '_'.join([name[l:r].lower() for l, r in zip([None, *uppers], [*uppers, None])]).strip('_')
|
1246
1316
|
|
1247
|
-
def __repr__(self) -> str:
|
1248
|
-
return f'just({self._v!r})'
|
1249
1317
|
|
1250
|
-
|
1251
|
-
|
1252
|
-
def __hash__(self) -> int:
|
1253
|
-
try:
|
1254
|
-
return self._hash
|
1255
|
-
except AttributeError:
|
1256
|
-
pass
|
1257
|
-
h = self._hash = hash((_JustMaybe, self._v))
|
1258
|
-
return h
|
1259
|
-
|
1260
|
-
def __eq__(self, other):
|
1261
|
-
return (
|
1262
|
-
self.__class__ is other.__class__ and
|
1263
|
-
self._v == other._v # noqa
|
1264
|
-
)
|
1265
|
-
|
1266
|
-
|
1267
|
-
class _EmptyMaybe(_Maybe[T]):
|
1268
|
-
__slots__ = ()
|
1269
|
-
|
1270
|
-
@property
|
1271
|
-
def present(self) -> bool:
|
1272
|
-
return False
|
1273
|
-
|
1274
|
-
def must(self) -> T:
|
1275
|
-
raise Maybe.ValueNotPresentError
|
1276
|
-
|
1277
|
-
#
|
1278
|
-
|
1279
|
-
def __repr__(self) -> str:
|
1280
|
-
return 'empty()'
|
1281
|
-
|
1282
|
-
def __hash__(self) -> int:
|
1283
|
-
return hash(_EmptyMaybe)
|
1284
|
-
|
1285
|
-
def __eq__(self, other):
|
1286
|
-
return self.__class__ is other.__class__
|
1287
|
-
|
1288
|
-
|
1289
|
-
Maybe._empty = _EmptyMaybe() # noqa
|
1290
|
-
|
1291
|
-
|
1292
|
-
########################################
|
1293
|
-
# ../../../omlish/lite/reflect.py
|
1294
|
-
|
1295
|
-
|
1296
|
-
##
|
1297
|
-
|
1298
|
-
|
1299
|
-
_GENERIC_ALIAS_TYPES = (
|
1300
|
-
ta._GenericAlias, # type: ignore # noqa
|
1301
|
-
*([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
|
1302
|
-
)
|
1303
|
-
|
1304
|
-
|
1305
|
-
def is_generic_alias(obj: ta.Any, *, origin: ta.Any = None) -> bool:
|
1306
|
-
return (
|
1307
|
-
isinstance(obj, _GENERIC_ALIAS_TYPES) and
|
1308
|
-
(origin is None or ta.get_origin(obj) is origin)
|
1309
|
-
)
|
1310
|
-
|
1311
|
-
|
1312
|
-
is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
|
1313
|
-
|
1314
|
-
|
1315
|
-
##
|
1316
|
-
|
1317
|
-
|
1318
|
-
_UNION_ALIAS_ORIGINS = frozenset([
|
1319
|
-
ta.get_origin(ta.Optional[int]),
|
1320
|
-
*(
|
1321
|
-
[
|
1322
|
-
ta.get_origin(int | None),
|
1323
|
-
ta.get_origin(getattr(ta, 'TypeVar')('_T') | None),
|
1324
|
-
] if sys.version_info >= (3, 10) else ()
|
1325
|
-
),
|
1326
|
-
])
|
1327
|
-
|
1328
|
-
|
1329
|
-
def is_union_alias(obj: ta.Any) -> bool:
|
1330
|
-
return ta.get_origin(obj) in _UNION_ALIAS_ORIGINS
|
1331
|
-
|
1332
|
-
|
1333
|
-
#
|
1334
|
-
|
1335
|
-
|
1336
|
-
def is_optional_alias(spec: ta.Any) -> bool:
|
1337
|
-
return (
|
1338
|
-
is_union_alias(spec) and
|
1339
|
-
len(ta.get_args(spec)) == 2 and
|
1340
|
-
any(a in (None, type(None)) for a in ta.get_args(spec))
|
1341
|
-
)
|
1342
|
-
|
1343
|
-
|
1344
|
-
def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
|
1345
|
-
[it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
|
1346
|
-
return it
|
1347
|
-
|
1348
|
-
|
1349
|
-
##
|
1350
|
-
|
1351
|
-
|
1352
|
-
def is_new_type(spec: ta.Any) -> bool:
|
1353
|
-
if isinstance(ta.NewType, type):
|
1354
|
-
return isinstance(spec, ta.NewType)
|
1355
|
-
else:
|
1356
|
-
# Before https://github.com/python/cpython/commit/c2f33dfc83ab270412bf243fb21f724037effa1a
|
1357
|
-
return isinstance(spec, types.FunctionType) and spec.__code__ is ta.NewType.__code__.co_consts[1] # type: ignore # noqa
|
1358
|
-
|
1359
|
-
|
1360
|
-
def get_new_type_supertype(spec: ta.Any) -> ta.Any:
|
1361
|
-
return spec.__supertype__
|
1362
|
-
|
1363
|
-
|
1364
|
-
##
|
1365
|
-
|
1366
|
-
|
1367
|
-
def is_literal_type(spec: ta.Any) -> bool:
|
1368
|
-
if hasattr(ta, '_LiteralGenericAlias'):
|
1369
|
-
return isinstance(spec, ta._LiteralGenericAlias) # noqa
|
1370
|
-
else:
|
1371
|
-
return (
|
1372
|
-
isinstance(spec, ta._GenericAlias) and # type: ignore # noqa
|
1373
|
-
spec.__origin__ is ta.Literal
|
1374
|
-
)
|
1375
|
-
|
1376
|
-
|
1377
|
-
def get_literal_type_args(spec: ta.Any) -> ta.Iterable[ta.Any]:
|
1378
|
-
return spec.__args__
|
1379
|
-
|
1380
|
-
|
1381
|
-
##
|
1382
|
-
|
1383
|
-
|
1384
|
-
def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
|
1385
|
-
seen = set()
|
1386
|
-
todo = list(reversed(cls.__subclasses__()))
|
1387
|
-
while todo:
|
1388
|
-
cur = todo.pop()
|
1389
|
-
if cur in seen:
|
1390
|
-
continue
|
1391
|
-
seen.add(cur)
|
1392
|
-
yield cur
|
1393
|
-
todo.extend(reversed(cur.__subclasses__()))
|
1394
|
-
|
1395
|
-
|
1396
|
-
########################################
|
1397
|
-
# ../../../omlish/lite/strings.py
|
1398
|
-
|
1399
|
-
|
1400
|
-
##
|
1401
|
-
|
1402
|
-
|
1403
|
-
def camel_case(name: str, *, lower: bool = False) -> str:
|
1404
|
-
if not name:
|
1405
|
-
return ''
|
1406
|
-
s = ''.join(map(str.capitalize, name.split('_'))) # noqa
|
1407
|
-
if lower:
|
1408
|
-
s = s[0].lower() + s[1:]
|
1409
|
-
return s
|
1410
|
-
|
1411
|
-
|
1412
|
-
def snake_case(name: str) -> str:
|
1413
|
-
uppers: list[int | None] = [i for i, c in enumerate(name) if c.isupper()]
|
1414
|
-
return '_'.join([name[l:r].lower() for l, r in zip([None, *uppers], [*uppers, None])]).strip('_')
|
1415
|
-
|
1416
|
-
|
1417
|
-
##
|
1318
|
+
##
|
1418
1319
|
|
1419
1320
|
|
1420
1321
|
def is_dunder(name: str) -> bool:
|
@@ -1492,277 +1393,78 @@ def format_num_bytes(num_bytes: int) -> str:
|
|
1492
1393
|
|
1493
1394
|
|
1494
1395
|
########################################
|
1495
|
-
# ../../../omlish/
|
1496
|
-
"""
|
1497
|
-
TODO:
|
1498
|
-
- Event (/ Predicate)
|
1499
|
-
"""
|
1396
|
+
# ../../../omlish/logs/filters.py
|
1500
1397
|
|
1501
1398
|
|
1502
1399
|
##
|
1503
1400
|
|
1504
1401
|
|
1505
|
-
class
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1402
|
+
class TidLogFilter(logging.Filter):
|
1403
|
+
def filter(self, record):
|
1404
|
+
# FIXME: handle better - missing from wasm and cosmos
|
1405
|
+
if hasattr(threading, 'get_native_id'):
|
1406
|
+
record.tid = threading.get_native_id()
|
1407
|
+
else:
|
1408
|
+
record.tid = '?'
|
1409
|
+
return True
|
1512
1410
|
|
1513
|
-
@abc.abstractmethod
|
1514
|
-
def expired(self) -> bool:
|
1515
|
-
"""Return whether or not this timeout has expired."""
|
1516
1411
|
|
1517
|
-
|
1412
|
+
########################################
|
1413
|
+
# ../../../omlish/logs/proxy.py
|
1518
1414
|
|
1519
|
-
@abc.abstractmethod
|
1520
|
-
def remaining(self) -> float:
|
1521
|
-
"""Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
|
1522
1415
|
|
1523
|
-
|
1416
|
+
##
|
1524
1417
|
|
1525
|
-
@abc.abstractmethod
|
1526
|
-
def __call__(self) -> float:
|
1527
|
-
"""Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
|
1528
1418
|
|
1529
|
-
|
1419
|
+
class ProxyLogFilterer(logging.Filterer):
|
1420
|
+
def __init__(self, underlying: logging.Filterer) -> None: # noqa
|
1421
|
+
self._underlying = underlying
|
1530
1422
|
|
1531
|
-
@
|
1532
|
-
def
|
1533
|
-
|
1423
|
+
@property
|
1424
|
+
def underlying(self) -> logging.Filterer:
|
1425
|
+
return self._underlying
|
1534
1426
|
|
1535
|
-
|
1427
|
+
@property
|
1428
|
+
def filters(self):
|
1429
|
+
return self._underlying.filters
|
1536
1430
|
|
1537
|
-
|
1431
|
+
@filters.setter
|
1432
|
+
def filters(self, filters):
|
1433
|
+
self._underlying.filters = filters
|
1538
1434
|
|
1539
|
-
|
1540
|
-
|
1541
|
-
return time.monotonic()
|
1435
|
+
def addFilter(self, filter): # noqa
|
1436
|
+
self._underlying.addFilter(filter)
|
1542
1437
|
|
1543
|
-
#
|
1438
|
+
def removeFilter(self, filter): # noqa
|
1439
|
+
self._underlying.removeFilter(filter)
|
1544
1440
|
|
1545
|
-
|
1546
|
-
|
1547
|
-
raise TypeError
|
1441
|
+
def filter(self, record):
|
1442
|
+
return self._underlying.filter(record)
|
1548
1443
|
|
1549
|
-
class _NOT_SPECIFIED: # noqa
|
1550
|
-
def __new__(cls, *args, **kwargs): # noqa
|
1551
|
-
raise TypeError
|
1552
1444
|
|
1553
|
-
|
1554
|
-
def
|
1555
|
-
|
1556
|
-
obj: TimeoutLike,
|
1557
|
-
default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
|
1558
|
-
) -> 'Timeout':
|
1559
|
-
if obj is None:
|
1560
|
-
return InfiniteTimeout()
|
1445
|
+
class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
|
1446
|
+
def __init__(self, underlying: logging.Handler) -> None: # noqa
|
1447
|
+
ProxyLogFilterer.__init__(self, underlying)
|
1561
1448
|
|
1562
|
-
|
1563
|
-
return obj
|
1449
|
+
_underlying: logging.Handler
|
1564
1450
|
|
1565
|
-
|
1566
|
-
|
1451
|
+
@property
|
1452
|
+
def underlying(self) -> logging.Handler:
|
1453
|
+
return self._underlying
|
1567
1454
|
|
1568
|
-
|
1569
|
-
|
1455
|
+
def get_name(self):
|
1456
|
+
return self._underlying.get_name()
|
1570
1457
|
|
1571
|
-
|
1572
|
-
|
1573
|
-
raise RuntimeError('Must specify a default timeout')
|
1458
|
+
def set_name(self, name):
|
1459
|
+
self._underlying.set_name(name)
|
1574
1460
|
|
1575
|
-
|
1576
|
-
|
1461
|
+
@property
|
1462
|
+
def name(self): # type: ignore[override]
|
1463
|
+
return self._underlying.name
|
1577
1464
|
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
@classmethod
|
1582
|
-
def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
|
1583
|
-
return DeadlineTimeout(deadline)
|
1584
|
-
|
1585
|
-
@classmethod
|
1586
|
-
def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
|
1587
|
-
return PredicateTimeout(expired_fn)
|
1588
|
-
|
1589
|
-
|
1590
|
-
class DeadlineTimeout(Timeout):
|
1591
|
-
def __init__(
|
1592
|
-
self,
|
1593
|
-
deadline: float,
|
1594
|
-
exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
|
1595
|
-
) -> None:
|
1596
|
-
super().__init__()
|
1597
|
-
|
1598
|
-
self.deadline = deadline
|
1599
|
-
self.exc = exc
|
1600
|
-
|
1601
|
-
@property
|
1602
|
-
def can_expire(self) -> bool:
|
1603
|
-
return True
|
1604
|
-
|
1605
|
-
def expired(self) -> bool:
|
1606
|
-
return not (self.remaining() > 0)
|
1607
|
-
|
1608
|
-
def remaining(self) -> float:
|
1609
|
-
return self.deadline - self._now()
|
1610
|
-
|
1611
|
-
def __call__(self) -> float:
|
1612
|
-
if (rem := self.remaining()) > 0:
|
1613
|
-
return rem
|
1614
|
-
raise self.exc
|
1615
|
-
|
1616
|
-
def or_(self, o: ta.Any) -> ta.Any:
|
1617
|
-
return self()
|
1618
|
-
|
1619
|
-
|
1620
|
-
class InfiniteTimeout(Timeout):
|
1621
|
-
@property
|
1622
|
-
def can_expire(self) -> bool:
|
1623
|
-
return False
|
1624
|
-
|
1625
|
-
def expired(self) -> bool:
|
1626
|
-
return False
|
1627
|
-
|
1628
|
-
def remaining(self) -> float:
|
1629
|
-
return float('inf')
|
1630
|
-
|
1631
|
-
def __call__(self) -> float:
|
1632
|
-
return float('inf')
|
1633
|
-
|
1634
|
-
def or_(self, o: ta.Any) -> ta.Any:
|
1635
|
-
return o
|
1636
|
-
|
1637
|
-
|
1638
|
-
class CompositeTimeout(Timeout):
|
1639
|
-
def __init__(self, *children: Timeout) -> None:
|
1640
|
-
super().__init__()
|
1641
|
-
|
1642
|
-
self.children = children
|
1643
|
-
|
1644
|
-
@property
|
1645
|
-
def can_expire(self) -> bool:
|
1646
|
-
return any(c.can_expire for c in self.children)
|
1647
|
-
|
1648
|
-
def expired(self) -> bool:
|
1649
|
-
return any(c.expired() for c in self.children)
|
1650
|
-
|
1651
|
-
def remaining(self) -> float:
|
1652
|
-
return min(c.remaining() for c in self.children)
|
1653
|
-
|
1654
|
-
def __call__(self) -> float:
|
1655
|
-
return min(c() for c in self.children)
|
1656
|
-
|
1657
|
-
def or_(self, o: ta.Any) -> ta.Any:
|
1658
|
-
if self.can_expire:
|
1659
|
-
return self()
|
1660
|
-
return o
|
1661
|
-
|
1662
|
-
|
1663
|
-
class PredicateTimeout(Timeout):
|
1664
|
-
def __init__(
|
1665
|
-
self,
|
1666
|
-
expired_fn: ta.Callable[[], bool],
|
1667
|
-
exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
|
1668
|
-
) -> None:
|
1669
|
-
super().__init__()
|
1670
|
-
|
1671
|
-
self.expired_fn = expired_fn
|
1672
|
-
self.exc = exc
|
1673
|
-
|
1674
|
-
@property
|
1675
|
-
def can_expire(self) -> bool:
|
1676
|
-
return True
|
1677
|
-
|
1678
|
-
def expired(self) -> bool:
|
1679
|
-
return self.expired_fn()
|
1680
|
-
|
1681
|
-
def remaining(self) -> float:
|
1682
|
-
return float('inf')
|
1683
|
-
|
1684
|
-
def __call__(self) -> float:
|
1685
|
-
if not self.expired_fn():
|
1686
|
-
return float('inf')
|
1687
|
-
raise self.exc
|
1688
|
-
|
1689
|
-
def or_(self, o: ta.Any) -> ta.Any:
|
1690
|
-
return self()
|
1691
|
-
|
1692
|
-
|
1693
|
-
########################################
|
1694
|
-
# ../../../omlish/logs/filters.py
|
1695
|
-
|
1696
|
-
|
1697
|
-
##
|
1698
|
-
|
1699
|
-
|
1700
|
-
class TidLogFilter(logging.Filter):
|
1701
|
-
def filter(self, record):
|
1702
|
-
# FIXME: handle better - missing from wasm and cosmos
|
1703
|
-
if hasattr(threading, 'get_native_id'):
|
1704
|
-
record.tid = threading.get_native_id()
|
1705
|
-
else:
|
1706
|
-
record.tid = '?'
|
1707
|
-
return True
|
1708
|
-
|
1709
|
-
|
1710
|
-
########################################
|
1711
|
-
# ../../../omlish/logs/proxy.py
|
1712
|
-
|
1713
|
-
|
1714
|
-
##
|
1715
|
-
|
1716
|
-
|
1717
|
-
class ProxyLogFilterer(logging.Filterer):
|
1718
|
-
def __init__(self, underlying: logging.Filterer) -> None: # noqa
|
1719
|
-
self._underlying = underlying
|
1720
|
-
|
1721
|
-
@property
|
1722
|
-
def underlying(self) -> logging.Filterer:
|
1723
|
-
return self._underlying
|
1724
|
-
|
1725
|
-
@property
|
1726
|
-
def filters(self):
|
1727
|
-
return self._underlying.filters
|
1728
|
-
|
1729
|
-
@filters.setter
|
1730
|
-
def filters(self, filters):
|
1731
|
-
self._underlying.filters = filters
|
1732
|
-
|
1733
|
-
def addFilter(self, filter): # noqa
|
1734
|
-
self._underlying.addFilter(filter)
|
1735
|
-
|
1736
|
-
def removeFilter(self, filter): # noqa
|
1737
|
-
self._underlying.removeFilter(filter)
|
1738
|
-
|
1739
|
-
def filter(self, record):
|
1740
|
-
return self._underlying.filter(record)
|
1741
|
-
|
1742
|
-
|
1743
|
-
class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
|
1744
|
-
def __init__(self, underlying: logging.Handler) -> None: # noqa
|
1745
|
-
ProxyLogFilterer.__init__(self, underlying)
|
1746
|
-
|
1747
|
-
_underlying: logging.Handler
|
1748
|
-
|
1749
|
-
@property
|
1750
|
-
def underlying(self) -> logging.Handler:
|
1751
|
-
return self._underlying
|
1752
|
-
|
1753
|
-
def get_name(self):
|
1754
|
-
return self._underlying.get_name()
|
1755
|
-
|
1756
|
-
def set_name(self, name):
|
1757
|
-
self._underlying.set_name(name)
|
1758
|
-
|
1759
|
-
@property
|
1760
|
-
def name(self): # type: ignore[override]
|
1761
|
-
return self._underlying.name
|
1762
|
-
|
1763
|
-
@property
|
1764
|
-
def level(self):
|
1765
|
-
return self._underlying.level
|
1465
|
+
@property
|
1466
|
+
def level(self):
|
1467
|
+
return self._underlying.level
|
1766
1468
|
|
1767
1469
|
@level.setter
|
1768
1470
|
def level(self, level):
|
@@ -2541,91 +2243,676 @@ class ArgparseCli:
|
|
2541
2243
|
else:
|
2542
2244
|
obj.dest = obj.kwargs['dest'] = att # type: ignore
|
2543
2245
|
|
2544
|
-
parser.add_argument(*obj.args, **obj.kwargs)
|
2246
|
+
parser.add_argument(*obj.args, **obj.kwargs)
|
2247
|
+
|
2248
|
+
else:
|
2249
|
+
raise TypeError(obj)
|
2250
|
+
|
2251
|
+
#
|
2252
|
+
|
2253
|
+
_parser: ta.ClassVar[argparse.ArgumentParser]
|
2254
|
+
|
2255
|
+
@classmethod
|
2256
|
+
def get_parser(cls) -> argparse.ArgumentParser:
|
2257
|
+
return cls._parser
|
2258
|
+
|
2259
|
+
@property
|
2260
|
+
def argv(self) -> ta.Sequence[str]:
|
2261
|
+
return self._argv
|
2262
|
+
|
2263
|
+
@property
|
2264
|
+
def args(self) -> argparse.Namespace:
|
2265
|
+
return self._args
|
2266
|
+
|
2267
|
+
@property
|
2268
|
+
def unknown_args(self) -> ta.Sequence[str]:
|
2269
|
+
return self._unknown_args
|
2270
|
+
|
2271
|
+
#
|
2272
|
+
|
2273
|
+
def _bind_cli_cmd(self, cmd: ArgparseCmd) -> ta.Callable:
|
2274
|
+
return cmd.__get__(self, type(self))
|
2275
|
+
|
2276
|
+
def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
|
2277
|
+
cmd = getattr(self.args, '_cmd', None)
|
2278
|
+
|
2279
|
+
if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
|
2280
|
+
msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
|
2281
|
+
if (parser := self.get_parser()).exit_on_error:
|
2282
|
+
parser.error(msg)
|
2283
|
+
else:
|
2284
|
+
raise argparse.ArgumentError(None, msg)
|
2285
|
+
|
2286
|
+
if cmd is None:
|
2287
|
+
self.get_parser().print_help()
|
2288
|
+
return None
|
2289
|
+
|
2290
|
+
return self._bind_cli_cmd(cmd)
|
2291
|
+
|
2292
|
+
#
|
2293
|
+
|
2294
|
+
def cli_run(self) -> ta.Optional[int]:
|
2295
|
+
if (fn := self.prepare_cli_run()) is None:
|
2296
|
+
return 0
|
2297
|
+
|
2298
|
+
return fn()
|
2299
|
+
|
2300
|
+
def cli_run_and_exit(self) -> ta.NoReturn:
|
2301
|
+
sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
|
2302
|
+
|
2303
|
+
def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
|
2304
|
+
if exit:
|
2305
|
+
return self.cli_run_and_exit()
|
2306
|
+
else:
|
2307
|
+
return self.cli_run()
|
2308
|
+
|
2309
|
+
#
|
2310
|
+
|
2311
|
+
async def async_cli_run(
|
2312
|
+
self,
|
2313
|
+
*,
|
2314
|
+
force_async: bool = False,
|
2315
|
+
) -> ta.Optional[int]:
|
2316
|
+
if (fn := self.prepare_cli_run()) is None:
|
2317
|
+
return 0
|
2318
|
+
|
2319
|
+
if force_async:
|
2320
|
+
is_async = True
|
2321
|
+
else:
|
2322
|
+
tfn = fn
|
2323
|
+
if isinstance(tfn, ArgparseCmd):
|
2324
|
+
tfn = tfn.fn
|
2325
|
+
is_async = inspect.iscoroutinefunction(tfn)
|
2326
|
+
|
2327
|
+
if is_async:
|
2328
|
+
return await fn()
|
2329
|
+
else:
|
2330
|
+
return fn()
|
2331
|
+
|
2332
|
+
|
2333
|
+
########################################
|
2334
|
+
# ../../../omlish/lite/maybes.py
|
2335
|
+
|
2336
|
+
|
2337
|
+
##
|
2338
|
+
|
2339
|
+
|
2340
|
+
@functools.total_ordering
|
2341
|
+
class Maybe(ta.Generic[T]):
|
2342
|
+
class ValueNotPresentError(BaseException):
|
2343
|
+
pass
|
2344
|
+
|
2345
|
+
#
|
2346
|
+
|
2347
|
+
@property
|
2348
|
+
@abc.abstractmethod
|
2349
|
+
def present(self) -> bool:
|
2350
|
+
raise NotImplementedError
|
2351
|
+
|
2352
|
+
@abc.abstractmethod
|
2353
|
+
def must(self) -> T:
|
2354
|
+
raise NotImplementedError
|
2355
|
+
|
2356
|
+
#
|
2357
|
+
|
2358
|
+
@abc.abstractmethod
|
2359
|
+
def __repr__(self) -> str:
|
2360
|
+
raise NotImplementedError
|
2361
|
+
|
2362
|
+
@abc.abstractmethod
|
2363
|
+
def __hash__(self) -> int:
|
2364
|
+
raise NotImplementedError
|
2365
|
+
|
2366
|
+
@abc.abstractmethod
|
2367
|
+
def __eq__(self, other) -> bool:
|
2368
|
+
raise NotImplementedError
|
2369
|
+
|
2370
|
+
@abc.abstractmethod
|
2371
|
+
def __lt__(self, other) -> bool:
|
2372
|
+
raise NotImplementedError
|
2373
|
+
|
2374
|
+
#
|
2375
|
+
|
2376
|
+
@ta.final
|
2377
|
+
def __ne__(self, other):
|
2378
|
+
return not (self == other)
|
2379
|
+
|
2380
|
+
@ta.final
|
2381
|
+
def __iter__(self) -> ta.Iterator[T]:
|
2382
|
+
if self.present:
|
2383
|
+
yield self.must()
|
2384
|
+
|
2385
|
+
@ta.final
|
2386
|
+
def __bool__(self) -> ta.NoReturn:
|
2387
|
+
raise TypeError
|
2388
|
+
|
2389
|
+
#
|
2390
|
+
|
2391
|
+
@ta.final
|
2392
|
+
def if_present(self, consumer: ta.Callable[[T], None]) -> None:
|
2393
|
+
if self.present:
|
2394
|
+
consumer(self.must())
|
2395
|
+
|
2396
|
+
@ta.final
|
2397
|
+
def filter(self, predicate: ta.Callable[[T], bool]) -> 'Maybe[T]':
|
2398
|
+
if self.present and predicate(self.must()):
|
2399
|
+
return self
|
2400
|
+
else:
|
2401
|
+
return Maybe.empty()
|
2402
|
+
|
2403
|
+
@ta.final
|
2404
|
+
def map(self, mapper: ta.Callable[[T], U]) -> 'Maybe[U]':
|
2405
|
+
if self.present:
|
2406
|
+
return Maybe.just(mapper(self.must()))
|
2407
|
+
else:
|
2408
|
+
return Maybe.empty()
|
2409
|
+
|
2410
|
+
@ta.final
|
2411
|
+
def flat_map(self, mapper: ta.Callable[[T], 'Maybe[U]']) -> 'Maybe[U]':
|
2412
|
+
if self.present:
|
2413
|
+
if not isinstance(v := mapper(self.must()), Maybe):
|
2414
|
+
raise TypeError(v)
|
2415
|
+
return v
|
2416
|
+
else:
|
2417
|
+
return Maybe.empty()
|
2418
|
+
|
2419
|
+
@ta.final
|
2420
|
+
def or_else(self, other: ta.Union[T, U]) -> ta.Union[T, U]:
|
2421
|
+
if self.present:
|
2422
|
+
return self.must()
|
2423
|
+
else:
|
2424
|
+
return other
|
2425
|
+
|
2426
|
+
@ta.final
|
2427
|
+
def or_else_get(self, supplier: ta.Callable[[], ta.Union[T, U]]) -> ta.Union[T, U]:
|
2428
|
+
if self.present:
|
2429
|
+
return self.must()
|
2430
|
+
else:
|
2431
|
+
return supplier()
|
2432
|
+
|
2433
|
+
@ta.final
|
2434
|
+
def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
|
2435
|
+
if self.present:
|
2436
|
+
return self.must()
|
2437
|
+
else:
|
2438
|
+
raise exception_supplier()
|
2439
|
+
|
2440
|
+
#
|
2441
|
+
|
2442
|
+
@classmethod
|
2443
|
+
def of_optional(cls, v: ta.Optional[T]) -> 'Maybe[T]':
|
2444
|
+
if v is not None:
|
2445
|
+
return cls.just(v)
|
2446
|
+
else:
|
2447
|
+
return cls.empty()
|
2448
|
+
|
2449
|
+
@classmethod
|
2450
|
+
def just(cls, v: T) -> 'Maybe[T]':
|
2451
|
+
return _JustMaybe(v)
|
2452
|
+
|
2453
|
+
_empty: ta.ClassVar['Maybe']
|
2454
|
+
|
2455
|
+
@classmethod
|
2456
|
+
def empty(cls) -> 'Maybe[T]':
|
2457
|
+
return Maybe._empty
|
2458
|
+
|
2459
|
+
|
2460
|
+
##
|
2461
|
+
|
2462
|
+
|
2463
|
+
class _Maybe(Maybe[T], Abstract):
|
2464
|
+
def __lt__(self, other):
|
2465
|
+
if not isinstance(other, _Maybe):
|
2466
|
+
return NotImplemented
|
2467
|
+
sp = self.present
|
2468
|
+
op = other.present
|
2469
|
+
if self.present and other.present:
|
2470
|
+
return self.must() < other.must()
|
2471
|
+
else:
|
2472
|
+
return op and not sp
|
2473
|
+
|
2474
|
+
|
2475
|
+
class _JustMaybe(_Maybe[T]):
|
2476
|
+
__slots__ = ('_v', '_hash')
|
2477
|
+
|
2478
|
+
def __init__(self, v: T) -> None:
|
2479
|
+
super().__init__()
|
2480
|
+
|
2481
|
+
self._v = v
|
2482
|
+
|
2483
|
+
@property
|
2484
|
+
def present(self) -> bool:
|
2485
|
+
return True
|
2486
|
+
|
2487
|
+
def must(self) -> T:
|
2488
|
+
return self._v
|
2489
|
+
|
2490
|
+
#
|
2491
|
+
|
2492
|
+
def __repr__(self) -> str:
|
2493
|
+
return f'just({self._v!r})'
|
2494
|
+
|
2495
|
+
_hash: int
|
2496
|
+
|
2497
|
+
def __hash__(self) -> int:
|
2498
|
+
try:
|
2499
|
+
return self._hash
|
2500
|
+
except AttributeError:
|
2501
|
+
pass
|
2502
|
+
h = self._hash = hash((_JustMaybe, self._v))
|
2503
|
+
return h
|
2504
|
+
|
2505
|
+
def __eq__(self, other):
|
2506
|
+
return (
|
2507
|
+
self.__class__ is other.__class__ and
|
2508
|
+
self._v == other._v # noqa
|
2509
|
+
)
|
2510
|
+
|
2511
|
+
|
2512
|
+
class _EmptyMaybe(_Maybe[T]):
|
2513
|
+
__slots__ = ()
|
2514
|
+
|
2515
|
+
@property
|
2516
|
+
def present(self) -> bool:
|
2517
|
+
return False
|
2518
|
+
|
2519
|
+
def must(self) -> T:
|
2520
|
+
raise Maybe.ValueNotPresentError
|
2521
|
+
|
2522
|
+
#
|
2523
|
+
|
2524
|
+
def __repr__(self) -> str:
|
2525
|
+
return 'empty()'
|
2526
|
+
|
2527
|
+
def __hash__(self) -> int:
|
2528
|
+
return hash(_EmptyMaybe)
|
2529
|
+
|
2530
|
+
def __eq__(self, other):
|
2531
|
+
return self.__class__ is other.__class__
|
2532
|
+
|
2533
|
+
|
2534
|
+
Maybe._empty = _EmptyMaybe() # noqa
|
2535
|
+
|
2536
|
+
|
2537
|
+
########################################
|
2538
|
+
# ../../../omlish/lite/runtime.py
|
2539
|
+
|
2540
|
+
|
2541
|
+
##
|
2542
|
+
|
2543
|
+
|
2544
|
+
@cached_nullary
|
2545
|
+
def is_debugger_attached() -> bool:
|
2546
|
+
return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
|
2547
|
+
|
2548
|
+
|
2549
|
+
LITE_REQUIRED_PYTHON_VERSION = (3, 8)
|
2550
|
+
|
2551
|
+
|
2552
|
+
def check_lite_runtime_version() -> None:
|
2553
|
+
if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
|
2554
|
+
raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|
2555
|
+
|
2556
|
+
|
2557
|
+
########################################
|
2558
|
+
# ../../../omlish/lite/timeouts.py
|
2559
|
+
"""
|
2560
|
+
TODO:
|
2561
|
+
- Event (/ Predicate)
|
2562
|
+
"""
|
2563
|
+
|
2564
|
+
|
2565
|
+
##
|
2566
|
+
|
2567
|
+
|
2568
|
+
class Timeout(Abstract):
|
2569
|
+
@property
|
2570
|
+
@abc.abstractmethod
|
2571
|
+
def can_expire(self) -> bool:
|
2572
|
+
"""Indicates whether or not this timeout will ever expire."""
|
2573
|
+
|
2574
|
+
raise NotImplementedError
|
2575
|
+
|
2576
|
+
@abc.abstractmethod
|
2577
|
+
def expired(self) -> bool:
|
2578
|
+
"""Return whether or not this timeout has expired."""
|
2579
|
+
|
2580
|
+
raise NotImplementedError
|
2581
|
+
|
2582
|
+
@abc.abstractmethod
|
2583
|
+
def remaining(self) -> float:
|
2584
|
+
"""Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
|
2585
|
+
|
2586
|
+
raise NotImplementedError
|
2587
|
+
|
2588
|
+
@abc.abstractmethod
|
2589
|
+
def __call__(self) -> float:
|
2590
|
+
"""Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
|
2591
|
+
|
2592
|
+
raise NotImplementedError
|
2593
|
+
|
2594
|
+
@abc.abstractmethod
|
2595
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
2596
|
+
"""Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
|
2597
|
+
|
2598
|
+
raise NotImplementedError
|
2599
|
+
|
2600
|
+
#
|
2601
|
+
|
2602
|
+
@classmethod
|
2603
|
+
def _now(cls) -> float:
|
2604
|
+
return time.monotonic()
|
2605
|
+
|
2606
|
+
#
|
2607
|
+
|
2608
|
+
class DEFAULT: # Noqa
|
2609
|
+
def __new__(cls, *args, **kwargs): # noqa
|
2610
|
+
raise TypeError
|
2611
|
+
|
2612
|
+
class _NOT_SPECIFIED: # noqa
|
2613
|
+
def __new__(cls, *args, **kwargs): # noqa
|
2614
|
+
raise TypeError
|
2615
|
+
|
2616
|
+
@classmethod
|
2617
|
+
def of(
|
2618
|
+
cls,
|
2619
|
+
obj: TimeoutLike,
|
2620
|
+
default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
|
2621
|
+
) -> 'Timeout':
|
2622
|
+
if obj is None:
|
2623
|
+
return InfiniteTimeout()
|
2624
|
+
|
2625
|
+
elif isinstance(obj, Timeout):
|
2626
|
+
return obj
|
2627
|
+
|
2628
|
+
elif isinstance(obj, (float, int)):
|
2629
|
+
return DeadlineTimeout(cls._now() + obj)
|
2630
|
+
|
2631
|
+
elif isinstance(obj, ta.Iterable):
|
2632
|
+
return CompositeTimeout(*[Timeout.of(c) for c in obj])
|
2633
|
+
|
2634
|
+
elif obj is Timeout.DEFAULT:
|
2635
|
+
if default is Timeout._NOT_SPECIFIED or default is Timeout.DEFAULT:
|
2636
|
+
raise RuntimeError('Must specify a default timeout')
|
2637
|
+
|
2638
|
+
else:
|
2639
|
+
return Timeout.of(default) # type: ignore[arg-type]
|
2640
|
+
|
2641
|
+
else:
|
2642
|
+
raise TypeError(obj)
|
2643
|
+
|
2644
|
+
@classmethod
|
2645
|
+
def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
|
2646
|
+
return DeadlineTimeout(deadline)
|
2647
|
+
|
2648
|
+
@classmethod
|
2649
|
+
def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
|
2650
|
+
return PredicateTimeout(expired_fn)
|
2651
|
+
|
2652
|
+
|
2653
|
+
class DeadlineTimeout(Timeout):
|
2654
|
+
def __init__(
|
2655
|
+
self,
|
2656
|
+
deadline: float,
|
2657
|
+
exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
|
2658
|
+
) -> None:
|
2659
|
+
super().__init__()
|
2660
|
+
|
2661
|
+
self.deadline = deadline
|
2662
|
+
self.exc = exc
|
2663
|
+
|
2664
|
+
@property
|
2665
|
+
def can_expire(self) -> bool:
|
2666
|
+
return True
|
2667
|
+
|
2668
|
+
def expired(self) -> bool:
|
2669
|
+
return not (self.remaining() > 0)
|
2670
|
+
|
2671
|
+
def remaining(self) -> float:
|
2672
|
+
return self.deadline - self._now()
|
2673
|
+
|
2674
|
+
def __call__(self) -> float:
|
2675
|
+
if (rem := self.remaining()) > 0:
|
2676
|
+
return rem
|
2677
|
+
raise self.exc
|
2678
|
+
|
2679
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
2680
|
+
return self()
|
2681
|
+
|
2682
|
+
|
2683
|
+
class InfiniteTimeout(Timeout):
|
2684
|
+
@property
|
2685
|
+
def can_expire(self) -> bool:
|
2686
|
+
return False
|
2687
|
+
|
2688
|
+
def expired(self) -> bool:
|
2689
|
+
return False
|
2690
|
+
|
2691
|
+
def remaining(self) -> float:
|
2692
|
+
return float('inf')
|
2693
|
+
|
2694
|
+
def __call__(self) -> float:
|
2695
|
+
return float('inf')
|
2696
|
+
|
2697
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
2698
|
+
return o
|
2699
|
+
|
2700
|
+
|
2701
|
+
class CompositeTimeout(Timeout):
|
2702
|
+
def __init__(self, *children: Timeout) -> None:
|
2703
|
+
super().__init__()
|
2704
|
+
|
2705
|
+
self.children = children
|
2706
|
+
|
2707
|
+
@property
|
2708
|
+
def can_expire(self) -> bool:
|
2709
|
+
return any(c.can_expire for c in self.children)
|
2710
|
+
|
2711
|
+
def expired(self) -> bool:
|
2712
|
+
return any(c.expired() for c in self.children)
|
2713
|
+
|
2714
|
+
def remaining(self) -> float:
|
2715
|
+
return min(c.remaining() for c in self.children)
|
2716
|
+
|
2717
|
+
def __call__(self) -> float:
|
2718
|
+
return min(c() for c in self.children)
|
2719
|
+
|
2720
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
2721
|
+
if self.can_expire:
|
2722
|
+
return self()
|
2723
|
+
return o
|
2724
|
+
|
2725
|
+
|
2726
|
+
class PredicateTimeout(Timeout):
|
2727
|
+
def __init__(
|
2728
|
+
self,
|
2729
|
+
expired_fn: ta.Callable[[], bool],
|
2730
|
+
exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
|
2731
|
+
) -> None:
|
2732
|
+
super().__init__()
|
2733
|
+
|
2734
|
+
self.expired_fn = expired_fn
|
2735
|
+
self.exc = exc
|
2736
|
+
|
2737
|
+
@property
|
2738
|
+
def can_expire(self) -> bool:
|
2739
|
+
return True
|
2740
|
+
|
2741
|
+
def expired(self) -> bool:
|
2742
|
+
return self.expired_fn()
|
2743
|
+
|
2744
|
+
def remaining(self) -> float:
|
2745
|
+
return float('inf')
|
2746
|
+
|
2747
|
+
def __call__(self) -> float:
|
2748
|
+
if not self.expired_fn():
|
2749
|
+
return float('inf')
|
2750
|
+
raise self.exc
|
2751
|
+
|
2752
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
2753
|
+
return self()
|
2754
|
+
|
2755
|
+
|
2756
|
+
########################################
|
2757
|
+
# ../../../omlish/logs/json.py
|
2758
|
+
"""
|
2759
|
+
TODO:
|
2760
|
+
- translate json keys
|
2761
|
+
"""
|
2762
|
+
|
2763
|
+
|
2764
|
+
##
|
2765
|
+
|
2766
|
+
|
2767
|
+
class JsonLogFormatter(logging.Formatter):
|
2768
|
+
KEYS: ta.Mapping[str, bool] = {
|
2769
|
+
'name': False,
|
2770
|
+
'msg': False,
|
2771
|
+
'args': False,
|
2772
|
+
'levelname': False,
|
2773
|
+
'levelno': False,
|
2774
|
+
'pathname': False,
|
2775
|
+
'filename': False,
|
2776
|
+
'module': False,
|
2777
|
+
'exc_info': True,
|
2778
|
+
'exc_text': True,
|
2779
|
+
'stack_info': True,
|
2780
|
+
'lineno': False,
|
2781
|
+
'funcName': False,
|
2782
|
+
'created': False,
|
2783
|
+
'msecs': False,
|
2784
|
+
'relativeCreated': False,
|
2785
|
+
'thread': False,
|
2786
|
+
'threadName': False,
|
2787
|
+
'processName': False,
|
2788
|
+
'process': False,
|
2789
|
+
}
|
2790
|
+
|
2791
|
+
def __init__(
|
2792
|
+
self,
|
2793
|
+
*args: ta.Any,
|
2794
|
+
json_dumps: ta.Optional[ta.Callable[[ta.Any], str]] = None,
|
2795
|
+
**kwargs: ta.Any,
|
2796
|
+
) -> None:
|
2797
|
+
super().__init__(*args, **kwargs)
|
2798
|
+
|
2799
|
+
if json_dumps is None:
|
2800
|
+
json_dumps = json_dumps_compact
|
2801
|
+
self._json_dumps = json_dumps
|
2802
|
+
|
2803
|
+
def format(self, record: logging.LogRecord) -> str:
|
2804
|
+
dct = {
|
2805
|
+
k: v
|
2806
|
+
for k, o in self.KEYS.items()
|
2807
|
+
for v in [getattr(record, k)]
|
2808
|
+
if not (o and v is None)
|
2809
|
+
}
|
2810
|
+
return self._json_dumps(dct)
|
2811
|
+
|
2812
|
+
|
2813
|
+
########################################
|
2814
|
+
# ../types.py
|
2815
|
+
|
2816
|
+
|
2817
|
+
##
|
2818
|
+
|
2819
|
+
|
2820
|
+
# See https://peps.python.org/pep-3149/
|
2821
|
+
INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
|
2822
|
+
('debug', 'd'),
|
2823
|
+
('threaded', 't'),
|
2824
|
+
])
|
2825
|
+
|
2826
|
+
INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
|
2827
|
+
(g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
|
2828
|
+
)
|
2545
2829
|
|
2546
|
-
else:
|
2547
|
-
raise TypeError(obj)
|
2548
2830
|
|
2549
|
-
|
2831
|
+
@dc.dataclass(frozen=True)
|
2832
|
+
class InterpOpts:
|
2833
|
+
threaded: bool = False
|
2834
|
+
debug: bool = False
|
2550
2835
|
|
2551
|
-
|
2836
|
+
def __str__(self) -> str:
|
2837
|
+
return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
|
2552
2838
|
|
2553
2839
|
@classmethod
|
2554
|
-
def
|
2555
|
-
return cls
|
2840
|
+
def parse(cls, s: str) -> 'InterpOpts':
|
2841
|
+
return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
|
2556
2842
|
|
2557
|
-
@
|
2558
|
-
def
|
2559
|
-
|
2843
|
+
@classmethod
|
2844
|
+
def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
|
2845
|
+
kw = {}
|
2846
|
+
while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
|
2847
|
+
s, kw[a] = s[:-1], True
|
2848
|
+
return s, cls(**kw)
|
2560
2849
|
|
2561
|
-
@property
|
2562
|
-
def args(self) -> argparse.Namespace:
|
2563
|
-
return self._args
|
2564
2850
|
|
2565
|
-
|
2566
|
-
def unknown_args(self) -> ta.Sequence[str]:
|
2567
|
-
return self._unknown_args
|
2851
|
+
##
|
2568
2852
|
|
2569
|
-
#
|
2570
2853
|
|
2571
|
-
|
2572
|
-
|
2854
|
+
@dc.dataclass(frozen=True)
|
2855
|
+
class InterpVersion:
|
2856
|
+
version: Version
|
2857
|
+
opts: InterpOpts
|
2573
2858
|
|
2574
|
-
def
|
2575
|
-
|
2859
|
+
def __str__(self) -> str:
|
2860
|
+
return str(self.version) + str(self.opts)
|
2576
2861
|
|
2577
|
-
|
2578
|
-
|
2579
|
-
|
2580
|
-
|
2581
|
-
|
2582
|
-
|
2862
|
+
@classmethod
|
2863
|
+
def parse(cls, s: str) -> 'InterpVersion':
|
2864
|
+
s, o = InterpOpts.parse_suffix(s)
|
2865
|
+
v = Version(s)
|
2866
|
+
return cls(
|
2867
|
+
version=v,
|
2868
|
+
opts=o,
|
2869
|
+
)
|
2583
2870
|
|
2584
|
-
|
2585
|
-
|
2871
|
+
@classmethod
|
2872
|
+
def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
|
2873
|
+
try:
|
2874
|
+
return cls.parse(s)
|
2875
|
+
except (KeyError, InvalidVersion):
|
2586
2876
|
return None
|
2587
2877
|
|
2588
|
-
return self._bind_cli_cmd(cmd)
|
2589
2878
|
|
2590
|
-
|
2879
|
+
##
|
2591
2880
|
|
2592
|
-
def cli_run(self) -> ta.Optional[int]:
|
2593
|
-
if (fn := self.prepare_cli_run()) is None:
|
2594
|
-
return 0
|
2595
2881
|
|
2596
|
-
|
2882
|
+
@dc.dataclass(frozen=True)
|
2883
|
+
class InterpSpecifier:
|
2884
|
+
specifier: Specifier
|
2885
|
+
opts: InterpOpts
|
2597
2886
|
|
2598
|
-
def
|
2599
|
-
|
2887
|
+
def __str__(self) -> str:
|
2888
|
+
return str(self.specifier) + str(self.opts)
|
2600
2889
|
|
2601
|
-
|
2602
|
-
|
2603
|
-
|
2604
|
-
|
2605
|
-
|
2890
|
+
@classmethod
|
2891
|
+
def parse(cls, s: str) -> 'InterpSpecifier':
|
2892
|
+
s, o = InterpOpts.parse_suffix(s)
|
2893
|
+
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
2894
|
+
s = '~=' + s
|
2895
|
+
if s.count('.') < 2:
|
2896
|
+
s += '.0'
|
2897
|
+
return cls(
|
2898
|
+
specifier=Specifier(s),
|
2899
|
+
opts=o,
|
2900
|
+
)
|
2606
2901
|
|
2607
|
-
|
2902
|
+
def contains(self, iv: InterpVersion) -> bool:
|
2903
|
+
return self.specifier.contains(iv.version) and self.opts == iv.opts
|
2608
2904
|
|
2609
|
-
|
2610
|
-
|
2611
|
-
*,
|
2612
|
-
force_async: bool = False,
|
2613
|
-
) -> ta.Optional[int]:
|
2614
|
-
if (fn := self.prepare_cli_run()) is None:
|
2615
|
-
return 0
|
2905
|
+
def __contains__(self, iv: InterpVersion) -> bool:
|
2906
|
+
return self.contains(iv)
|
2616
2907
|
|
2617
|
-
if force_async:
|
2618
|
-
is_async = True
|
2619
|
-
else:
|
2620
|
-
tfn = fn
|
2621
|
-
if isinstance(tfn, ArgparseCmd):
|
2622
|
-
tfn = tfn.fn
|
2623
|
-
is_async = inspect.iscoroutinefunction(tfn)
|
2624
2908
|
|
2625
|
-
|
2626
|
-
|
2627
|
-
|
2628
|
-
|
2909
|
+
##
|
2910
|
+
|
2911
|
+
|
2912
|
+
@dc.dataclass(frozen=True)
|
2913
|
+
class Interp:
|
2914
|
+
exe: str
|
2915
|
+
version: InterpVersion
|
2629
2916
|
|
2630
2917
|
|
2631
2918
|
########################################
|
@@ -2678,7 +2965,7 @@ def check_valid_injector_key_cls(cls: T) -> T:
|
|
2678
2965
|
##
|
2679
2966
|
|
2680
2967
|
|
2681
|
-
class InjectorProvider(
|
2968
|
+
class InjectorProvider(Abstract):
|
2682
2969
|
@abc.abstractmethod
|
2683
2970
|
def provider_fn(self) -> InjectorProviderFn:
|
2684
2971
|
raise NotImplementedError
|
@@ -2697,7 +2984,7 @@ class InjectorBinding:
|
|
2697
2984
|
check.isinstance(self.provider, InjectorProvider)
|
2698
2985
|
|
2699
2986
|
|
2700
|
-
class InjectorBindings(
|
2987
|
+
class InjectorBindings(Abstract):
|
2701
2988
|
@abc.abstractmethod
|
2702
2989
|
def bindings(self) -> ta.Iterator[InjectorBinding]:
|
2703
2990
|
raise NotImplementedError
|
@@ -2705,7 +2992,7 @@ class InjectorBindings(abc.ABC):
|
|
2705
2992
|
##
|
2706
2993
|
|
2707
2994
|
|
2708
|
-
class Injector(
|
2995
|
+
class Injector(Abstract):
|
2709
2996
|
@abc.abstractmethod
|
2710
2997
|
def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
|
2711
2998
|
raise NotImplementedError
|
@@ -2964,14 +3251,12 @@ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) ->
|
|
2964
3251
|
# scopes
|
2965
3252
|
|
2966
3253
|
|
2967
|
-
class InjectorScope(
|
3254
|
+
class InjectorScope(Abstract):
|
2968
3255
|
def __init__(
|
2969
3256
|
self,
|
2970
3257
|
*,
|
2971
3258
|
_i: Injector,
|
2972
3259
|
) -> None:
|
2973
|
-
check.not_in(abc.ABC, type(self).__bases__)
|
2974
|
-
|
2975
3260
|
super().__init__()
|
2976
3261
|
|
2977
3262
|
self._i = _i
|
@@ -3002,7 +3287,7 @@ class InjectorScope(abc.ABC): # noqa
|
|
3002
3287
|
raise NotImplementedError
|
3003
3288
|
|
3004
3289
|
|
3005
|
-
class ExclusiveInjectorScope(InjectorScope,
|
3290
|
+
class ExclusiveInjectorScope(InjectorScope, Abstract):
|
3006
3291
|
_st: ta.Optional[InjectorScope.State] = None
|
3007
3292
|
|
3008
3293
|
def state(self) -> InjectorScope.State:
|
@@ -3018,12 +3303,13 @@ class ExclusiveInjectorScope(InjectorScope, abc.ABC):
|
|
3018
3303
|
self._st = None
|
3019
3304
|
|
3020
3305
|
|
3021
|
-
class ContextvarInjectorScope(InjectorScope,
|
3306
|
+
class ContextvarInjectorScope(InjectorScope, Abstract):
|
3022
3307
|
_cv: contextvars.ContextVar
|
3023
3308
|
|
3024
3309
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3025
3310
|
super().__init_subclass__(**kwargs)
|
3026
3311
|
|
3312
|
+
check.not_in(Abstract, cls.__bases__)
|
3027
3313
|
check.not_in(abc.ABC, cls.__bases__)
|
3028
3314
|
check.state(not hasattr(cls, '_cv'))
|
3029
3315
|
cls._cv = contextvars.ContextVar(f'{cls.__name__}_cv')
|
@@ -3529,7 +3815,7 @@ class InjectorBinder:
|
|
3529
3815
|
pws: ta.List[ta.Any] = []
|
3530
3816
|
if in_ is not None:
|
3531
3817
|
check.issubclass(in_, InjectorScope)
|
3532
|
-
check.not_in(
|
3818
|
+
check.not_in(Abstract, in_.__bases__)
|
3533
3819
|
pws.append(functools.partial(ScopedInjectorProvider, k=key, sc=in_))
|
3534
3820
|
if singleton:
|
3535
3821
|
pws.append(SingletonInjectorProvider)
|
@@ -3730,80 +4016,125 @@ inj = InjectionApi()
|
|
3730
4016
|
|
3731
4017
|
|
3732
4018
|
########################################
|
3733
|
-
# ../../../omlish/
|
4019
|
+
# ../../../omlish/logs/standard.py
|
4020
|
+
"""
|
4021
|
+
TODO:
|
4022
|
+
- structured
|
4023
|
+
- prefixed
|
4024
|
+
- debug
|
4025
|
+
- optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
|
4026
|
+
"""
|
3734
4027
|
|
3735
4028
|
|
3736
4029
|
##
|
3737
4030
|
|
3738
4031
|
|
3739
|
-
|
3740
|
-
|
3741
|
-
|
4032
|
+
STANDARD_LOG_FORMAT_PARTS = [
|
4033
|
+
('asctime', '%(asctime)-15s'),
|
4034
|
+
('process', 'pid=%(process)s'),
|
4035
|
+
('thread', 'tid=%(thread)x'),
|
4036
|
+
('levelname', '%(levelname)s'),
|
4037
|
+
('name', '%(name)s'),
|
4038
|
+
('separator', '::'),
|
4039
|
+
('message', '%(message)s'),
|
4040
|
+
]
|
3742
4041
|
|
3743
4042
|
|
3744
|
-
|
4043
|
+
class StandardLogFormatter(logging.Formatter):
|
4044
|
+
@staticmethod
|
4045
|
+
def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
|
4046
|
+
return ' '.join(v for k, v in parts)
|
3745
4047
|
|
4048
|
+
converter = datetime.datetime.fromtimestamp # type: ignore
|
3746
4049
|
|
3747
|
-
def
|
3748
|
-
|
3749
|
-
|
4050
|
+
def formatTime(self, record, datefmt=None):
|
4051
|
+
ct = self.converter(record.created)
|
4052
|
+
if datefmt:
|
4053
|
+
return ct.strftime(datefmt) # noqa
|
4054
|
+
else:
|
4055
|
+
t = ct.strftime('%Y-%m-%d %H:%M:%S')
|
4056
|
+
return '%s.%03d' % (t, record.msecs) # noqa
|
3750
4057
|
|
3751
4058
|
|
3752
|
-
|
3753
|
-
|
3754
|
-
|
3755
|
-
|
3756
|
-
|
3757
|
-
|
4059
|
+
##
|
4060
|
+
|
4061
|
+
|
4062
|
+
class StandardConfiguredLogHandler(ProxyLogHandler):
|
4063
|
+
def __init_subclass__(cls, **kwargs):
|
4064
|
+
raise TypeError('This class serves only as a marker and should not be subclassed.')
|
3758
4065
|
|
3759
4066
|
|
3760
4067
|
##
|
3761
4068
|
|
3762
4069
|
|
3763
|
-
|
3764
|
-
|
3765
|
-
|
3766
|
-
|
3767
|
-
|
3768
|
-
|
3769
|
-
|
3770
|
-
|
3771
|
-
|
3772
|
-
|
3773
|
-
|
3774
|
-
|
3775
|
-
|
3776
|
-
|
3777
|
-
|
3778
|
-
'
|
3779
|
-
|
3780
|
-
|
3781
|
-
|
3782
|
-
|
3783
|
-
|
3784
|
-
|
3785
|
-
|
4070
|
+
@contextlib.contextmanager
|
4071
|
+
def _locking_logging_module_lock() -> ta.Iterator[None]:
|
4072
|
+
if hasattr(logging, '_acquireLock'):
|
4073
|
+
logging._acquireLock() # noqa
|
4074
|
+
try:
|
4075
|
+
yield
|
4076
|
+
finally:
|
4077
|
+
logging._releaseLock() # type: ignore # noqa
|
4078
|
+
|
4079
|
+
elif hasattr(logging, '_lock'):
|
4080
|
+
# https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
|
4081
|
+
with logging._lock: # noqa
|
4082
|
+
yield
|
4083
|
+
|
4084
|
+
else:
|
4085
|
+
raise Exception("Can't find lock in logging module")
|
4086
|
+
|
4087
|
+
|
4088
|
+
def configure_standard_logging(
|
4089
|
+
level: ta.Union[int, str] = logging.INFO,
|
4090
|
+
*,
|
4091
|
+
json: bool = False,
|
4092
|
+
target: ta.Optional[logging.Logger] = None,
|
4093
|
+
force: bool = False,
|
4094
|
+
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
4095
|
+
) -> ta.Optional[StandardConfiguredLogHandler]:
|
4096
|
+
with _locking_logging_module_lock():
|
4097
|
+
if target is None:
|
4098
|
+
target = logging.root
|
4099
|
+
|
4100
|
+
#
|
4101
|
+
|
4102
|
+
if not force:
|
4103
|
+
if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
|
4104
|
+
return None
|
4105
|
+
|
4106
|
+
#
|
4107
|
+
|
4108
|
+
if handler_factory is not None:
|
4109
|
+
handler = handler_factory()
|
4110
|
+
else:
|
4111
|
+
handler = logging.StreamHandler()
|
4112
|
+
|
4113
|
+
#
|
4114
|
+
|
4115
|
+
formatter: logging.Formatter
|
4116
|
+
if json:
|
4117
|
+
formatter = JsonLogFormatter()
|
4118
|
+
else:
|
4119
|
+
formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
|
4120
|
+
handler.setFormatter(formatter)
|
4121
|
+
|
4122
|
+
#
|
4123
|
+
|
4124
|
+
handler.addFilter(TidLogFilter())
|
3786
4125
|
|
3787
|
-
|
3788
|
-
self,
|
3789
|
-
*args: ta.Any,
|
3790
|
-
json_dumps: ta.Optional[ta.Callable[[ta.Any], str]] = None,
|
3791
|
-
**kwargs: ta.Any,
|
3792
|
-
) -> None:
|
3793
|
-
super().__init__(*args, **kwargs)
|
4126
|
+
#
|
3794
4127
|
|
3795
|
-
|
3796
|
-
json_dumps = json_dumps_compact
|
3797
|
-
self._json_dumps = json_dumps
|
4128
|
+
target.addHandler(handler)
|
3798
4129
|
|
3799
|
-
|
3800
|
-
|
3801
|
-
|
3802
|
-
|
3803
|
-
|
3804
|
-
|
3805
|
-
|
3806
|
-
return
|
4130
|
+
#
|
4131
|
+
|
4132
|
+
if level is not None:
|
4133
|
+
target.setLevel(level)
|
4134
|
+
|
4135
|
+
#
|
4136
|
+
|
4137
|
+
return StandardConfiguredLogHandler(handler)
|
3807
4138
|
|
3808
4139
|
|
3809
4140
|
########################################
|
@@ -3922,7 +4253,7 @@ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessR
|
|
3922
4253
|
##
|
3923
4254
|
|
3924
4255
|
|
3925
|
-
class SubprocessRunnable(
|
4256
|
+
class SubprocessRunnable(Abstract, ta.Generic[T]):
|
3926
4257
|
@abc.abstractmethod
|
3927
4258
|
def make_run(self) -> SubprocessRun:
|
3928
4259
|
raise NotImplementedError
|
@@ -3955,233 +4286,6 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
|
|
3955
4286
|
return self.handle_run_output(await self.make_run().maysync_run(maysync_subprocesses, **kwargs))
|
3956
4287
|
|
3957
4288
|
|
3958
|
-
########################################
|
3959
|
-
# ../types.py
|
3960
|
-
|
3961
|
-
|
3962
|
-
##
|
3963
|
-
|
3964
|
-
|
3965
|
-
# See https://peps.python.org/pep-3149/
|
3966
|
-
INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
|
3967
|
-
('debug', 'd'),
|
3968
|
-
('threaded', 't'),
|
3969
|
-
])
|
3970
|
-
|
3971
|
-
INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
|
3972
|
-
(g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
|
3973
|
-
)
|
3974
|
-
|
3975
|
-
|
3976
|
-
@dc.dataclass(frozen=True)
|
3977
|
-
class InterpOpts:
|
3978
|
-
threaded: bool = False
|
3979
|
-
debug: bool = False
|
3980
|
-
|
3981
|
-
def __str__(self) -> str:
|
3982
|
-
return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
|
3983
|
-
|
3984
|
-
@classmethod
|
3985
|
-
def parse(cls, s: str) -> 'InterpOpts':
|
3986
|
-
return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
|
3987
|
-
|
3988
|
-
@classmethod
|
3989
|
-
def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
|
3990
|
-
kw = {}
|
3991
|
-
while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
|
3992
|
-
s, kw[a] = s[:-1], True
|
3993
|
-
return s, cls(**kw)
|
3994
|
-
|
3995
|
-
|
3996
|
-
##
|
3997
|
-
|
3998
|
-
|
3999
|
-
@dc.dataclass(frozen=True)
|
4000
|
-
class InterpVersion:
|
4001
|
-
version: Version
|
4002
|
-
opts: InterpOpts
|
4003
|
-
|
4004
|
-
def __str__(self) -> str:
|
4005
|
-
return str(self.version) + str(self.opts)
|
4006
|
-
|
4007
|
-
@classmethod
|
4008
|
-
def parse(cls, s: str) -> 'InterpVersion':
|
4009
|
-
s, o = InterpOpts.parse_suffix(s)
|
4010
|
-
v = Version(s)
|
4011
|
-
return cls(
|
4012
|
-
version=v,
|
4013
|
-
opts=o,
|
4014
|
-
)
|
4015
|
-
|
4016
|
-
@classmethod
|
4017
|
-
def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
|
4018
|
-
try:
|
4019
|
-
return cls.parse(s)
|
4020
|
-
except (KeyError, InvalidVersion):
|
4021
|
-
return None
|
4022
|
-
|
4023
|
-
|
4024
|
-
##
|
4025
|
-
|
4026
|
-
|
4027
|
-
@dc.dataclass(frozen=True)
|
4028
|
-
class InterpSpecifier:
|
4029
|
-
specifier: Specifier
|
4030
|
-
opts: InterpOpts
|
4031
|
-
|
4032
|
-
def __str__(self) -> str:
|
4033
|
-
return str(self.specifier) + str(self.opts)
|
4034
|
-
|
4035
|
-
@classmethod
|
4036
|
-
def parse(cls, s: str) -> 'InterpSpecifier':
|
4037
|
-
s, o = InterpOpts.parse_suffix(s)
|
4038
|
-
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
4039
|
-
s = '~=' + s
|
4040
|
-
if s.count('.') < 2:
|
4041
|
-
s += '.0'
|
4042
|
-
return cls(
|
4043
|
-
specifier=Specifier(s),
|
4044
|
-
opts=o,
|
4045
|
-
)
|
4046
|
-
|
4047
|
-
def contains(self, iv: InterpVersion) -> bool:
|
4048
|
-
return self.specifier.contains(iv.version) and self.opts == iv.opts
|
4049
|
-
|
4050
|
-
def __contains__(self, iv: InterpVersion) -> bool:
|
4051
|
-
return self.contains(iv)
|
4052
|
-
|
4053
|
-
|
4054
|
-
##
|
4055
|
-
|
4056
|
-
|
4057
|
-
@dc.dataclass(frozen=True)
|
4058
|
-
class Interp:
|
4059
|
-
exe: str
|
4060
|
-
version: InterpVersion
|
4061
|
-
|
4062
|
-
|
4063
|
-
########################################
|
4064
|
-
# ../../../omlish/logs/standard.py
|
4065
|
-
"""
|
4066
|
-
TODO:
|
4067
|
-
- structured
|
4068
|
-
- prefixed
|
4069
|
-
- debug
|
4070
|
-
- optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
|
4071
|
-
"""
|
4072
|
-
|
4073
|
-
|
4074
|
-
##
|
4075
|
-
|
4076
|
-
|
4077
|
-
STANDARD_LOG_FORMAT_PARTS = [
|
4078
|
-
('asctime', '%(asctime)-15s'),
|
4079
|
-
('process', 'pid=%(process)s'),
|
4080
|
-
('thread', 'tid=%(thread)x'),
|
4081
|
-
('levelname', '%(levelname)s'),
|
4082
|
-
('name', '%(name)s'),
|
4083
|
-
('separator', '::'),
|
4084
|
-
('message', '%(message)s'),
|
4085
|
-
]
|
4086
|
-
|
4087
|
-
|
4088
|
-
class StandardLogFormatter(logging.Formatter):
|
4089
|
-
@staticmethod
|
4090
|
-
def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
|
4091
|
-
return ' '.join(v for k, v in parts)
|
4092
|
-
|
4093
|
-
converter = datetime.datetime.fromtimestamp # type: ignore
|
4094
|
-
|
4095
|
-
def formatTime(self, record, datefmt=None):
|
4096
|
-
ct = self.converter(record.created)
|
4097
|
-
if datefmt:
|
4098
|
-
return ct.strftime(datefmt) # noqa
|
4099
|
-
else:
|
4100
|
-
t = ct.strftime('%Y-%m-%d %H:%M:%S')
|
4101
|
-
return '%s.%03d' % (t, record.msecs) # noqa
|
4102
|
-
|
4103
|
-
|
4104
|
-
##
|
4105
|
-
|
4106
|
-
|
4107
|
-
class StandardConfiguredLogHandler(ProxyLogHandler):
|
4108
|
-
def __init_subclass__(cls, **kwargs):
|
4109
|
-
raise TypeError('This class serves only as a marker and should not be subclassed.')
|
4110
|
-
|
4111
|
-
|
4112
|
-
##
|
4113
|
-
|
4114
|
-
|
4115
|
-
@contextlib.contextmanager
|
4116
|
-
def _locking_logging_module_lock() -> ta.Iterator[None]:
|
4117
|
-
if hasattr(logging, '_acquireLock'):
|
4118
|
-
logging._acquireLock() # noqa
|
4119
|
-
try:
|
4120
|
-
yield
|
4121
|
-
finally:
|
4122
|
-
logging._releaseLock() # type: ignore # noqa
|
4123
|
-
|
4124
|
-
elif hasattr(logging, '_lock'):
|
4125
|
-
# https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
|
4126
|
-
with logging._lock: # noqa
|
4127
|
-
yield
|
4128
|
-
|
4129
|
-
else:
|
4130
|
-
raise Exception("Can't find lock in logging module")
|
4131
|
-
|
4132
|
-
|
4133
|
-
def configure_standard_logging(
|
4134
|
-
level: ta.Union[int, str] = logging.INFO,
|
4135
|
-
*,
|
4136
|
-
json: bool = False,
|
4137
|
-
target: ta.Optional[logging.Logger] = None,
|
4138
|
-
force: bool = False,
|
4139
|
-
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
4140
|
-
) -> ta.Optional[StandardConfiguredLogHandler]:
|
4141
|
-
with _locking_logging_module_lock():
|
4142
|
-
if target is None:
|
4143
|
-
target = logging.root
|
4144
|
-
|
4145
|
-
#
|
4146
|
-
|
4147
|
-
if not force:
|
4148
|
-
if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
|
4149
|
-
return None
|
4150
|
-
|
4151
|
-
#
|
4152
|
-
|
4153
|
-
if handler_factory is not None:
|
4154
|
-
handler = handler_factory()
|
4155
|
-
else:
|
4156
|
-
handler = logging.StreamHandler()
|
4157
|
-
|
4158
|
-
#
|
4159
|
-
|
4160
|
-
formatter: logging.Formatter
|
4161
|
-
if json:
|
4162
|
-
formatter = JsonLogFormatter()
|
4163
|
-
else:
|
4164
|
-
formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
|
4165
|
-
handler.setFormatter(formatter)
|
4166
|
-
|
4167
|
-
#
|
4168
|
-
|
4169
|
-
handler.addFilter(TidLogFilter())
|
4170
|
-
|
4171
|
-
#
|
4172
|
-
|
4173
|
-
target.addHandler(handler)
|
4174
|
-
|
4175
|
-
#
|
4176
|
-
|
4177
|
-
if level is not None:
|
4178
|
-
target.setLevel(level)
|
4179
|
-
|
4180
|
-
#
|
4181
|
-
|
4182
|
-
return StandardConfiguredLogHandler(handler)
|
4183
|
-
|
4184
|
-
|
4185
4289
|
########################################
|
4186
4290
|
# ../../../omlish/subprocesses/wrap.py
|
4187
4291
|
"""
|
@@ -4223,13 +4327,13 @@ TODO:
|
|
4223
4327
|
##
|
4224
4328
|
|
4225
4329
|
|
4226
|
-
class InterpProvider(
|
4330
|
+
class InterpProvider(Abstract):
|
4227
4331
|
name: ta.ClassVar[str]
|
4228
4332
|
|
4229
4333
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
4230
4334
|
super().__init_subclass__(**kwargs)
|
4231
4335
|
|
4232
|
-
if
|
4336
|
+
if Abstract not in cls.__bases__ and 'name' not in cls.__dict__:
|
4233
4337
|
sfx = 'InterpProvider'
|
4234
4338
|
if not cls.__name__.endswith(sfx):
|
4235
4339
|
raise NameError(cls)
|
@@ -4295,7 +4399,7 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
|
|
4295
4399
|
return msg
|
4296
4400
|
|
4297
4401
|
|
4298
|
-
class BaseSubprocesses(
|
4402
|
+
class BaseSubprocesses(Abstract):
|
4299
4403
|
DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
|
4300
4404
|
|
4301
4405
|
def __init__(
|
@@ -4560,7 +4664,7 @@ class InterpResolver:
|
|
4560
4664
|
##
|
4561
4665
|
|
4562
4666
|
|
4563
|
-
class AbstractAsyncSubprocesses(BaseSubprocesses):
|
4667
|
+
class AbstractAsyncSubprocesses(BaseSubprocesses, Abstract):
|
4564
4668
|
@abc.abstractmethod
|
4565
4669
|
def run_(self, run: SubprocessRun) -> ta.Awaitable[SubprocessRunOutput]:
|
4566
4670
|
raise NotImplementedError
|
@@ -5326,7 +5430,7 @@ THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
|
|
5326
5430
|
#
|
5327
5431
|
|
5328
5432
|
|
5329
|
-
class PyenvInstallOptsProvider(
|
5433
|
+
class PyenvInstallOptsProvider(Abstract):
|
5330
5434
|
@abc.abstractmethod
|
5331
5435
|
def opts(self) -> ta.Awaitable[PyenvInstallOpts]:
|
5332
5436
|
raise NotImplementedError
|