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/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):
@@ -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
- _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))
2552
2838
 
2553
2839
  @classmethod
2554
- def get_parser(cls) -> argparse.ArgumentParser:
2555
- return cls._parser
2840
+ def parse(cls, s: str) -> 'InterpOpts':
2841
+ return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
2556
2842
 
2557
- @property
2558
- def argv(self) -> ta.Sequence[str]:
2559
- 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)
2560
2849
 
2561
- @property
2562
- def args(self) -> argparse.Namespace:
2563
- return self._args
2564
2850
 
2565
- @property
2566
- def unknown_args(self) -> ta.Sequence[str]:
2567
- return self._unknown_args
2851
+ ##
2568
2852
 
2569
- #
2570
2853
 
2571
- def _bind_cli_cmd(self, cmd: ArgparseCmd) -> ta.Callable:
2572
- return cmd.__get__(self, type(self))
2854
+ @dc.dataclass(frozen=True)
2855
+ class InterpVersion:
2856
+ version: Version
2857
+ opts: InterpOpts
2573
2858
 
2574
- def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
2575
- cmd = getattr(self.args, '_cmd', None)
2859
+ def __str__(self) -> str:
2860
+ return str(self.version) + str(self.opts)
2576
2861
 
2577
- if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
2578
- msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
2579
- if (parser := self.get_parser()).exit_on_error:
2580
- parser.error(msg)
2581
- else:
2582
- 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
+ )
2583
2870
 
2584
- if cmd is None:
2585
- 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):
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
- return fn()
2882
+ @dc.dataclass(frozen=True)
2883
+ class InterpSpecifier:
2884
+ specifier: Specifier
2885
+ opts: InterpOpts
2597
2886
 
2598
- def cli_run_and_exit(self) -> ta.NoReturn:
2599
- 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)
2600
2889
 
2601
- def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
2602
- if exit:
2603
- return self.cli_run_and_exit()
2604
- else:
2605
- 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
+ )
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
- async def async_cli_run(
2610
- self,
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
- if is_async:
2626
- return await fn()
2627
- else:
2628
- return fn()
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(abc.ABC):
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(abc.ABC):
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(abc.ABC):
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(abc.ABC): # noqa
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, abc.ABC):
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, abc.ABC):
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(abc.ABC, in_.__bases__)
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/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
+ """
3734
4027
 
3735
4028
 
3736
4029
  ##
3737
4030
 
3738
4031
 
3739
- @cached_nullary
3740
- def is_debugger_attached() -> bool:
3741
- 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
+ ]
3742
4041
 
3743
4042
 
3744
- 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)
3745
4047
 
4048
+ converter = datetime.datetime.fromtimestamp # type: ignore
3746
4049
 
3747
- def check_lite_runtime_version() -> None:
3748
- if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
3749
- 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
3750
4057
 
3751
4058
 
3752
- ########################################
3753
- # ../../../omlish/logs/json.py
3754
- """
3755
- TODO:
3756
- - translate json keys
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
- class JsonLogFormatter(logging.Formatter):
3764
- KEYS: ta.Mapping[str, bool] = {
3765
- 'name': False,
3766
- 'msg': False,
3767
- 'args': False,
3768
- 'levelname': False,
3769
- 'levelno': False,
3770
- 'pathname': False,
3771
- 'filename': False,
3772
- 'module': False,
3773
- 'exc_info': True,
3774
- 'exc_text': True,
3775
- 'stack_info': True,
3776
- 'lineno': False,
3777
- 'funcName': False,
3778
- 'created': False,
3779
- 'msecs': False,
3780
- 'relativeCreated': False,
3781
- 'thread': False,
3782
- 'threadName': False,
3783
- 'processName': False,
3784
- 'process': False,
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
- def __init__(
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
- if json_dumps is None:
3796
- json_dumps = json_dumps_compact
3797
- self._json_dumps = json_dumps
4128
+ target.addHandler(handler)
3798
4129
 
3799
- def format(self, record: logging.LogRecord) -> str:
3800
- dct = {
3801
- k: v
3802
- for k, o in self.KEYS.items()
3803
- for v in [getattr(record, k)]
3804
- if not (o and v is None)
3805
- }
3806
- return self._json_dumps(dct)
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(abc.ABC, ta.Generic[T]):
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(abc.ABC):
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 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__:
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(abc.ABC): # noqa
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(abc.ABC):
5433
+ class PyenvInstallOptsProvider(Abstract):
5330
5434
  @abc.abstractmethod
5331
5435
  def opts(self) -> ta.Awaitable[PyenvInstallOpts]:
5332
5436
  raise NotImplementedError