omdev 0.0.0.dev419__py3-none-any.whl → 0.0.0.dev421__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/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/maybes.py
1209
+ # ../../../omlish/lite/reflect.py
1090
1210
 
1091
1211
 
1092
1212
  ##
1093
1213
 
1094
1214
 
1095
- @functools.total_ordering
1096
- class Maybe(ta.Generic[T]):
1097
- class ValueNotPresentError(BaseException):
1098
- pass
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
- @property
1103
- @abc.abstractmethod
1104
- def present(self) -> bool:
1105
- raise NotImplementedError
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
- @abc.abstractmethod
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
- @abc.abstractmethod
1126
- def __lt__(self, other) -> bool:
1127
- raise NotImplementedError
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
- @ta.final
1132
- def __ne__(self, other):
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
- @ta.final
1141
- def __bool__(self) -> ta.NoReturn:
1142
- raise TypeError
1249
+ #
1143
1250
 
1144
- #
1145
1251
 
1146
- @ta.final
1147
- def if_present(self, consumer: ta.Callable[[T], None]) -> None:
1148
- if self.present:
1149
- consumer(self.must())
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
- @ta.final
1159
- def map(self, mapper: ta.Callable[[T], U]) -> 'Maybe[U]':
1160
- if self.present:
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
- @ta.final
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
- @ta.final
1189
- def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
1190
- if self.present:
1191
- return self.must()
1192
- else:
1193
- raise exception_supplier()
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
- @classmethod
1198
- def of_optional(cls, v: ta.Optional[T]) -> 'Maybe[T]':
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
- _empty: ta.ClassVar['Maybe']
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
- def __init__(self, v: T) -> None:
1234
- super().__init__()
1301
+ ##
1235
1302
 
1236
- self._v = v
1237
1303
 
1238
- @property
1239
- def present(self) -> bool:
1240
- return True
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
- _hash: int
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/lite/timeouts.py
1496
- """
1497
- TODO:
1498
- - Event (/ Predicate)
1499
- """
1396
+ # ../../../omlish/logs/filters.py
1500
1397
 
1501
1398
 
1502
1399
  ##
1503
1400
 
1504
1401
 
1505
- class Timeout(abc.ABC):
1506
- @property
1507
- @abc.abstractmethod
1508
- def can_expire(self) -> bool:
1509
- """Indicates whether or not this timeout will ever expire."""
1510
-
1511
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
1419
+ class ProxyLogFilterer(logging.Filterer):
1420
+ def __init__(self, underlying: logging.Filterer) -> None: # noqa
1421
+ self._underlying = underlying
1530
1422
 
1531
- @abc.abstractmethod
1532
- def or_(self, o: ta.Any) -> ta.Any:
1533
- """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
1423
+ @property
1424
+ def underlying(self) -> logging.Filterer:
1425
+ return self._underlying
1534
1426
 
1535
- raise NotImplementedError
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
- @classmethod
1540
- def _now(cls) -> float:
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
- class DEFAULT: # Noqa
1546
- def __new__(cls, *args, **kwargs): # noqa
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
- @classmethod
1554
- def of(
1555
- cls,
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
- elif isinstance(obj, Timeout):
1563
- return obj
1449
+ _underlying: logging.Handler
1564
1450
 
1565
- elif isinstance(obj, (float, int)):
1566
- return DeadlineTimeout(cls._now() + obj)
1451
+ @property
1452
+ def underlying(self) -> logging.Handler:
1453
+ return self._underlying
1567
1454
 
1568
- elif isinstance(obj, ta.Iterable):
1569
- return CompositeTimeout(*[Timeout.of(c) for c in obj])
1455
+ def get_name(self):
1456
+ return self._underlying.get_name()
1570
1457
 
1571
- elif obj is Timeout.DEFAULT:
1572
- if default is Timeout._NOT_SPECIFIED or default is Timeout.DEFAULT:
1573
- raise RuntimeError('Must specify a default timeout')
1458
+ def set_name(self, name):
1459
+ self._underlying.set_name(name)
1574
1460
 
1575
- else:
1576
- return Timeout.of(default) # type: ignore[arg-type]
1461
+ @property
1462
+ def name(self): # type: ignore[override]
1463
+ return self._underlying.name
1577
1464
 
1578
- else:
1579
- raise TypeError(obj)
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):
@@ -1833,7 +1535,7 @@ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
1833
1535
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
1834
1536
  # Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
1835
1537
  # details.
1836
- # https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/specifiers.py
1538
+ # https://github.com/pypa/packaging/blob/48125006684bb2d7d28c50af48a03176da45942d/src/packaging/specifiers.py
1837
1539
 
1838
1540
 
1839
1541
  ##
@@ -1981,7 +1683,7 @@ class Specifier(BaseSpecifier):
1981
1683
  ) -> None:
1982
1684
  match = self._regex.search(spec)
1983
1685
  if not match:
1984
- raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
1686
+ raise InvalidSpecifier(f'Invalid specifier: {spec!r}')
1985
1687
 
1986
1688
  self._spec: ta.Tuple[str, str] = (
1987
1689
  match.group('operator').strip(),
@@ -1996,7 +1698,7 @@ class Specifier(BaseSpecifier):
1996
1698
  return self._prereleases
1997
1699
 
1998
1700
  operator, version = self._spec
1999
- if operator in ['==', '>=', '<=', '~=', '===']:
1701
+ if operator in ['==', '>=', '<=', '~=', '===', '>', '<']:
2000
1702
  if operator == '==' and version.endswith('.*'):
2001
1703
  version = version[:-2]
2002
1704
 
@@ -2122,40 +1824,38 @@ class Specifier(BaseSpecifier):
2122
1824
  return self.contains(item)
2123
1825
 
2124
1826
  def contains(self, item: UnparsedVersion, prereleases: ta.Optional[bool] = None) -> bool:
2125
- if prereleases is None:
2126
- prereleases = self.prereleases
2127
-
2128
- normalized_item = _coerce_version(item)
2129
-
2130
- if normalized_item.is_prerelease and not prereleases:
2131
- return False
2132
-
2133
- operator_callable: CallableVersionOperator = self._get_operator(self.operator)
2134
- return operator_callable(normalized_item, self.version)
1827
+ return bool(list(self.filter([item], prereleases=prereleases)))
2135
1828
 
2136
1829
  def filter(
2137
1830
  self,
2138
1831
  iterable: ta.Iterable[UnparsedVersionVar],
2139
1832
  prereleases: ta.Optional[bool] = None,
2140
1833
  ) -> ta.Iterator[UnparsedVersionVar]:
2141
- yielded = False
2142
- found_prereleases = []
1834
+ prereleases_versions = []
1835
+ found_non_prereleases = False
1836
+
1837
+ include_prereleases = (
1838
+ prereleases if prereleases is not None else self.prereleases
1839
+ )
2143
1840
 
2144
- kw = {'prereleases': prereleases if prereleases is not None else True}
1841
+ operator_callable = self._get_operator(self.operator)
2145
1842
 
2146
1843
  for version in iterable:
2147
1844
  parsed_version = _coerce_version(version)
2148
1845
 
2149
- if self.contains(parsed_version, **kw):
2150
- if parsed_version.is_prerelease and not (prereleases or self.prereleases):
2151
- found_prereleases.append(version)
2152
- else:
2153
- yielded = True
1846
+ if operator_callable(parsed_version, self.version):
1847
+ if not parsed_version.is_prerelease or include_prereleases:
1848
+ found_non_prereleases = True
2154
1849
  yield version
1850
+ elif prereleases is None and self._prereleases is not False:
1851
+ prereleases_versions.append(version)
2155
1852
 
2156
- if not yielded and found_prereleases:
2157
- for version in found_prereleases:
2158
- yield version
1853
+ if (
1854
+ not found_non_prereleases and
1855
+ prereleases is None and
1856
+ self._prereleases is not False
1857
+ ):
1858
+ yield from prereleases_versions
2159
1859
 
2160
1860
 
2161
1861
  _version_prefix_regex = re.compile(r'^([0-9]+)((?:a|b|c|rc)[0-9]+)$')
@@ -2206,12 +1906,15 @@ def _pad_version(left: ta.List[str], right: ta.List[str]) -> ta.Tuple[ta.List[st
2206
1906
  class SpecifierSet(BaseSpecifier):
2207
1907
  def __init__(
2208
1908
  self,
2209
- specifiers: str = '',
1909
+ specifiers: ta.Union[str, ta.Iterable['Specifier']] = '',
2210
1910
  prereleases: ta.Optional[bool] = None,
2211
1911
  ) -> None:
2212
- split_specifiers = [s.strip() for s in specifiers.split(',') if s.strip()]
1912
+ if isinstance(specifiers, str):
1913
+ split_specifiers = [s.strip() for s in specifiers.split(',') if s.strip()]
1914
+ self._specs = frozenset(map(Specifier, split_specifiers))
1915
+ else:
1916
+ self._specs = frozenset(specifiers)
2213
1917
 
2214
- self._specs = frozenset(map(Specifier, split_specifiers))
2215
1918
  self._prereleases = prereleases
2216
1919
 
2217
1920
  @property
@@ -2222,7 +1925,10 @@ class SpecifierSet(BaseSpecifier):
2222
1925
  if not self._specs:
2223
1926
  return None
2224
1927
 
2225
- return any(s.prereleases for s in self._specs)
1928
+ if any(s.prereleases for s in self._specs):
1929
+ return True
1930
+
1931
+ return None
2226
1932
 
2227
1933
  @prereleases.setter
2228
1934
  def prereleases(self, value: bool) -> None:
@@ -2289,28 +1995,22 @@ class SpecifierSet(BaseSpecifier):
2289
1995
  if not isinstance(item, Version):
2290
1996
  item = Version(item)
2291
1997
 
2292
- if prereleases is None:
2293
- prereleases = self.prereleases
2294
-
2295
- if not prereleases and item.is_prerelease:
2296
- return False
2297
-
2298
1998
  if installed and item.is_prerelease:
2299
- item = Version(item.base_version)
1999
+ prereleases = True
2300
2000
 
2301
- return all(s.contains(item, prereleases=prereleases) for s in self._specs)
2001
+ return bool(list(self.filter([item], prereleases=prereleases)))
2302
2002
 
2303
2003
  def filter(
2304
2004
  self,
2305
2005
  iterable: ta.Iterable[UnparsedVersionVar],
2306
2006
  prereleases: ta.Optional[bool] = None,
2307
2007
  ) -> ta.Iterator[UnparsedVersionVar]:
2308
- if prereleases is None:
2008
+ if prereleases is None and self.prereleases is not None:
2309
2009
  prereleases = self.prereleases
2310
2010
 
2311
2011
  if self._specs:
2312
2012
  for spec in self._specs:
2313
- iterable = spec.filter(iterable, prereleases=bool(prereleases))
2013
+ iterable = spec.filter(iterable, prereleases=prereleases)
2314
2014
  return iter(iterable)
2315
2015
 
2316
2016
  else:
@@ -2543,91 +2243,676 @@ class ArgparseCli:
2543
2243
  else:
2544
2244
  obj.dest = obj.kwargs['dest'] = att # type: ignore
2545
2245
 
2546
- 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
+ )
2547
2829
 
2548
- else:
2549
- raise TypeError(obj)
2550
2830
 
2551
- #
2831
+ @dc.dataclass(frozen=True)
2832
+ class InterpOpts:
2833
+ threaded: bool = False
2834
+ debug: bool = False
2552
2835
 
2553
- _parser: ta.ClassVar[argparse.ArgumentParser]
2836
+ def __str__(self) -> str:
2837
+ return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
2554
2838
 
2555
2839
  @classmethod
2556
- def get_parser(cls) -> argparse.ArgumentParser:
2557
- return cls._parser
2840
+ def parse(cls, s: str) -> 'InterpOpts':
2841
+ return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
2558
2842
 
2559
- @property
2560
- def argv(self) -> ta.Sequence[str]:
2561
- return self._argv
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)
2562
2849
 
2563
- @property
2564
- def args(self) -> argparse.Namespace:
2565
- return self._args
2566
2850
 
2567
- @property
2568
- def unknown_args(self) -> ta.Sequence[str]:
2569
- return self._unknown_args
2851
+ ##
2570
2852
 
2571
- #
2572
2853
 
2573
- def _bind_cli_cmd(self, cmd: ArgparseCmd) -> ta.Callable:
2574
- return cmd.__get__(self, type(self))
2854
+ @dc.dataclass(frozen=True)
2855
+ class InterpVersion:
2856
+ version: Version
2857
+ opts: InterpOpts
2575
2858
 
2576
- def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
2577
- cmd = getattr(self.args, '_cmd', None)
2859
+ def __str__(self) -> str:
2860
+ return str(self.version) + str(self.opts)
2578
2861
 
2579
- if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
2580
- msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
2581
- if (parser := self.get_parser()).exit_on_error:
2582
- parser.error(msg)
2583
- else:
2584
- raise argparse.ArgumentError(None, msg)
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
+ )
2585
2870
 
2586
- if cmd is None:
2587
- self.get_parser().print_help()
2871
+ @classmethod
2872
+ def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
2873
+ try:
2874
+ return cls.parse(s)
2875
+ except (KeyError, InvalidVersion):
2588
2876
  return None
2589
2877
 
2590
- return self._bind_cli_cmd(cmd)
2591
2878
 
2592
- #
2879
+ ##
2593
2880
 
2594
- def cli_run(self) -> ta.Optional[int]:
2595
- if (fn := self.prepare_cli_run()) is None:
2596
- return 0
2597
2881
 
2598
- return fn()
2882
+ @dc.dataclass(frozen=True)
2883
+ class InterpSpecifier:
2884
+ specifier: Specifier
2885
+ opts: InterpOpts
2599
2886
 
2600
- def cli_run_and_exit(self) -> ta.NoReturn:
2601
- sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
2887
+ def __str__(self) -> str:
2888
+ return str(self.specifier) + str(self.opts)
2602
2889
 
2603
- def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
2604
- if exit:
2605
- return self.cli_run_and_exit()
2606
- else:
2607
- return self.cli_run()
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
+ )
2608
2901
 
2609
- #
2902
+ def contains(self, iv: InterpVersion) -> bool:
2903
+ return self.specifier.contains(iv.version) and self.opts == iv.opts
2610
2904
 
2611
- async def async_cli_run(
2612
- self,
2613
- *,
2614
- force_async: bool = False,
2615
- ) -> ta.Optional[int]:
2616
- if (fn := self.prepare_cli_run()) is None:
2617
- return 0
2905
+ def __contains__(self, iv: InterpVersion) -> bool:
2906
+ return self.contains(iv)
2618
2907
 
2619
- if force_async:
2620
- is_async = True
2621
- else:
2622
- tfn = fn
2623
- if isinstance(tfn, ArgparseCmd):
2624
- tfn = tfn.fn
2625
- is_async = inspect.iscoroutinefunction(tfn)
2626
2908
 
2627
- if is_async:
2628
- return await fn()
2629
- else:
2630
- return fn()
2909
+ ##
2910
+
2911
+
2912
+ @dc.dataclass(frozen=True)
2913
+ class Interp:
2914
+ exe: str
2915
+ version: InterpVersion
2631
2916
 
2632
2917
 
2633
2918
  ########################################
@@ -2680,7 +2965,7 @@ def check_valid_injector_key_cls(cls: T) -> T:
2680
2965
  ##
2681
2966
 
2682
2967
 
2683
- class InjectorProvider(abc.ABC):
2968
+ class InjectorProvider(Abstract):
2684
2969
  @abc.abstractmethod
2685
2970
  def provider_fn(self) -> InjectorProviderFn:
2686
2971
  raise NotImplementedError
@@ -2699,7 +2984,7 @@ class InjectorBinding:
2699
2984
  check.isinstance(self.provider, InjectorProvider)
2700
2985
 
2701
2986
 
2702
- class InjectorBindings(abc.ABC):
2987
+ class InjectorBindings(Abstract):
2703
2988
  @abc.abstractmethod
2704
2989
  def bindings(self) -> ta.Iterator[InjectorBinding]:
2705
2990
  raise NotImplementedError
@@ -2707,7 +2992,7 @@ class InjectorBindings(abc.ABC):
2707
2992
  ##
2708
2993
 
2709
2994
 
2710
- class Injector(abc.ABC):
2995
+ class Injector(Abstract):
2711
2996
  @abc.abstractmethod
2712
2997
  def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
2713
2998
  raise NotImplementedError
@@ -2966,14 +3251,12 @@ def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) ->
2966
3251
  # scopes
2967
3252
 
2968
3253
 
2969
- class InjectorScope(abc.ABC): # noqa
3254
+ class InjectorScope(Abstract):
2970
3255
  def __init__(
2971
3256
  self,
2972
3257
  *,
2973
3258
  _i: Injector,
2974
3259
  ) -> None:
2975
- check.not_in(abc.ABC, type(self).__bases__)
2976
-
2977
3260
  super().__init__()
2978
3261
 
2979
3262
  self._i = _i
@@ -3004,7 +3287,7 @@ class InjectorScope(abc.ABC): # noqa
3004
3287
  raise NotImplementedError
3005
3288
 
3006
3289
 
3007
- class ExclusiveInjectorScope(InjectorScope, abc.ABC):
3290
+ class ExclusiveInjectorScope(InjectorScope, Abstract):
3008
3291
  _st: ta.Optional[InjectorScope.State] = None
3009
3292
 
3010
3293
  def state(self) -> InjectorScope.State:
@@ -3020,12 +3303,13 @@ class ExclusiveInjectorScope(InjectorScope, abc.ABC):
3020
3303
  self._st = None
3021
3304
 
3022
3305
 
3023
- class ContextvarInjectorScope(InjectorScope, abc.ABC):
3306
+ class ContextvarInjectorScope(InjectorScope, Abstract):
3024
3307
  _cv: contextvars.ContextVar
3025
3308
 
3026
3309
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
3027
3310
  super().__init_subclass__(**kwargs)
3028
3311
 
3312
+ check.not_in(Abstract, cls.__bases__)
3029
3313
  check.not_in(abc.ABC, cls.__bases__)
3030
3314
  check.state(not hasattr(cls, '_cv'))
3031
3315
  cls._cv = contextvars.ContextVar(f'{cls.__name__}_cv')
@@ -3531,7 +3815,7 @@ class InjectorBinder:
3531
3815
  pws: ta.List[ta.Any] = []
3532
3816
  if in_ is not None:
3533
3817
  check.issubclass(in_, InjectorScope)
3534
- check.not_in(abc.ABC, in_.__bases__)
3818
+ check.not_in(Abstract, in_.__bases__)
3535
3819
  pws.append(functools.partial(ScopedInjectorProvider, k=key, sc=in_))
3536
3820
  if singleton:
3537
3821
  pws.append(SingletonInjectorProvider)
@@ -3732,80 +4016,125 @@ inj = InjectionApi()
3732
4016
 
3733
4017
 
3734
4018
  ########################################
3735
- # ../../../omlish/lite/runtime.py
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
+ """
3736
4027
 
3737
4028
 
3738
4029
  ##
3739
4030
 
3740
4031
 
3741
- @cached_nullary
3742
- def is_debugger_attached() -> bool:
3743
- return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
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
+ ]
3744
4041
 
3745
4042
 
3746
- LITE_REQUIRED_PYTHON_VERSION = (3, 8)
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)
3747
4047
 
4048
+ converter = datetime.datetime.fromtimestamp # type: ignore
3748
4049
 
3749
- def check_lite_runtime_version() -> None:
3750
- if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
3751
- raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
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
3752
4057
 
3753
4058
 
3754
- ########################################
3755
- # ../../../omlish/logs/json.py
3756
- """
3757
- TODO:
3758
- - translate json keys
3759
- """
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.')
3760
4065
 
3761
4066
 
3762
4067
  ##
3763
4068
 
3764
4069
 
3765
- class JsonLogFormatter(logging.Formatter):
3766
- KEYS: ta.Mapping[str, bool] = {
3767
- 'name': False,
3768
- 'msg': False,
3769
- 'args': False,
3770
- 'levelname': False,
3771
- 'levelno': False,
3772
- 'pathname': False,
3773
- 'filename': False,
3774
- 'module': False,
3775
- 'exc_info': True,
3776
- 'exc_text': True,
3777
- 'stack_info': True,
3778
- 'lineno': False,
3779
- 'funcName': False,
3780
- 'created': False,
3781
- 'msecs': False,
3782
- 'relativeCreated': False,
3783
- 'thread': False,
3784
- 'threadName': False,
3785
- 'processName': False,
3786
- 'process': False,
3787
- }
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())
3788
4125
 
3789
- def __init__(
3790
- self,
3791
- *args: ta.Any,
3792
- json_dumps: ta.Optional[ta.Callable[[ta.Any], str]] = None,
3793
- **kwargs: ta.Any,
3794
- ) -> None:
3795
- super().__init__(*args, **kwargs)
4126
+ #
3796
4127
 
3797
- if json_dumps is None:
3798
- json_dumps = json_dumps_compact
3799
- self._json_dumps = json_dumps
4128
+ target.addHandler(handler)
3800
4129
 
3801
- def format(self, record: logging.LogRecord) -> str:
3802
- dct = {
3803
- k: v
3804
- for k, o in self.KEYS.items()
3805
- for v in [getattr(record, k)]
3806
- if not (o and v is None)
3807
- }
3808
- return self._json_dumps(dct)
4130
+ #
4131
+
4132
+ if level is not None:
4133
+ target.setLevel(level)
4134
+
4135
+ #
4136
+
4137
+ return StandardConfiguredLogHandler(handler)
3809
4138
 
3810
4139
 
3811
4140
  ########################################
@@ -3924,7 +4253,7 @@ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessR
3924
4253
  ##
3925
4254
 
3926
4255
 
3927
- class SubprocessRunnable(abc.ABC, ta.Generic[T]):
4256
+ class SubprocessRunnable(Abstract, ta.Generic[T]):
3928
4257
  @abc.abstractmethod
3929
4258
  def make_run(self) -> SubprocessRun:
3930
4259
  raise NotImplementedError
@@ -3957,233 +4286,6 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
3957
4286
  return self.handle_run_output(await self.make_run().maysync_run(maysync_subprocesses, **kwargs))
3958
4287
 
3959
4288
 
3960
- ########################################
3961
- # ../types.py
3962
-
3963
-
3964
- ##
3965
-
3966
-
3967
- # See https://peps.python.org/pep-3149/
3968
- INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
3969
- ('debug', 'd'),
3970
- ('threaded', 't'),
3971
- ])
3972
-
3973
- INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
3974
- (g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
3975
- )
3976
-
3977
-
3978
- @dc.dataclass(frozen=True)
3979
- class InterpOpts:
3980
- threaded: bool = False
3981
- debug: bool = False
3982
-
3983
- def __str__(self) -> str:
3984
- return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
3985
-
3986
- @classmethod
3987
- def parse(cls, s: str) -> 'InterpOpts':
3988
- return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
3989
-
3990
- @classmethod
3991
- def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
3992
- kw = {}
3993
- while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
3994
- s, kw[a] = s[:-1], True
3995
- return s, cls(**kw)
3996
-
3997
-
3998
- ##
3999
-
4000
-
4001
- @dc.dataclass(frozen=True)
4002
- class InterpVersion:
4003
- version: Version
4004
- opts: InterpOpts
4005
-
4006
- def __str__(self) -> str:
4007
- return str(self.version) + str(self.opts)
4008
-
4009
- @classmethod
4010
- def parse(cls, s: str) -> 'InterpVersion':
4011
- s, o = InterpOpts.parse_suffix(s)
4012
- v = Version(s)
4013
- return cls(
4014
- version=v,
4015
- opts=o,
4016
- )
4017
-
4018
- @classmethod
4019
- def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
4020
- try:
4021
- return cls.parse(s)
4022
- except (KeyError, InvalidVersion):
4023
- return None
4024
-
4025
-
4026
- ##
4027
-
4028
-
4029
- @dc.dataclass(frozen=True)
4030
- class InterpSpecifier:
4031
- specifier: Specifier
4032
- opts: InterpOpts
4033
-
4034
- def __str__(self) -> str:
4035
- return str(self.specifier) + str(self.opts)
4036
-
4037
- @classmethod
4038
- def parse(cls, s: str) -> 'InterpSpecifier':
4039
- s, o = InterpOpts.parse_suffix(s)
4040
- if not any(s.startswith(o) for o in Specifier.OPERATORS):
4041
- s = '~=' + s
4042
- if s.count('.') < 2:
4043
- s += '.0'
4044
- return cls(
4045
- specifier=Specifier(s),
4046
- opts=o,
4047
- )
4048
-
4049
- def contains(self, iv: InterpVersion) -> bool:
4050
- return self.specifier.contains(iv.version) and self.opts == iv.opts
4051
-
4052
- def __contains__(self, iv: InterpVersion) -> bool:
4053
- return self.contains(iv)
4054
-
4055
-
4056
- ##
4057
-
4058
-
4059
- @dc.dataclass(frozen=True)
4060
- class Interp:
4061
- exe: str
4062
- version: InterpVersion
4063
-
4064
-
4065
- ########################################
4066
- # ../../../omlish/logs/standard.py
4067
- """
4068
- TODO:
4069
- - structured
4070
- - prefixed
4071
- - debug
4072
- - optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
4073
- """
4074
-
4075
-
4076
- ##
4077
-
4078
-
4079
- STANDARD_LOG_FORMAT_PARTS = [
4080
- ('asctime', '%(asctime)-15s'),
4081
- ('process', 'pid=%(process)s'),
4082
- ('thread', 'tid=%(thread)x'),
4083
- ('levelname', '%(levelname)s'),
4084
- ('name', '%(name)s'),
4085
- ('separator', '::'),
4086
- ('message', '%(message)s'),
4087
- ]
4088
-
4089
-
4090
- class StandardLogFormatter(logging.Formatter):
4091
- @staticmethod
4092
- def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
4093
- return ' '.join(v for k, v in parts)
4094
-
4095
- converter = datetime.datetime.fromtimestamp # type: ignore
4096
-
4097
- def formatTime(self, record, datefmt=None):
4098
- ct = self.converter(record.created)
4099
- if datefmt:
4100
- return ct.strftime(datefmt) # noqa
4101
- else:
4102
- t = ct.strftime('%Y-%m-%d %H:%M:%S')
4103
- return '%s.%03d' % (t, record.msecs) # noqa
4104
-
4105
-
4106
- ##
4107
-
4108
-
4109
- class StandardConfiguredLogHandler(ProxyLogHandler):
4110
- def __init_subclass__(cls, **kwargs):
4111
- raise TypeError('This class serves only as a marker and should not be subclassed.')
4112
-
4113
-
4114
- ##
4115
-
4116
-
4117
- @contextlib.contextmanager
4118
- def _locking_logging_module_lock() -> ta.Iterator[None]:
4119
- if hasattr(logging, '_acquireLock'):
4120
- logging._acquireLock() # noqa
4121
- try:
4122
- yield
4123
- finally:
4124
- logging._releaseLock() # type: ignore # noqa
4125
-
4126
- elif hasattr(logging, '_lock'):
4127
- # https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
4128
- with logging._lock: # noqa
4129
- yield
4130
-
4131
- else:
4132
- raise Exception("Can't find lock in logging module")
4133
-
4134
-
4135
- def configure_standard_logging(
4136
- level: ta.Union[int, str] = logging.INFO,
4137
- *,
4138
- json: bool = False,
4139
- target: ta.Optional[logging.Logger] = None,
4140
- force: bool = False,
4141
- handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
4142
- ) -> ta.Optional[StandardConfiguredLogHandler]:
4143
- with _locking_logging_module_lock():
4144
- if target is None:
4145
- target = logging.root
4146
-
4147
- #
4148
-
4149
- if not force:
4150
- if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
4151
- return None
4152
-
4153
- #
4154
-
4155
- if handler_factory is not None:
4156
- handler = handler_factory()
4157
- else:
4158
- handler = logging.StreamHandler()
4159
-
4160
- #
4161
-
4162
- formatter: logging.Formatter
4163
- if json:
4164
- formatter = JsonLogFormatter()
4165
- else:
4166
- formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
4167
- handler.setFormatter(formatter)
4168
-
4169
- #
4170
-
4171
- handler.addFilter(TidLogFilter())
4172
-
4173
- #
4174
-
4175
- target.addHandler(handler)
4176
-
4177
- #
4178
-
4179
- if level is not None:
4180
- target.setLevel(level)
4181
-
4182
- #
4183
-
4184
- return StandardConfiguredLogHandler(handler)
4185
-
4186
-
4187
4289
  ########################################
4188
4290
  # ../../../omlish/subprocesses/wrap.py
4189
4291
  """
@@ -4225,13 +4327,13 @@ TODO:
4225
4327
  ##
4226
4328
 
4227
4329
 
4228
- class InterpProvider(abc.ABC):
4330
+ class InterpProvider(Abstract):
4229
4331
  name: ta.ClassVar[str]
4230
4332
 
4231
4333
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
4232
4334
  super().__init_subclass__(**kwargs)
4233
4335
 
4234
- if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
4336
+ if Abstract not in cls.__bases__ and 'name' not in cls.__dict__:
4235
4337
  sfx = 'InterpProvider'
4236
4338
  if not cls.__name__.endswith(sfx):
4237
4339
  raise NameError(cls)
@@ -4297,7 +4399,7 @@ class VerboseCalledProcessError(subprocess.CalledProcessError):
4297
4399
  return msg
4298
4400
 
4299
4401
 
4300
- class BaseSubprocesses(abc.ABC): # noqa
4402
+ class BaseSubprocesses(Abstract):
4301
4403
  DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
4302
4404
 
4303
4405
  def __init__(
@@ -4562,7 +4664,7 @@ class InterpResolver:
4562
4664
  ##
4563
4665
 
4564
4666
 
4565
- class AbstractAsyncSubprocesses(BaseSubprocesses):
4667
+ class AbstractAsyncSubprocesses(BaseSubprocesses, Abstract):
4566
4668
  @abc.abstractmethod
4567
4669
  def run_(self, run: SubprocessRun) -> ta.Awaitable[SubprocessRunOutput]:
4568
4670
  raise NotImplementedError
@@ -5328,7 +5430,7 @@ THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
5328
5430
  #
5329
5431
 
5330
5432
 
5331
- class PyenvInstallOptsProvider(abc.ABC):
5433
+ class PyenvInstallOptsProvider(Abstract):
5332
5434
  @abc.abstractmethod
5333
5435
  def opts(self) -> ta.Awaitable[PyenvInstallOpts]:
5334
5436
  raise NotImplementedError