ominfra 0.0.0.dev148__py3-none-any.whl → 0.0.0.dev150__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.
Files changed (36) hide show
  1. ominfra/clouds/aws/auth.py +7 -9
  2. ominfra/clouds/aws/cli.py +1 -1
  3. ominfra/clouds/aws/journald2aws/driver.py +4 -4
  4. ominfra/clouds/aws/logs.py +4 -5
  5. ominfra/clouds/gcp/auth.py +1 -1
  6. ominfra/configs.py +3 -4
  7. ominfra/journald/messages.py +3 -3
  8. ominfra/journald/tailer.py +2 -2
  9. ominfra/manage/commands/base.py +2 -2
  10. ominfra/manage/commands/interp.py +3 -3
  11. ominfra/manage/commands/subprocess.py +3 -4
  12. ominfra/manage/deploy/paths.py +12 -15
  13. ominfra/manage/main.py +72 -75
  14. ominfra/manage/remote/_main.py +6 -7
  15. ominfra/manage/remote/execution.py +7 -10
  16. ominfra/manage/remote/spawning.py +3 -3
  17. ominfra/scripts/journald2aws.py +508 -147
  18. ominfra/scripts/manage.py +772 -183
  19. ominfra/scripts/supervisor.py +1144 -783
  20. ominfra/supervisor/dispatchers.py +1 -1
  21. ominfra/supervisor/groupsimpl.py +2 -2
  22. ominfra/supervisor/http.py +7 -7
  23. ominfra/supervisor/inject.py +4 -4
  24. ominfra/supervisor/io.py +1 -1
  25. ominfra/supervisor/main.py +1 -1
  26. ominfra/supervisor/processimpl.py +2 -2
  27. ominfra/supervisor/spawningimpl.py +9 -10
  28. ominfra/supervisor/supervisor.py +3 -3
  29. ominfra/supervisor/types.py +1 -1
  30. ominfra/tailscale/cli.py +1 -1
  31. {ominfra-0.0.0.dev148.dist-info → ominfra-0.0.0.dev150.dist-info}/METADATA +3 -3
  32. {ominfra-0.0.0.dev148.dist-info → ominfra-0.0.0.dev150.dist-info}/RECORD +36 -36
  33. {ominfra-0.0.0.dev148.dist-info → ominfra-0.0.0.dev150.dist-info}/LICENSE +0 -0
  34. {ominfra-0.0.0.dev148.dist-info → ominfra-0.0.0.dev150.dist-info}/WHEEL +0 -0
  35. {ominfra-0.0.0.dev148.dist-info → ominfra-0.0.0.dev150.dist-info}/entry_points.txt +0 -0
  36. {ominfra-0.0.0.dev148.dist-info → ominfra-0.0.0.dev150.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py CHANGED
@@ -9,6 +9,7 @@ manage.py -s 'docker run -i python:3.12'
9
9
  manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
10
10
  """
11
11
  import abc
12
+ import argparse
12
13
  import asyncio
13
14
  import asyncio.base_subprocess
14
15
  import asyncio.subprocess
@@ -76,6 +77,11 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
76
77
 
77
78
  # ../../omlish/lite/check.py
78
79
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
80
+ CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
81
+ CheckLateConfigureFn = ta.Callable[['Checks'], None]
82
+ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
83
+ CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
84
+ CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
79
85
 
80
86
  # ../../omdev/packaging/specifiers.py
81
87
  UnparsedVersion = ta.Union['Version', str]
@@ -86,6 +92,9 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
86
92
  CommandT = ta.TypeVar('CommandT', bound='Command')
87
93
  CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
88
94
 
95
+ # ../../omlish/argparse/cli.py
96
+ ArgparseCommandFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
97
+
89
98
  # ../../omlish/lite/inject.py
90
99
  U = ta.TypeVar('U')
91
100
  InjectorKeyCls = ta.Union[type, ta.NewType]
@@ -1152,99 +1161,454 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
1152
1161
 
1153
1162
  ########################################
1154
1163
  # ../../../omlish/lite/check.py
1164
+ """
1165
+ TODO:
1166
+ - def maybe(v: lang.Maybe[T])
1167
+ - patch / override lite.check ?
1168
+ - checker interface?
1169
+ """
1170
+
1171
+
1172
+ ##
1173
+
1174
+
1175
+ class Checks:
1176
+ def __init__(self) -> None:
1177
+ super().__init__()
1178
+
1179
+ self._config_lock = threading.RLock()
1180
+ self._on_raise_fns: ta.Sequence[CheckOnRaiseFn] = []
1181
+ self._exception_factory: CheckExceptionFactory = Checks.default_exception_factory
1182
+ self._args_renderer: ta.Optional[CheckArgsRenderer] = None
1183
+ self._late_configure_fns: ta.Sequence[CheckLateConfigureFn] = []
1184
+
1185
+ @staticmethod
1186
+ def default_exception_factory(exc_cls: ta.Type[Exception], *args, **kwargs) -> Exception:
1187
+ return exc_cls(*args, **kwargs) # noqa
1188
+
1189
+ #
1190
+
1191
+ def register_on_raise(self, fn: CheckOnRaiseFn) -> None:
1192
+ with self._config_lock:
1193
+ self._on_raise_fns = [*self._on_raise_fns, fn]
1194
+
1195
+ def unregister_on_raise(self, fn: CheckOnRaiseFn) -> None:
1196
+ with self._config_lock:
1197
+ self._on_raise_fns = [e for e in self._on_raise_fns if e != fn]
1198
+
1199
+ #
1200
+
1201
+ def set_exception_factory(self, factory: CheckExceptionFactory) -> None:
1202
+ self._exception_factory = factory
1203
+
1204
+ def set_args_renderer(self, renderer: ta.Optional[CheckArgsRenderer]) -> None:
1205
+ self._args_renderer = renderer
1206
+
1207
+ #
1208
+
1209
+ def register_late_configure(self, fn: CheckLateConfigureFn) -> None:
1210
+ with self._config_lock:
1211
+ self._late_configure_fns = [*self._late_configure_fns, fn]
1155
1212
 
1213
+ def _late_configure(self) -> None:
1214
+ if not self._late_configure_fns:
1215
+ return
1156
1216
 
1157
- def check_isinstance(v: ta.Any, spec: ta.Union[ta.Type[T], tuple]) -> T:
1158
- if not isinstance(v, spec):
1159
- raise TypeError(v)
1160
- return v
1217
+ with self._config_lock:
1218
+ if not (lc := self._late_configure_fns):
1219
+ return
1161
1220
 
1221
+ for fn in lc:
1222
+ fn(self)
1162
1223
 
1163
- def check_not_isinstance(v: T, spec: ta.Union[type, tuple]) -> T:
1164
- if isinstance(v, spec):
1165
- raise TypeError(v)
1166
- return v
1224
+ self._late_configure_fns = []
1167
1225
 
1226
+ #
1168
1227
 
1169
- def check_none(v: T) -> None:
1170
- if v is not None:
1171
- raise ValueError(v)
1228
+ class _ArgsKwargs:
1229
+ def __init__(self, *args, **kwargs):
1230
+ self.args = args
1231
+ self.kwargs = kwargs
1172
1232
 
1233
+ def _raise(
1234
+ self,
1235
+ exception_type: ta.Type[Exception],
1236
+ default_message: str,
1237
+ message: CheckMessage,
1238
+ ak: _ArgsKwargs = _ArgsKwargs(),
1239
+ *,
1240
+ render_fmt: ta.Optional[str] = None,
1241
+ ) -> ta.NoReturn:
1242
+ exc_args = ()
1243
+ if callable(message):
1244
+ message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
1245
+ if isinstance(message, tuple):
1246
+ message, *exc_args = message # type: ignore
1247
+
1248
+ if message is None:
1249
+ message = default_message
1250
+
1251
+ self._late_configure()
1252
+
1253
+ if render_fmt is not None and (af := self._args_renderer) is not None:
1254
+ rendered_args = af(render_fmt, *ak.args)
1255
+ if rendered_args is not None:
1256
+ message = f'{message} : {rendered_args}'
1257
+
1258
+ exc = self._exception_factory(
1259
+ exception_type,
1260
+ message,
1261
+ *exc_args,
1262
+ *ak.args,
1263
+ **ak.kwargs,
1264
+ )
1173
1265
 
1174
- def check_not_none(v: ta.Optional[T]) -> T:
1175
- if v is None:
1176
- raise ValueError
1177
- return v
1266
+ for fn in self._on_raise_fns:
1267
+ fn(exc)
1178
1268
 
1269
+ raise exc
1179
1270
 
1180
- def check_not(v: ta.Any) -> None:
1181
- if v:
1182
- raise ValueError(v)
1183
- return v
1271
+ #
1184
1272
 
1273
+ def _unpack_isinstance_spec(self, spec: ta.Any) -> tuple:
1274
+ if isinstance(spec, type):
1275
+ return (spec,)
1276
+ if not isinstance(spec, tuple):
1277
+ spec = (spec,)
1278
+ if None in spec:
1279
+ spec = tuple(filter(None, spec)) + (None.__class__,) # noqa
1280
+ if ta.Any in spec:
1281
+ spec = (object,)
1282
+ return spec
1283
+
1284
+ def isinstance(self, v: ta.Any, spec: ta.Union[ta.Type[T], tuple], msg: CheckMessage = None) -> T: # noqa
1285
+ if not isinstance(v, self._unpack_isinstance_spec(spec)):
1286
+ self._raise(
1287
+ TypeError,
1288
+ 'Must be instance',
1289
+ msg,
1290
+ Checks._ArgsKwargs(v, spec),
1291
+ render_fmt='not isinstance(%s, %s)',
1292
+ )
1185
1293
 
1186
- def check_non_empty_str(v: ta.Optional[str]) -> str:
1187
- if not v:
1188
- raise ValueError
1189
- return v
1294
+ return v
1190
1295
 
1296
+ def of_isinstance(self, spec: ta.Union[ta.Type[T], tuple], msg: CheckMessage = None) -> ta.Callable[[ta.Any], T]:
1297
+ def inner(v):
1298
+ return self.isinstance(v, self._unpack_isinstance_spec(spec), msg)
1191
1299
 
1192
- def check_state(v: bool, msg: str = 'Illegal state') -> None:
1193
- if not v:
1194
- raise ValueError(msg)
1300
+ return inner
1195
1301
 
1302
+ def cast(self, v: ta.Any, cls: ta.Type[T], msg: CheckMessage = None) -> T: # noqa
1303
+ if not isinstance(v, cls):
1304
+ self._raise(
1305
+ TypeError,
1306
+ 'Must be instance',
1307
+ msg,
1308
+ Checks._ArgsKwargs(v, cls),
1309
+ )
1310
+
1311
+ return v
1312
+
1313
+ def of_cast(self, cls: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[T], T]:
1314
+ def inner(v):
1315
+ return self.cast(v, cls, msg)
1196
1316
 
1197
- def check_equal(l: T, r: T) -> T:
1198
- if l != r:
1199
- raise ValueError(l, r)
1200
- return l
1317
+ return inner
1201
1318
 
1319
+ def not_isinstance(self, v: T, spec: ta.Any, msg: CheckMessage = None) -> T: # noqa
1320
+ if isinstance(v, self._unpack_isinstance_spec(spec)):
1321
+ self._raise(
1322
+ TypeError,
1323
+ 'Must not be instance',
1324
+ msg,
1325
+ Checks._ArgsKwargs(v, spec),
1326
+ render_fmt='isinstance(%s, %s)',
1327
+ )
1202
1328
 
1203
- def check_not_equal(l: T, r: T) -> T:
1204
- if l == r:
1205
- raise ValueError(l, r)
1206
- return l
1329
+ return v
1207
1330
 
1331
+ def of_not_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[T], T]:
1332
+ def inner(v):
1333
+ return self.not_isinstance(v, self._unpack_isinstance_spec(spec), msg)
1208
1334
 
1209
- def check_is(l: T, r: T) -> T:
1210
- if l is not r:
1211
- raise ValueError(l, r)
1212
- return l
1335
+ return inner
1213
1336
 
1337
+ ##
1214
1338
 
1215
- def check_is_not(l: T, r: ta.Any) -> T:
1216
- if l is r:
1217
- raise ValueError(l, r)
1218
- return l
1339
+ def issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
1340
+ if not issubclass(v, spec):
1341
+ self._raise(
1342
+ TypeError,
1343
+ 'Must be subclass',
1344
+ msg,
1345
+ Checks._ArgsKwargs(v, spec),
1346
+ render_fmt='not issubclass(%s, %s)',
1347
+ )
1219
1348
 
1349
+ return v
1220
1350
 
1221
- def check_in(v: T, c: ta.Container[T]) -> T:
1222
- if v not in c:
1223
- raise ValueError(v, c)
1224
- return v
1351
+ def not_issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
1352
+ if issubclass(v, spec):
1353
+ self._raise(
1354
+ TypeError,
1355
+ 'Must not be subclass',
1356
+ msg,
1357
+ Checks._ArgsKwargs(v, spec),
1358
+ render_fmt='issubclass(%s, %s)',
1359
+ )
1225
1360
 
1361
+ return v
1226
1362
 
1227
- def check_not_in(v: T, c: ta.Container[T]) -> T:
1228
- if v in c:
1229
- raise ValueError(v, c)
1230
- return v
1363
+ #
1364
+
1365
+ def in_(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
1366
+ if v not in c:
1367
+ self._raise(
1368
+ ValueError,
1369
+ 'Must be in',
1370
+ msg,
1371
+ Checks._ArgsKwargs(v, c),
1372
+ render_fmt='%s not in %s',
1373
+ )
1374
+
1375
+ return v
1376
+
1377
+ def not_in(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
1378
+ if v in c:
1379
+ self._raise(
1380
+ ValueError,
1381
+ 'Must not be in',
1382
+ msg,
1383
+ Checks._ArgsKwargs(v, c),
1384
+ render_fmt='%s in %s',
1385
+ )
1386
+
1387
+ return v
1388
+
1389
+ def empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
1390
+ if len(v) != 0:
1391
+ self._raise(
1392
+ ValueError,
1393
+ 'Must be empty',
1394
+ msg,
1395
+ Checks._ArgsKwargs(v),
1396
+ render_fmt='%s',
1397
+ )
1398
+
1399
+ return v
1400
+
1401
+ def iterempty(self, v: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
1402
+ it = iter(v)
1403
+ try:
1404
+ next(it)
1405
+ except StopIteration:
1406
+ pass
1407
+ else:
1408
+ self._raise(
1409
+ ValueError,
1410
+ 'Must be empty',
1411
+ msg,
1412
+ Checks._ArgsKwargs(v),
1413
+ render_fmt='%s',
1414
+ )
1415
+
1416
+ return v
1417
+
1418
+ def not_empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
1419
+ if len(v) == 0:
1420
+ self._raise(
1421
+ ValueError,
1422
+ 'Must not be empty',
1423
+ msg,
1424
+ Checks._ArgsKwargs(v),
1425
+ render_fmt='%s',
1426
+ )
1427
+
1428
+ return v
1429
+
1430
+ def unique(self, it: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
1431
+ dupes = [e for e, c in collections.Counter(it).items() if c > 1]
1432
+ if dupes:
1433
+ self._raise(
1434
+ ValueError,
1435
+ 'Must be unique',
1436
+ msg,
1437
+ Checks._ArgsKwargs(it, dupes),
1438
+ )
1439
+
1440
+ return it
1441
+
1442
+ def single(self, obj: ta.Iterable[T], message: CheckMessage = None) -> T:
1443
+ try:
1444
+ [value] = obj
1445
+ except ValueError:
1446
+ self._raise(
1447
+ ValueError,
1448
+ 'Must be single',
1449
+ message,
1450
+ Checks._ArgsKwargs(obj),
1451
+ render_fmt='%s',
1452
+ )
1453
+
1454
+ return value
1455
+
1456
+ def opt_single(self, obj: ta.Iterable[T], message: CheckMessage = None) -> ta.Optional[T]:
1457
+ it = iter(obj)
1458
+ try:
1459
+ value = next(it)
1460
+ except StopIteration:
1461
+ return None
1462
+
1463
+ try:
1464
+ next(it)
1465
+ except StopIteration:
1466
+ return value # noqa
1467
+
1468
+ self._raise(
1469
+ ValueError,
1470
+ 'Must be empty or single',
1471
+ message,
1472
+ Checks._ArgsKwargs(obj),
1473
+ render_fmt='%s',
1474
+ )
1475
+
1476
+ raise RuntimeError # noqa
1477
+
1478
+ #
1479
+
1480
+ def none(self, v: ta.Any, msg: CheckMessage = None) -> None:
1481
+ if v is not None:
1482
+ self._raise(
1483
+ ValueError,
1484
+ 'Must be None',
1485
+ msg,
1486
+ Checks._ArgsKwargs(v),
1487
+ render_fmt='%s',
1488
+ )
1489
+
1490
+ def not_none(self, v: ta.Optional[T], msg: CheckMessage = None) -> T:
1491
+ if v is None:
1492
+ self._raise(
1493
+ ValueError,
1494
+ 'Must not be None',
1495
+ msg,
1496
+ Checks._ArgsKwargs(v),
1497
+ render_fmt='%s',
1498
+ )
1499
+
1500
+ return v
1501
+
1502
+ #
1503
+
1504
+ def equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
1505
+ if o != v:
1506
+ self._raise(
1507
+ ValueError,
1508
+ 'Must be equal',
1509
+ msg,
1510
+ Checks._ArgsKwargs(v, o),
1511
+ render_fmt='%s != %s',
1512
+ )
1513
+
1514
+ return v
1515
+
1516
+ def is_(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
1517
+ if o is not v:
1518
+ self._raise(
1519
+ ValueError,
1520
+ 'Must be the same',
1521
+ msg,
1522
+ Checks._ArgsKwargs(v, o),
1523
+ render_fmt='%s is not %s',
1524
+ )
1525
+
1526
+ return v
1527
+
1528
+ def is_not(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
1529
+ if o is v:
1530
+ self._raise(
1531
+ ValueError,
1532
+ 'Must not be the same',
1533
+ msg,
1534
+ Checks._ArgsKwargs(v, o),
1535
+ render_fmt='%s is %s',
1536
+ )
1537
+
1538
+ return v
1539
+
1540
+ def callable(self, v: T, msg: CheckMessage = None) -> T: # noqa
1541
+ if not callable(v):
1542
+ self._raise(
1543
+ TypeError,
1544
+ 'Must be callable',
1545
+ msg,
1546
+ Checks._ArgsKwargs(v),
1547
+ render_fmt='%s',
1548
+ )
1549
+
1550
+ return v # type: ignore
1551
+
1552
+ def non_empty_str(self, v: ta.Optional[str], msg: CheckMessage = None) -> str:
1553
+ if not isinstance(v, str) or not v:
1554
+ self._raise(
1555
+ ValueError,
1556
+ 'Must be non-empty str',
1557
+ msg,
1558
+ Checks._ArgsKwargs(v),
1559
+ render_fmt='%s',
1560
+ )
1561
+
1562
+ return v
1563
+
1564
+ def replacing(self, expected: ta.Any, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
1565
+ if old != expected:
1566
+ self._raise(
1567
+ ValueError,
1568
+ 'Must be replacing',
1569
+ msg,
1570
+ Checks._ArgsKwargs(expected, old, new),
1571
+ render_fmt='%s -> %s -> %s',
1572
+ )
1573
+
1574
+ return new
1575
+
1576
+ def replacing_none(self, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
1577
+ if old is not None:
1578
+ self._raise(
1579
+ ValueError,
1580
+ 'Must be replacing None',
1581
+ msg,
1582
+ Checks._ArgsKwargs(old, new),
1583
+ render_fmt='%s -> %s',
1584
+ )
1231
1585
 
1586
+ return new
1232
1587
 
1233
- def check_single(vs: ta.Iterable[T]) -> T:
1234
- [v] = vs
1235
- return v
1588
+ #
1236
1589
 
1590
+ def arg(self, v: bool, msg: CheckMessage = None) -> None:
1591
+ if not v:
1592
+ self._raise(
1593
+ RuntimeError,
1594
+ 'Argument condition not met',
1595
+ msg,
1596
+ Checks._ArgsKwargs(v),
1597
+ render_fmt='%s',
1598
+ )
1237
1599
 
1238
- def check_empty(v: SizedT) -> SizedT:
1239
- if len(v):
1240
- raise ValueError(v)
1241
- return v
1600
+ def state(self, v: bool, msg: CheckMessage = None) -> None:
1601
+ if not v:
1602
+ self._raise(
1603
+ RuntimeError,
1604
+ 'State condition not met',
1605
+ msg,
1606
+ Checks._ArgsKwargs(v),
1607
+ render_fmt='%s',
1608
+ )
1242
1609
 
1243
1610
 
1244
- def check_non_empty(v: SizedT) -> SizedT:
1245
- if not len(v):
1246
- raise ValueError(v)
1247
- return v
1611
+ check = Checks()
1248
1612
 
1249
1613
 
1250
1614
  ########################################
@@ -2053,7 +2417,7 @@ class Command(abc.ABC, ta.Generic[CommandOutputT]):
2053
2417
 
2054
2418
  @ta.final
2055
2419
  async def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
2056
- return check_isinstance(await executor.execute(self), self.Output) # type: ignore[return-value]
2420
+ return check.isinstance(await executor.execute(self), self.Output) # type: ignore[return-value]
2057
2421
 
2058
2422
 
2059
2423
  ##
@@ -2248,6 +2612,237 @@ def get_remote_payload_src(
2248
2612
  return importlib.resources.files(__package__.split('.')[0] + '.scripts').joinpath('manage.py').read_text()
2249
2613
 
2250
2614
 
2615
+ ########################################
2616
+ # ../../../omlish/argparse/cli.py
2617
+ """
2618
+ TODO:
2619
+ - default command
2620
+ - auto match all underscores to hyphens
2621
+ """
2622
+
2623
+
2624
+ ##
2625
+
2626
+
2627
+ @dc.dataclass(eq=False)
2628
+ class ArgparseArg:
2629
+ args: ta.Sequence[ta.Any]
2630
+ kwargs: ta.Mapping[str, ta.Any]
2631
+ dest: ta.Optional[str] = None
2632
+
2633
+ def __get__(self, instance, owner=None):
2634
+ if instance is None:
2635
+ return self
2636
+ return getattr(instance.args, self.dest) # type: ignore
2637
+
2638
+
2639
+ def argparse_arg(*args, **kwargs) -> ArgparseArg:
2640
+ return ArgparseArg(args, kwargs)
2641
+
2642
+
2643
+ #
2644
+
2645
+
2646
+ @dc.dataclass(eq=False)
2647
+ class ArgparseCommand:
2648
+ name: str
2649
+ fn: ArgparseCommandFn
2650
+ args: ta.Sequence[ArgparseArg] = () # noqa
2651
+
2652
+ # _: dc.KW_ONLY
2653
+
2654
+ aliases: ta.Optional[ta.Sequence[str]] = None
2655
+ parent: ta.Optional['ArgparseCommand'] = None
2656
+ accepts_unknown: bool = False
2657
+
2658
+ def __post_init__(self) -> None:
2659
+ def check_name(s: str) -> None:
2660
+ check.isinstance(s, str)
2661
+ check.not_in('_', s)
2662
+ check.not_empty(s)
2663
+ check_name(self.name)
2664
+ check.not_isinstance(self.aliases, str)
2665
+ for a in self.aliases or []:
2666
+ check_name(a)
2667
+
2668
+ check.arg(callable(self.fn))
2669
+ check.arg(all(isinstance(a, ArgparseArg) for a in self.args))
2670
+ check.isinstance(self.parent, (ArgparseCommand, type(None)))
2671
+ check.isinstance(self.accepts_unknown, bool)
2672
+
2673
+ functools.update_wrapper(self, self.fn)
2674
+
2675
+ def __get__(self, instance, owner=None):
2676
+ if instance is None:
2677
+ return self
2678
+ return dc.replace(self, fn=self.fn.__get__(instance, owner)) # noqa
2679
+
2680
+ def __call__(self, *args, **kwargs) -> ta.Optional[int]:
2681
+ return self.fn(*args, **kwargs)
2682
+
2683
+
2684
+ def argparse_command(
2685
+ *args: ArgparseArg,
2686
+ name: ta.Optional[str] = None,
2687
+ aliases: ta.Optional[ta.Iterable[str]] = None,
2688
+ parent: ta.Optional[ArgparseCommand] = None,
2689
+ accepts_unknown: bool = False,
2690
+ ) -> ta.Any: # ta.Callable[[ArgparseCommandFn], ArgparseCommand]: # FIXME
2691
+ for arg in args:
2692
+ check.isinstance(arg, ArgparseArg)
2693
+ check.isinstance(name, (str, type(None)))
2694
+ check.isinstance(parent, (ArgparseCommand, type(None)))
2695
+ check.not_isinstance(aliases, str)
2696
+
2697
+ def inner(fn):
2698
+ return ArgparseCommand(
2699
+ (name if name is not None else fn.__name__).replace('_', '-'),
2700
+ fn,
2701
+ args,
2702
+ aliases=tuple(aliases) if aliases is not None else None,
2703
+ parent=parent,
2704
+ accepts_unknown=accepts_unknown,
2705
+ )
2706
+
2707
+ return inner
2708
+
2709
+
2710
+ ##
2711
+
2712
+
2713
+ def _get_argparse_arg_ann_kwargs(ann: ta.Any) -> ta.Mapping[str, ta.Any]:
2714
+ if ann is str:
2715
+ return {}
2716
+ elif ann is int:
2717
+ return {'type': int}
2718
+ elif ann is bool:
2719
+ return {'action': 'store_true'}
2720
+ elif ann is list:
2721
+ return {'action': 'append'}
2722
+ else:
2723
+ raise TypeError(ann)
2724
+
2725
+
2726
+ class _ArgparseCliAnnotationBox:
2727
+ def __init__(self, annotations: ta.Mapping[str, ta.Any]) -> None:
2728
+ super().__init__()
2729
+ self.__annotations__ = annotations # type: ignore
2730
+
2731
+
2732
+ class ArgparseCli:
2733
+ def __init__(self, argv: ta.Optional[ta.Sequence[str]] = None) -> None:
2734
+ super().__init__()
2735
+
2736
+ self._argv = argv if argv is not None else sys.argv[1:]
2737
+
2738
+ self._args, self._unknown_args = self.get_parser().parse_known_args(self._argv)
2739
+
2740
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
2741
+ super().__init_subclass__(**kwargs)
2742
+
2743
+ ns = cls.__dict__
2744
+
2745
+ objs = {}
2746
+ mro = cls.__mro__[::-1]
2747
+ for bns in [bcls.__dict__ for bcls in reversed(mro)] + [ns]:
2748
+ bseen = set() # type: ignore
2749
+ for k, v in bns.items():
2750
+ if isinstance(v, (ArgparseCommand, ArgparseArg)):
2751
+ check.not_in(v, bseen)
2752
+ bseen.add(v)
2753
+ objs[k] = v
2754
+ elif k in objs:
2755
+ del [k]
2756
+
2757
+ anns = ta.get_type_hints(_ArgparseCliAnnotationBox({
2758
+ **{k: v for bcls in reversed(mro) for k, v in getattr(bcls, '__annotations__', {}).items()},
2759
+ **ns.get('__annotations__', {}),
2760
+ }), globalns=ns.get('__globals__', {}))
2761
+
2762
+ if '_parser' in ns:
2763
+ parser = check.isinstance(ns['_parser'], argparse.ArgumentParser)
2764
+ else:
2765
+ parser = argparse.ArgumentParser()
2766
+ setattr(cls, '_parser', parser)
2767
+
2768
+ subparsers = parser.add_subparsers()
2769
+ for att, obj in objs.items():
2770
+ if isinstance(obj, ArgparseCommand):
2771
+ if obj.parent is not None:
2772
+ raise NotImplementedError
2773
+ for cn in [obj.name, *(obj.aliases or [])]:
2774
+ cparser = subparsers.add_parser(cn)
2775
+ for arg in (obj.args or []):
2776
+ if (
2777
+ len(arg.args) == 1 and
2778
+ isinstance(arg.args[0], str) and
2779
+ not (n := check.isinstance(arg.args[0], str)).startswith('-') and
2780
+ 'metavar' not in arg.kwargs
2781
+ ):
2782
+ cparser.add_argument(
2783
+ n.replace('-', '_'),
2784
+ **arg.kwargs,
2785
+ metavar=n,
2786
+ )
2787
+ else:
2788
+ cparser.add_argument(*arg.args, **arg.kwargs)
2789
+ cparser.set_defaults(_cmd=obj)
2790
+
2791
+ elif isinstance(obj, ArgparseArg):
2792
+ if att in anns:
2793
+ akwargs = _get_argparse_arg_ann_kwargs(anns[att])
2794
+ obj.kwargs = {**akwargs, **obj.kwargs}
2795
+ if not obj.dest:
2796
+ if 'dest' in obj.kwargs:
2797
+ obj.dest = obj.kwargs['dest']
2798
+ else:
2799
+ obj.dest = obj.kwargs['dest'] = att # type: ignore
2800
+ parser.add_argument(*obj.args, **obj.kwargs)
2801
+
2802
+ else:
2803
+ raise TypeError(obj)
2804
+
2805
+ _parser: ta.ClassVar[argparse.ArgumentParser]
2806
+
2807
+ @classmethod
2808
+ def get_parser(cls) -> argparse.ArgumentParser:
2809
+ return cls._parser
2810
+
2811
+ @property
2812
+ def argv(self) -> ta.Sequence[str]:
2813
+ return self._argv
2814
+
2815
+ @property
2816
+ def args(self) -> argparse.Namespace:
2817
+ return self._args
2818
+
2819
+ @property
2820
+ def unknown_args(self) -> ta.Sequence[str]:
2821
+ return self._unknown_args
2822
+
2823
+ def _run_cmd(self, cmd: ArgparseCommand) -> ta.Optional[int]:
2824
+ return cmd.__get__(self, type(self))()
2825
+
2826
+ def __call__(self) -> ta.Optional[int]:
2827
+ cmd = getattr(self.args, '_cmd', None)
2828
+
2829
+ if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
2830
+ msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
2831
+ if (parser := self.get_parser()).exit_on_error: # type: ignore
2832
+ parser.error(msg)
2833
+ else:
2834
+ raise argparse.ArgumentError(None, msg)
2835
+
2836
+ if cmd is None:
2837
+ self.get_parser().print_help()
2838
+ return 0
2839
+
2840
+ return self._run_cmd(cmd)
2841
+
2842
+ def call_and_exit(self) -> ta.NoReturn:
2843
+ sys.exit(rc if isinstance(rc := self(), int) else 0)
2844
+
2845
+
2251
2846
  ########################################
2252
2847
  # ../../../omlish/lite/inject.py
2253
2848
 
@@ -2392,7 +2987,7 @@ class FnInjectorProvider(InjectorProvider):
2392
2987
  fn: ta.Any
2393
2988
 
2394
2989
  def __post_init__(self) -> None:
2395
- check_not_isinstance(self.fn, type)
2990
+ check.not_isinstance(self.fn, type)
2396
2991
 
2397
2992
  def provider_fn(self) -> InjectorProviderFn:
2398
2993
  def pfn(i: Injector) -> ta.Any:
@@ -2406,7 +3001,7 @@ class CtorInjectorProvider(InjectorProvider):
2406
3001
  cls_: type
2407
3002
 
2408
3003
  def __post_init__(self) -> None:
2409
- check_isinstance(self.cls_, type)
3004
+ check.isinstance(self.cls_, type)
2410
3005
 
2411
3006
  def provider_fn(self) -> InjectorProviderFn:
2412
3007
  def pfn(i: Injector) -> ta.Any:
@@ -2428,7 +3023,7 @@ class SingletonInjectorProvider(InjectorProvider):
2428
3023
  p: InjectorProvider
2429
3024
 
2430
3025
  def __post_init__(self) -> None:
2431
- check_isinstance(self.p, InjectorProvider)
3026
+ check.isinstance(self.p, InjectorProvider)
2432
3027
 
2433
3028
  def provider_fn(self) -> InjectorProviderFn:
2434
3029
  v = not_set = object()
@@ -2448,7 +3043,7 @@ class LinkInjectorProvider(InjectorProvider):
2448
3043
  k: InjectorKey
2449
3044
 
2450
3045
  def __post_init__(self) -> None:
2451
- check_isinstance(self.k, InjectorKey)
3046
+ check.isinstance(self.k, InjectorKey)
2452
3047
 
2453
3048
  def provider_fn(self) -> InjectorProviderFn:
2454
3049
  def pfn(i: Injector) -> ta.Any:
@@ -2645,7 +3240,7 @@ def build_injection_kwargs_target(
2645
3240
 
2646
3241
  skip_names: ta.Set[str] = set()
2647
3242
  if skip_kwargs is not None:
2648
- skip_names.update(check_not_isinstance(skip_kwargs, str))
3243
+ skip_names.update(check.not_isinstance(skip_kwargs, str))
2649
3244
 
2650
3245
  seen: ta.Set[InjectorKey] = set()
2651
3246
  kws: ta.List[InjectionKwarg] = []
@@ -2706,8 +3301,8 @@ class _Injector(Injector):
2706
3301
  def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
2707
3302
  super().__init__()
2708
3303
 
2709
- self._bs = check_isinstance(bs, InjectorBindings)
2710
- self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
3304
+ self._bs = check.isinstance(bs, InjectorBindings)
3305
+ self._p: ta.Optional[Injector] = check.isinstance(p, (Injector, type(None)))
2711
3306
 
2712
3307
  self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
2713
3308
 
@@ -2738,8 +3333,8 @@ class _Injector(Injector):
2738
3333
  return Maybe.empty()
2739
3334
 
2740
3335
  def handle_provision(self, key: InjectorKey, mv: Maybe) -> Maybe:
2741
- check_in(key, self._seen_keys)
2742
- check_not_in(key, self._provisions)
3336
+ check.in_(key, self._seen_keys)
3337
+ check.not_in(key, self._provisions)
2743
3338
  self._provisions[key] = mv
2744
3339
  return mv
2745
3340
 
@@ -2853,7 +3448,7 @@ class InjectorBinder:
2853
3448
 
2854
3449
  @classmethod
2855
3450
  def bind_as_fn(cls, icls: ta.Type[T]) -> ta.Type[T]:
2856
- check_isinstance(icls, type)
3451
+ check.isinstance(icls, type)
2857
3452
  if icls not in cls._FN_TYPES:
2858
3453
  cls._FN_TYPES = (*cls._FN_TYPES, icls)
2859
3454
  return icls
@@ -2910,7 +3505,7 @@ class InjectorBinder:
2910
3505
  to_fn = obj
2911
3506
  if key is None:
2912
3507
  insp = _injection_inspect(obj)
2913
- key_cls: ta.Any = check_valid_injector_key_cls(check_not_none(insp.type_hints.get('return')))
3508
+ key_cls: ta.Any = check_valid_injector_key_cls(check.not_none(insp.type_hints.get('return')))
2914
3509
  key = InjectorKey(key_cls)
2915
3510
  else:
2916
3511
  if to_const is not None:
@@ -3452,10 +4047,10 @@ class ProxyObjMarshaler(ObjMarshaler):
3452
4047
  m: ta.Optional[ObjMarshaler] = None
3453
4048
 
3454
4049
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
3455
- return check_not_none(self.m).marshal(o, ctx)
4050
+ return check.not_none(self.m).marshal(o, ctx)
3456
4051
 
3457
4052
  def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
3458
- return check_not_none(self.m).unmarshal(o, ctx)
4053
+ return check.not_none(self.m).unmarshal(o, ctx)
3459
4054
 
3460
4055
 
3461
4056
  @dc.dataclass(frozen=True)
@@ -3614,19 +4209,19 @@ class DatetimeObjMarshaler(ObjMarshaler):
3614
4209
 
3615
4210
  class DecimalObjMarshaler(ObjMarshaler):
3616
4211
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
3617
- return str(check_isinstance(o, decimal.Decimal))
4212
+ return str(check.isinstance(o, decimal.Decimal))
3618
4213
 
3619
4214
  def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
3620
- return decimal.Decimal(check_isinstance(v, str))
4215
+ return decimal.Decimal(check.isinstance(v, str))
3621
4216
 
3622
4217
 
3623
4218
  class FractionObjMarshaler(ObjMarshaler):
3624
4219
  def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
3625
- fr = check_isinstance(o, fractions.Fraction)
4220
+ fr = check.isinstance(o, fractions.Fraction)
3626
4221
  return [fr.numerator, fr.denominator]
3627
4222
 
3628
4223
  def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
3629
- num, denom = check_isinstance(v, list)
4224
+ num, denom = check.isinstance(v, list)
3630
4225
  return fractions.Fraction(num, denom)
3631
4226
 
3632
4227
 
@@ -4431,7 +5026,7 @@ class _RemoteCommandHandler:
4431
5026
  ], return_when=asyncio.FIRST_COMPLETED)
4432
5027
 
4433
5028
  if recv_task in done:
4434
- msg: ta.Optional[_RemoteProtocol.Message] = check_isinstance(
5029
+ msg: ta.Optional[_RemoteProtocol.Message] = check.isinstance(
4435
5030
  recv_task.result(),
4436
5031
  (_RemoteProtocol.Message, type(None)),
4437
5032
  )
@@ -4500,8 +5095,8 @@ class RemoteCommandExecutor(CommandExecutor):
4500
5095
  #
4501
5096
 
4502
5097
  async def start(self) -> None:
4503
- check_none(self._loop_task)
4504
- check_state(not self._stop.is_set())
5098
+ check.none(self._loop_task)
5099
+ check.state(not self._stop.is_set())
4505
5100
  self._loop_task = asyncio.create_task(self._loop())
4506
5101
 
4507
5102
  async def aclose(self) -> None:
@@ -4537,12 +5132,12 @@ class RemoteCommandExecutor(CommandExecutor):
4537
5132
  ], return_when=asyncio.FIRST_COMPLETED)
4538
5133
 
4539
5134
  if queue_task in done:
4540
- req = check_isinstance(queue_task.result(), RemoteCommandExecutor._Request)
5135
+ req = check.isinstance(queue_task.result(), RemoteCommandExecutor._Request)
4541
5136
  queue_task = None
4542
5137
  await self._handle_request(req)
4543
5138
 
4544
5139
  if recv_task in done:
4545
- msg: ta.Optional[_RemoteProtocol.Message] = check_isinstance(
5140
+ msg: ta.Optional[_RemoteProtocol.Message] = check.isinstance(
4546
5141
  recv_task.result(),
4547
5142
  (_RemoteProtocol.Message, type(None)),
4548
5143
  )
@@ -4610,7 +5205,7 @@ class RemoteCommandExecutor(CommandExecutor):
4610
5205
  if (e := r.exception) is not None:
4611
5206
  raise RemoteCommandError(e)
4612
5207
  else:
4613
- return check_not_none(r.output)
5208
+ return check.not_none(r.output)
4614
5209
 
4615
5210
  # @ta.override
4616
5211
  async def try_execute(
@@ -4655,7 +5250,7 @@ async def asyncio_subprocess_popen(
4655
5250
  if shell:
4656
5251
  fac = functools.partial(
4657
5252
  asyncio.create_subprocess_shell,
4658
- check_single(cmd),
5253
+ check.single(cmd),
4659
5254
  )
4660
5255
  else:
4661
5256
  fac = functools.partial(
@@ -4695,7 +5290,7 @@ class AsyncioProcessCommunicator:
4695
5290
  self._proc = proc
4696
5291
  self._loop = loop
4697
5292
 
4698
- self._transport: asyncio.base_subprocess.BaseSubprocessTransport = check_isinstance(
5293
+ self._transport: asyncio.base_subprocess.BaseSubprocessTransport = check.isinstance(
4699
5294
  proc._transport, # type: ignore # noqa
4700
5295
  asyncio.base_subprocess.BaseSubprocessTransport,
4701
5296
  )
@@ -4705,7 +5300,7 @@ class AsyncioProcessCommunicator:
4705
5300
  return self._loop.get_debug()
4706
5301
 
4707
5302
  async def _feed_stdin(self, input: bytes) -> None: # noqa
4708
- stdin = check_not_none(self._proc.stdin)
5303
+ stdin = check.not_none(self._proc.stdin)
4709
5304
  try:
4710
5305
  if input is not None:
4711
5306
  stdin.write(input)
@@ -4729,13 +5324,13 @@ class AsyncioProcessCommunicator:
4729
5324
  return None
4730
5325
 
4731
5326
  async def _read_stream(self, fd: int) -> bytes:
4732
- transport: ta.Any = check_not_none(self._transport.get_pipe_transport(fd))
5327
+ transport: ta.Any = check.not_none(self._transport.get_pipe_transport(fd))
4733
5328
 
4734
5329
  if fd == 2:
4735
- stream = check_not_none(self._proc.stderr)
5330
+ stream = check.not_none(self._proc.stderr)
4736
5331
  else:
4737
- check_equal(fd, 1)
4738
- stream = check_not_none(self._proc.stdout)
5332
+ check.equal(fd, 1)
5333
+ stream = check.not_none(self._proc.stdout)
4739
5334
 
4740
5335
  if self._debug:
4741
5336
  name = 'stdout' if fd == 1 else 'stderr'
@@ -4855,7 +5450,7 @@ async def asyncio_subprocess_check_output(
4855
5450
  **kwargs,
4856
5451
  )
4857
5452
 
4858
- return check_not_none(stdout)
5453
+ return check.not_none(stdout)
4859
5454
 
4860
5455
 
4861
5456
  async def asyncio_subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
@@ -5033,7 +5628,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
5033
5628
  timeout: ta.Optional[float] = None
5034
5629
 
5035
5630
  def __post_init__(self) -> None:
5036
- check_not_isinstance(self.cmd, str)
5631
+ check.not_isinstance(self.cmd, str)
5037
5632
 
5038
5633
  @dc.dataclass(frozen=True)
5039
5634
  class Output(Command.Output):
@@ -5074,7 +5669,7 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
5074
5669
  end_time = time.time()
5075
5670
 
5076
5671
  return SubprocessCommand.Output(
5077
- rc=check_not_none(proc.returncode),
5672
+ rc=check.not_none(proc.returncode),
5078
5673
  pid=proc.pid,
5079
5674
 
5080
5675
  elapsed_s=end_time - start_time,
@@ -5118,11 +5713,11 @@ class _RemoteExecutionMain:
5118
5713
 
5119
5714
  @property
5120
5715
  def _bootstrap(self) -> MainBootstrap:
5121
- return check_not_none(self.__bootstrap)
5716
+ return check.not_none(self.__bootstrap)
5122
5717
 
5123
5718
  @property
5124
5719
  def _injector(self) -> Injector:
5125
- return check_not_none(self.__injector)
5720
+ return check.not_none(self.__injector)
5126
5721
 
5127
5722
  #
5128
5723
 
@@ -5166,12 +5761,12 @@ class _RemoteExecutionMain:
5166
5761
  #
5167
5762
 
5168
5763
  async def _setup(self) -> None:
5169
- check_none(self.__bootstrap)
5170
- check_none(self.__injector)
5764
+ check.none(self.__bootstrap)
5765
+ check.none(self.__injector)
5171
5766
 
5172
5767
  # Bootstrap
5173
5768
 
5174
- self.__bootstrap = check_not_none(await self._chan.recv_obj(MainBootstrap))
5769
+ self.__bootstrap = check.not_none(await self._chan.recv_obj(MainBootstrap))
5175
5770
 
5176
5771
  if (prd := self._bootstrap.remote_config.pycharm_remote_debug) is not None:
5177
5772
  pycharm_debug_connect(prd)
@@ -5313,8 +5908,8 @@ class SubprocessRemoteSpawning(RemoteSpawning):
5313
5908
  ),
5314
5909
  timeout=timeout,
5315
5910
  ) as proc:
5316
- stdin = check_not_none(proc.stdin)
5317
- stdout = check_not_none(proc.stdout)
5911
+ stdin = check.not_none(proc.stdin)
5912
+ stdout = check.not_none(proc.stdout)
5318
5913
 
5319
5914
  try:
5320
5915
  yield RemoteSpawning.Spawned(
@@ -5530,7 +6125,7 @@ class Pyenv:
5530
6125
 
5531
6126
  @async_cached_nullary
5532
6127
  async def exe(self) -> str:
5533
- return os.path.join(check_not_none(await self.root()), 'bin', 'pyenv')
6128
+ return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
5534
6129
 
5535
6130
  async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
5536
6131
  if (root := await self.root()) is None:
@@ -5756,7 +6351,7 @@ class PyenvVersionInstaller:
5756
6351
 
5757
6352
  @async_cached_nullary
5758
6353
  async def install_dir(self) -> str:
5759
- return str(os.path.join(check_not_none(await self._pyenv.root()), 'versions', self.install_name()))
6354
+ return str(os.path.join(check.not_none(await self._pyenv.root()), 'versions', self.install_name()))
5760
6355
 
5761
6356
  @async_cached_nullary
5762
6357
  async def install(self) -> str:
@@ -5779,7 +6374,7 @@ class PyenvVersionInstaller:
5779
6374
 
5780
6375
  if self._given_install_name is not None:
5781
6376
  full_args = [
5782
- os.path.join(check_not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
6377
+ os.path.join(check.not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
5783
6378
  *conf_args,
5784
6379
  self.install_dir(),
5785
6380
  ]
@@ -5851,7 +6446,7 @@ class PyenvInterpProvider(InterpProvider):
5851
6446
  iv: ta.Optional[InterpVersion]
5852
6447
  if self._inspect:
5853
6448
  try:
5854
- iv = check_not_none(await self._inspector.inspect(ep)).iv
6449
+ iv = check.not_none(await self._inspector.inspect(ep)).iv
5855
6450
  except Exception as e: # noqa
5856
6451
  return None
5857
6452
  else:
@@ -6186,8 +6781,8 @@ class InterpCommand(Command['InterpCommand.Output']):
6186
6781
 
6187
6782
  class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
6188
6783
  async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
6189
- i = InterpSpecifier.parse(check_not_none(cmd.spec))
6190
- o = check_not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
6784
+ i = InterpSpecifier.parse(check.not_none(cmd.spec))
6785
+ o = check.not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
6191
6786
  return InterpCommand.Output(
6192
6787
  exe=o.exe,
6193
6788
  version=str(o.version.version),
@@ -6378,108 +6973,102 @@ def main_bootstrap(bs: MainBootstrap) -> Injector:
6378
6973
  # main.py
6379
6974
 
6380
6975
 
6381
- ##
6976
+ class MainCli(ArgparseCli):
6977
+ @argparse_command(
6978
+ argparse_arg('--payload-file'),
6382
6979
 
6980
+ argparse_arg('-s', '--shell'),
6981
+ argparse_arg('-q', '--shell-quote', action='store_true'),
6982
+ argparse_arg('--python', default='python3'),
6383
6983
 
6384
- async def _async_main(args: ta.Any) -> None:
6385
- bs = MainBootstrap(
6386
- main_config=MainConfig(
6387
- log_level='DEBUG' if args.debug else 'INFO',
6984
+ argparse_arg('--pycharm-debug-port', type=int),
6985
+ argparse_arg('--pycharm-debug-host'),
6986
+ argparse_arg('--pycharm-debug-version'),
6388
6987
 
6389
- debug=bool(args.debug),
6390
- ),
6988
+ argparse_arg('--remote-timebomb-delay-s', type=float),
6391
6989
 
6392
- remote_config=RemoteConfig(
6393
- payload_file=args._payload_file, # noqa
6990
+ argparse_arg('--debug', action='store_true'),
6394
6991
 
6395
- pycharm_remote_debug=PycharmRemoteDebug(
6396
- port=args.pycharm_debug_port,
6397
- **(dict(host=args.pycharm_debug_host) if args.pycharm_debug_host is not None else {}),
6398
- install_version=args.pycharm_debug_version,
6399
- ) if args.pycharm_debug_port is not None else None,
6992
+ argparse_arg('--local', action='store_true'),
6400
6993
 
6401
- timebomb_delay_s=args.remote_timebomb_delay_s,
6402
- ),
6994
+ argparse_arg('command', nargs='+'),
6403
6995
  )
6996
+ def run(self) -> None:
6997
+ asyncio.run(self._async_run())
6404
6998
 
6405
- #
6406
-
6407
- injector = main_bootstrap(
6408
- bs,
6409
- )
6999
+ async def _async_run(self) -> None:
7000
+ bs = MainBootstrap(
7001
+ main_config=MainConfig(
7002
+ log_level='DEBUG' if self.args.debug else 'INFO',
6410
7003
 
6411
- #
6412
-
6413
- msh = injector[ObjMarshalerManager]
6414
-
6415
- cmds: ta.List[Command] = []
6416
- cmd: Command
6417
- for c in args.command:
6418
- if not c.startswith('{'):
6419
- c = json.dumps({c: {}})
6420
- cmd = msh.unmarshal_obj(json.loads(c), Command)
6421
- cmds.append(cmd)
6422
-
6423
- #
6424
-
6425
- async with contextlib.AsyncExitStack() as es:
6426
- ce: CommandExecutor
6427
-
6428
- if args.local:
6429
- ce = injector[LocalCommandExecutor]
7004
+ debug=bool(self.args.debug),
7005
+ ),
6430
7006
 
6431
- else:
6432
- tgt = RemoteSpawning.Target(
6433
- shell=args.shell,
6434
- shell_quote=args.shell_quote,
6435
- python=args.python,
6436
- )
7007
+ remote_config=RemoteConfig(
7008
+ payload_file=self.args.payload_file, # noqa
6437
7009
 
6438
- ce = await es.enter_async_context(injector[RemoteExecutionConnector].connect(tgt, bs)) # noqa
7010
+ pycharm_remote_debug=PycharmRemoteDebug(
7011
+ port=self.args.pycharm_debug_port,
7012
+ **(dict(host=self.args.pycharm_debug_host) if self.args.pycharm_debug_host is not None else {}),
7013
+ install_version=self.args.pycharm_debug_version,
7014
+ ) if self.args.pycharm_debug_port is not None else None,
6439
7015
 
6440
- async def run_command(cmd: Command) -> None:
6441
- res = await ce.try_execute(
6442
- cmd,
6443
- log=log,
6444
- omit_exc_object=True,
6445
- )
7016
+ timebomb_delay_s=self.args.remote_timebomb_delay_s,
7017
+ ),
7018
+ )
6446
7019
 
6447
- print(msh.marshal_obj(res, opts=ObjMarshalOptions(raw_bytes=True)))
7020
+ #
6448
7021
 
6449
- await asyncio.gather(*[
6450
- run_command(cmd)
6451
- for cmd in cmds
6452
- ])
7022
+ injector = main_bootstrap(
7023
+ bs,
7024
+ )
6453
7025
 
7026
+ #
6454
7027
 
6455
- def _main() -> None:
6456
- import argparse
7028
+ msh = injector[ObjMarshalerManager]
6457
7029
 
6458
- parser = argparse.ArgumentParser()
7030
+ cmds: ta.List[Command] = []
7031
+ cmd: Command
7032
+ for c in self.args.command:
7033
+ if not c.startswith('{'):
7034
+ c = json.dumps({c: {}})
7035
+ cmd = msh.unmarshal_obj(json.loads(c), Command)
7036
+ cmds.append(cmd)
6459
7037
 
6460
- parser.add_argument('--_payload-file')
7038
+ #
6461
7039
 
6462
- parser.add_argument('-s', '--shell')
6463
- parser.add_argument('-q', '--shell-quote', action='store_true')
6464
- parser.add_argument('--python', default='python3')
7040
+ async with contextlib.AsyncExitStack() as es:
7041
+ ce: CommandExecutor
6465
7042
 
6466
- parser.add_argument('--pycharm-debug-port', type=int)
6467
- parser.add_argument('--pycharm-debug-host')
6468
- parser.add_argument('--pycharm-debug-version')
7043
+ if self.args.local:
7044
+ ce = injector[LocalCommandExecutor]
6469
7045
 
6470
- parser.add_argument('--remote-timebomb-delay-s', type=float)
7046
+ else:
7047
+ tgt = RemoteSpawning.Target(
7048
+ shell=self.args.shell,
7049
+ shell_quote=self.args.shell_quote,
7050
+ python=self.args.python,
7051
+ )
6471
7052
 
6472
- parser.add_argument('--debug', action='store_true')
7053
+ ce = await es.enter_async_context(injector[RemoteExecutionConnector].connect(tgt, bs)) # noqa
6473
7054
 
6474
- parser.add_argument('--local', action='store_true')
7055
+ async def run_command(cmd: Command) -> None:
7056
+ res = await ce.try_execute(
7057
+ cmd,
7058
+ log=log,
7059
+ omit_exc_object=True,
7060
+ )
6475
7061
 
6476
- parser.add_argument('command', nargs='+')
7062
+ print(msh.marshal_obj(res, opts=ObjMarshalOptions(raw_bytes=True)))
6477
7063
 
6478
- args = parser.parse_args()
7064
+ await asyncio.gather(*[
7065
+ run_command(cmd)
7066
+ for cmd in cmds
7067
+ ])
6479
7068
 
6480
- #
6481
7069
 
6482
- asyncio.run(_async_main(args))
7070
+ def _main() -> None:
7071
+ MainCli().call_and_exit()
6483
7072
 
6484
7073
 
6485
7074
  if __name__ == '__main__':