omdev 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev226__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
@@ -57,9 +57,6 @@ VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalTy
57
57
  VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
58
58
  VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
59
59
 
60
- # ../../omlish/asyncs/asyncio/timeouts.py
61
- AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
62
-
63
60
  # ../../omlish/lite/cached.py
64
61
  T = ta.TypeVar('T')
65
62
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
@@ -72,6 +69,9 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
72
69
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
73
70
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
74
71
 
72
+ # ../../omlish/lite/timeouts.py
73
+ TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
74
+
75
75
  # ../packaging/specifiers.py
76
76
  UnparsedVersion = ta.Union['Version', str]
77
77
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
@@ -80,6 +80,9 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
80
80
  # ../../omlish/argparse/cli.py
81
81
  ArgparseCmdFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
82
82
 
83
+ # ../../omlish/asyncs/asyncio/timeouts.py
84
+ AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
85
+
83
86
  # ../../omlish/lite/inject.py
84
87
  U = ta.TypeVar('U')
85
88
  InjectorKeyCls = ta.Union[type, ta.NewType]
@@ -87,7 +90,7 @@ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
87
90
  InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
88
91
  InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
89
92
 
90
- # ../../omlish/subprocesses.py
93
+ # ../../omlish/subprocesses/base.py
91
94
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
92
95
 
93
96
 
@@ -498,19 +501,6 @@ def canonicalize_version(
498
501
  return ''.join(parts)
499
502
 
500
503
 
501
- ########################################
502
- # ../../../omlish/asyncs/asyncio/timeouts.py
503
-
504
-
505
- def asyncio_maybe_timeout(
506
- fut: AwaitableT,
507
- timeout: ta.Optional[float] = None,
508
- ) -> AwaitableT:
509
- if timeout is not None:
510
- fut = asyncio.wait_for(fut, timeout) # type: ignore
511
- return fut
512
-
513
-
514
504
  ########################################
515
505
  # ../../../omlish/lite/cached.py
516
506
 
@@ -1303,6 +1293,205 @@ def format_num_bytes(num_bytes: int) -> str:
1303
1293
  return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
1304
1294
 
1305
1295
 
1296
+ ########################################
1297
+ # ../../../omlish/lite/timeouts.py
1298
+ """
1299
+ TODO:
1300
+ - Event (/ Predicate)
1301
+ """
1302
+
1303
+
1304
+ ##
1305
+
1306
+
1307
+ class Timeout(abc.ABC):
1308
+ @property
1309
+ @abc.abstractmethod
1310
+ def can_expire(self) -> bool:
1311
+ """Indicates whether or not this timeout will ever expire."""
1312
+
1313
+ raise NotImplementedError
1314
+
1315
+ @abc.abstractmethod
1316
+ def expired(self) -> bool:
1317
+ """Return whether or not this timeout has expired."""
1318
+
1319
+ raise NotImplementedError
1320
+
1321
+ @abc.abstractmethod
1322
+ def remaining(self) -> float:
1323
+ """Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
1324
+
1325
+ raise NotImplementedError
1326
+
1327
+ @abc.abstractmethod
1328
+ def __call__(self) -> float:
1329
+ """Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
1330
+
1331
+ raise NotImplementedError
1332
+
1333
+ @abc.abstractmethod
1334
+ def or_(self, o: ta.Any) -> ta.Any:
1335
+ """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
1336
+
1337
+ raise NotImplementedError
1338
+
1339
+ #
1340
+
1341
+ @classmethod
1342
+ def _now(cls) -> float:
1343
+ return time.time()
1344
+
1345
+ #
1346
+
1347
+ class Default:
1348
+ def __new__(cls, *args, **kwargs): # noqa
1349
+ raise TypeError
1350
+
1351
+ class _NOT_SPECIFIED: # noqa
1352
+ def __new__(cls, *args, **kwargs): # noqa
1353
+ raise TypeError
1354
+
1355
+ @classmethod
1356
+ def of(
1357
+ cls,
1358
+ obj: ta.Optional[TimeoutLike],
1359
+ default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
1360
+ ) -> 'Timeout':
1361
+ if obj is None:
1362
+ return InfiniteTimeout()
1363
+
1364
+ elif isinstance(obj, Timeout):
1365
+ return obj
1366
+
1367
+ elif isinstance(obj, (float, int)):
1368
+ return DeadlineTimeout(cls._now() + obj)
1369
+
1370
+ elif isinstance(obj, ta.Iterable):
1371
+ return CompositeTimeout(*[Timeout.of(c) for c in obj])
1372
+
1373
+ elif obj is Timeout.Default:
1374
+ if default is Timeout._NOT_SPECIFIED or default is Timeout.Default:
1375
+ raise RuntimeError('Must specify a default timeout')
1376
+
1377
+ else:
1378
+ return Timeout.of(default) # type: ignore[arg-type]
1379
+
1380
+ else:
1381
+ raise TypeError(obj)
1382
+
1383
+ @classmethod
1384
+ def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
1385
+ return DeadlineTimeout(deadline)
1386
+
1387
+ @classmethod
1388
+ def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
1389
+ return PredicateTimeout(expired_fn)
1390
+
1391
+
1392
+ class DeadlineTimeout(Timeout):
1393
+ def __init__(
1394
+ self,
1395
+ deadline: float,
1396
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1397
+ ) -> None:
1398
+ super().__init__()
1399
+
1400
+ self.deadline = deadline
1401
+ self.exc = exc
1402
+
1403
+ @property
1404
+ def can_expire(self) -> bool:
1405
+ return True
1406
+
1407
+ def expired(self) -> bool:
1408
+ return not (self.remaining() > 0)
1409
+
1410
+ def remaining(self) -> float:
1411
+ return self.deadline - self._now()
1412
+
1413
+ def __call__(self) -> float:
1414
+ if (rem := self.remaining()) > 0:
1415
+ return rem
1416
+ raise self.exc
1417
+
1418
+ def or_(self, o: ta.Any) -> ta.Any:
1419
+ return self()
1420
+
1421
+
1422
+ class InfiniteTimeout(Timeout):
1423
+ @property
1424
+ def can_expire(self) -> bool:
1425
+ return False
1426
+
1427
+ def expired(self) -> bool:
1428
+ return False
1429
+
1430
+ def remaining(self) -> float:
1431
+ return float('inf')
1432
+
1433
+ def __call__(self) -> float:
1434
+ return float('inf')
1435
+
1436
+ def or_(self, o: ta.Any) -> ta.Any:
1437
+ return o
1438
+
1439
+
1440
+ class CompositeTimeout(Timeout):
1441
+ def __init__(self, *children: Timeout) -> None:
1442
+ super().__init__()
1443
+
1444
+ self.children = children
1445
+
1446
+ @property
1447
+ def can_expire(self) -> bool:
1448
+ return any(c.can_expire for c in self.children)
1449
+
1450
+ def expired(self) -> bool:
1451
+ return any(c.expired() for c in self.children)
1452
+
1453
+ def remaining(self) -> float:
1454
+ return min(c.remaining() for c in self.children)
1455
+
1456
+ def __call__(self) -> float:
1457
+ return min(c() for c in self.children)
1458
+
1459
+ def or_(self, o: ta.Any) -> ta.Any:
1460
+ if self.can_expire:
1461
+ return self()
1462
+ return o
1463
+
1464
+
1465
+ class PredicateTimeout(Timeout):
1466
+ def __init__(
1467
+ self,
1468
+ expired_fn: ta.Callable[[], bool],
1469
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1470
+ ) -> None:
1471
+ super().__init__()
1472
+
1473
+ self.expired_fn = expired_fn
1474
+ self.exc = exc
1475
+
1476
+ @property
1477
+ def can_expire(self) -> bool:
1478
+ return True
1479
+
1480
+ def expired(self) -> bool:
1481
+ return self.expired_fn()
1482
+
1483
+ def remaining(self) -> float:
1484
+ return float('inf')
1485
+
1486
+ def __call__(self) -> float:
1487
+ if not self.expired_fn():
1488
+ return float('inf')
1489
+ raise self.exc
1490
+
1491
+ def or_(self, o: ta.Any) -> ta.Any:
1492
+ return self()
1493
+
1494
+
1306
1495
  ########################################
1307
1496
  # ../../../omlish/logs/filters.py
1308
1497
 
@@ -2224,6 +2413,19 @@ class ArgparseCli:
2224
2413
  return fn()
2225
2414
 
2226
2415
 
2416
+ ########################################
2417
+ # ../../../omlish/asyncs/asyncio/timeouts.py
2418
+
2419
+
2420
+ def asyncio_maybe_timeout(
2421
+ fut: AwaitableT,
2422
+ timeout: ta.Optional[TimeoutLike] = None,
2423
+ ) -> AwaitableT:
2424
+ if timeout is not None:
2425
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
2426
+ return fut
2427
+
2428
+
2227
2429
  ########################################
2228
2430
  # ../../../omlish/lite/inject.py
2229
2431
 
@@ -3369,6 +3571,137 @@ class JsonLogFormatter(logging.Formatter):
3369
3571
  return self._json_dumps(dct)
3370
3572
 
3371
3573
 
3574
+ ########################################
3575
+ # ../../../omlish/subprocesses/run.py
3576
+
3577
+
3578
+ ##
3579
+
3580
+
3581
+ @dc.dataclass(frozen=True)
3582
+ class SubprocessRunOutput(ta.Generic[T]):
3583
+ proc: T
3584
+
3585
+ returncode: int # noqa
3586
+
3587
+ stdout: ta.Optional[bytes] = None
3588
+ stderr: ta.Optional[bytes] = None
3589
+
3590
+
3591
+ ##
3592
+
3593
+
3594
+ @dc.dataclass(frozen=True)
3595
+ class SubprocessRun:
3596
+ cmd: ta.Sequence[str]
3597
+ input: ta.Any = None
3598
+ timeout: ta.Optional[TimeoutLike] = None
3599
+ check: bool = False
3600
+ capture_output: ta.Optional[bool] = None
3601
+ kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
3602
+
3603
+ #
3604
+
3605
+ _FIELD_NAMES: ta.ClassVar[ta.FrozenSet[str]]
3606
+
3607
+ def replace(self, **kwargs: ta.Any) -> 'SubprocessRun':
3608
+ if not kwargs:
3609
+ return self
3610
+
3611
+ field_kws = {}
3612
+ extra_kws = {}
3613
+ for k, v in kwargs.items():
3614
+ if k in self._FIELD_NAMES:
3615
+ field_kws[k] = v
3616
+ else:
3617
+ extra_kws[k] = v
3618
+
3619
+ return dc.replace(self, **{
3620
+ **dict(kwargs={
3621
+ **(self.kwargs or {}),
3622
+ **extra_kws,
3623
+ }),
3624
+ **field_kws, # passing a kwarg named 'kwargs' intentionally clobbers
3625
+ })
3626
+
3627
+ #
3628
+
3629
+ @classmethod
3630
+ def of(
3631
+ cls,
3632
+ *cmd: str,
3633
+ input: ta.Any = None, # noqa
3634
+ timeout: ta.Optional[TimeoutLike] = None,
3635
+ check: bool = False, # noqa
3636
+ capture_output: ta.Optional[bool] = None,
3637
+ **kwargs: ta.Any,
3638
+ ) -> 'SubprocessRun':
3639
+ return cls(
3640
+ cmd=cmd,
3641
+ input=input,
3642
+ timeout=timeout,
3643
+ check=check,
3644
+ capture_output=capture_output,
3645
+ kwargs=kwargs,
3646
+ )
3647
+
3648
+ #
3649
+
3650
+ _DEFAULT_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractSubprocesses
3651
+
3652
+ def run(
3653
+ self,
3654
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
3655
+ **kwargs: ta.Any,
3656
+ ) -> SubprocessRunOutput:
3657
+ if subprocesses is None:
3658
+ subprocesses = self._DEFAULT_SUBPROCESSES
3659
+ return check.not_none(subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
3660
+
3661
+ _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
3662
+
3663
+ async def async_run(
3664
+ self,
3665
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
3666
+ **kwargs: ta.Any,
3667
+ ) -> SubprocessRunOutput:
3668
+ if async_subprocesses is None:
3669
+ async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
3670
+ return await check.not_none(async_subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
3671
+
3672
+
3673
+ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessRun)) # noqa
3674
+
3675
+
3676
+ ##
3677
+
3678
+
3679
+ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
3680
+ @abc.abstractmethod
3681
+ def make_run(self) -> SubprocessRun:
3682
+ raise NotImplementedError
3683
+
3684
+ @abc.abstractmethod
3685
+ def handle_run_output(self, output: SubprocessRunOutput) -> T:
3686
+ raise NotImplementedError
3687
+
3688
+ #
3689
+
3690
+ def run(
3691
+ self,
3692
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
3693
+ **kwargs: ta.Any,
3694
+ ) -> T:
3695
+ return self.handle_run_output(self.make_run().run(subprocesses, **kwargs))
3696
+
3697
+ async def async_run(
3698
+ self,
3699
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
3700
+ **kwargs: ta.Any,
3701
+ ) -> T:
3702
+ return self.handle_run_output(await self.make_run().async_run(async_subprocesses, **kwargs))
3703
+
3704
+
3372
3705
  ########################################
3373
3706
  # ../types.py
3374
3707
 
@@ -3607,23 +3940,7 @@ def configure_standard_logging(
3607
3940
 
3608
3941
 
3609
3942
  ########################################
3610
- # ../../../omlish/subprocesses.py
3611
-
3612
-
3613
- ##
3614
-
3615
-
3616
- # Valid channel type kwarg values:
3617
- # - A special flag negative int
3618
- # - A positive fd int
3619
- # - A file-like object
3620
- # - None
3621
-
3622
- SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
3623
- 'pipe': subprocess.PIPE,
3624
- 'stdout': subprocess.STDOUT,
3625
- 'devnull': subprocess.DEVNULL,
3626
- }
3943
+ # ../../../omlish/subprocesses/wrap.py
3627
3944
 
3628
3945
 
3629
3946
  ##
@@ -3643,28 +3960,74 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
3643
3960
  return cmd
3644
3961
 
3645
3962
 
3646
- ##
3647
-
3648
-
3649
- def subprocess_close(
3650
- proc: subprocess.Popen,
3651
- timeout: ta.Optional[float] = None,
3652
- ) -> None:
3653
- # TODO: terminate, sleep, kill
3654
- if proc.stdout:
3655
- proc.stdout.close()
3656
- if proc.stderr:
3657
- proc.stderr.close()
3658
- if proc.stdin:
3659
- proc.stdin.close()
3660
-
3661
- proc.wait(timeout)
3963
+ ########################################
3964
+ # ../providers/base.py
3965
+ """
3966
+ TODO:
3967
+ - backends
3968
+ - local builds
3969
+ - deadsnakes?
3970
+ - uv
3971
+ - loose versions
3972
+ """
3662
3973
 
3663
3974
 
3664
3975
  ##
3665
3976
 
3666
3977
 
3667
- class VerboseCalledProcessError(subprocess.CalledProcessError):
3978
+ class InterpProvider(abc.ABC):
3979
+ name: ta.ClassVar[str]
3980
+
3981
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
3982
+ super().__init_subclass__(**kwargs)
3983
+ if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
3984
+ sfx = 'InterpProvider'
3985
+ if not cls.__name__.endswith(sfx):
3986
+ raise NameError(cls)
3987
+ setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
3988
+
3989
+ @abc.abstractmethod
3990
+ def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
3991
+ raise NotImplementedError
3992
+
3993
+ @abc.abstractmethod
3994
+ def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
3995
+ raise NotImplementedError
3996
+
3997
+ async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
3998
+ return []
3999
+
4000
+ async def install_version(self, version: InterpVersion) -> Interp:
4001
+ raise TypeError
4002
+
4003
+
4004
+ InterpProviders = ta.NewType('InterpProviders', ta.Sequence[InterpProvider])
4005
+
4006
+
4007
+ ########################################
4008
+ # ../../../omlish/subprocesses/base.py
4009
+
4010
+
4011
+ ##
4012
+
4013
+
4014
+ # Valid channel type kwarg values:
4015
+ # - A special flag negative int
4016
+ # - A positive fd int
4017
+ # - A file-like object
4018
+ # - None
4019
+
4020
+ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
4021
+ 'pipe': subprocess.PIPE,
4022
+ 'stdout': subprocess.STDOUT,
4023
+ 'devnull': subprocess.DEVNULL,
4024
+ }
4025
+
4026
+
4027
+ ##
4028
+
4029
+
4030
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
3668
4031
  @classmethod
3669
4032
  def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
3670
4033
  return cls(
@@ -3741,6 +4104,11 @@ class BaseSubprocesses(abc.ABC): # noqa
3741
4104
 
3742
4105
  #
3743
4106
 
4107
+ if 'timeout' in kwargs:
4108
+ kwargs['timeout'] = Timeout.of(kwargs['timeout']).or_(None)
4109
+
4110
+ #
4111
+
3744
4112
  return cmd, dict(
3745
4113
  env=env,
3746
4114
  shell=shell,
@@ -3845,174 +4213,96 @@ class BaseSubprocesses(abc.ABC): # noqa
3845
4213
  return e
3846
4214
 
3847
4215
 
3848
- ##
3849
-
3850
-
3851
- @dc.dataclass(frozen=True)
3852
- class SubprocessRun:
3853
- cmd: ta.Sequence[str]
3854
- input: ta.Any = None
3855
- timeout: ta.Optional[float] = None
3856
- check: bool = False
3857
- capture_output: ta.Optional[bool] = None
3858
- kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
3859
-
3860
- @classmethod
3861
- def of(
3862
- cls,
3863
- *cmd: str,
3864
- input: ta.Any = None, # noqa
3865
- timeout: ta.Optional[float] = None,
3866
- check: bool = False,
3867
- capture_output: ta.Optional[bool] = None,
3868
- **kwargs: ta.Any,
3869
- ) -> 'SubprocessRun':
3870
- return cls(
3871
- cmd=cmd,
3872
- input=input,
3873
- timeout=timeout,
3874
- check=check,
3875
- capture_output=capture_output,
3876
- kwargs=kwargs,
3877
- )
4216
+ ########################################
4217
+ # ../resolvers.py
3878
4218
 
3879
4219
 
3880
4220
  @dc.dataclass(frozen=True)
3881
- class SubprocessRunOutput(ta.Generic[T]):
3882
- proc: T
3883
-
3884
- returncode: int # noqa
3885
-
3886
- stdout: ta.Optional[bytes] = None
3887
- stderr: ta.Optional[bytes] = None
3888
-
3889
-
3890
- class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
3891
- @abc.abstractmethod
3892
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
3893
- raise NotImplementedError
3894
-
3895
- def run(
3896
- self,
3897
- *cmd: str,
3898
- input: ta.Any = None, # noqa
3899
- timeout: ta.Optional[float] = None,
3900
- check: bool = False,
3901
- capture_output: ta.Optional[bool] = None,
3902
- **kwargs: ta.Any,
3903
- ) -> SubprocessRunOutput:
3904
- return self.run_(SubprocessRun(
3905
- cmd=cmd,
3906
- input=input,
3907
- timeout=timeout,
3908
- check=check,
3909
- capture_output=capture_output,
3910
- kwargs=kwargs,
3911
- ))
4221
+ class InterpResolverProviders:
4222
+ providers: ta.Sequence[ta.Tuple[str, InterpProvider]]
3912
4223
 
3913
- #
3914
4224
 
3915
- @abc.abstractmethod
3916
- def check_call(
4225
+ class InterpResolver:
4226
+ def __init__(
3917
4227
  self,
3918
- *cmd: str,
3919
- stdout: ta.Any = sys.stderr,
3920
- **kwargs: ta.Any,
4228
+ providers: InterpResolverProviders,
3921
4229
  ) -> None:
3922
- raise NotImplementedError
3923
-
3924
- @abc.abstractmethod
3925
- def check_output(
3926
- self,
3927
- *cmd: str,
3928
- **kwargs: ta.Any,
3929
- ) -> bytes:
3930
- raise NotImplementedError
4230
+ super().__init__()
3931
4231
 
3932
- #
4232
+ self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers.providers)
3933
4233
 
3934
- def check_output_str(
3935
- self,
3936
- *cmd: str,
3937
- **kwargs: ta.Any,
3938
- ) -> str:
3939
- return self.check_output(*cmd, **kwargs).decode().strip()
4234
+ async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
4235
+ lst = [
4236
+ (i, si)
4237
+ for i, p in enumerate(self._providers.values())
4238
+ for si in await p.get_installed_versions(spec)
4239
+ if spec.contains(si)
4240
+ ]
3940
4241
 
3941
- #
4242
+ slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
4243
+ if not slst:
4244
+ return None
3942
4245
 
3943
- def try_call(
3944
- self,
3945
- *cmd: str,
3946
- **kwargs: ta.Any,
3947
- ) -> bool:
3948
- if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
3949
- return False
3950
- else:
3951
- return True
4246
+ bi, bv = slst[-1]
4247
+ bp = list(self._providers.values())[bi]
4248
+ return (bp, bv)
3952
4249
 
3953
- def try_output(
4250
+ async def resolve(
3954
4251
  self,
3955
- *cmd: str,
3956
- **kwargs: ta.Any,
3957
- ) -> ta.Optional[bytes]:
3958
- if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
3959
- return None
3960
- else:
3961
- return ret
4252
+ spec: InterpSpecifier,
4253
+ *,
4254
+ install: bool = False,
4255
+ ) -> ta.Optional[Interp]:
4256
+ tup = await self._resolve_installed(spec)
4257
+ if tup is not None:
4258
+ bp, bv = tup
4259
+ return await bp.get_installed_version(bv)
3962
4260
 
3963
- def try_output_str(
3964
- self,
3965
- *cmd: str,
3966
- **kwargs: ta.Any,
3967
- ) -> ta.Optional[str]:
3968
- if (ret := self.try_output(*cmd, **kwargs)) is None:
4261
+ if not install:
3969
4262
  return None
3970
- else:
3971
- return ret.decode().strip()
3972
-
3973
-
3974
- ##
3975
4263
 
4264
+ tp = list(self._providers.values())[0] # noqa
3976
4265
 
3977
- class Subprocesses(AbstractSubprocesses):
3978
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
3979
- proc = subprocess.run(
3980
- run.cmd,
3981
- input=run.input,
3982
- timeout=run.timeout,
3983
- check=run.check,
3984
- capture_output=run.capture_output or False,
3985
- **(run.kwargs or {}),
4266
+ sv = sorted(
4267
+ [s for s in await tp.get_installable_versions(spec) if s in spec],
4268
+ key=lambda s: s.version,
3986
4269
  )
4270
+ if not sv:
4271
+ return None
3987
4272
 
3988
- return SubprocessRunOutput(
3989
- proc=proc,
3990
-
3991
- returncode=proc.returncode,
4273
+ bv = sv[-1]
4274
+ return await tp.install_version(bv)
3992
4275
 
3993
- stdout=proc.stdout, # noqa
3994
- stderr=proc.stderr, # noqa
3995
- )
4276
+ async def list(self, spec: InterpSpecifier) -> None:
4277
+ print('installed:')
4278
+ for n, p in self._providers.items():
4279
+ lst = [
4280
+ si
4281
+ for si in await p.get_installed_versions(spec)
4282
+ if spec.contains(si)
4283
+ ]
4284
+ if lst:
4285
+ print(f' {n}')
4286
+ for si in lst:
4287
+ print(f' {si}')
3996
4288
 
3997
- def check_call(
3998
- self,
3999
- *cmd: str,
4000
- stdout: ta.Any = sys.stderr,
4001
- **kwargs: ta.Any,
4002
- ) -> None:
4003
- with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
4004
- subprocess.check_call(cmd, **kwargs)
4289
+ print()
4005
4290
 
4006
- def check_output(
4007
- self,
4008
- *cmd: str,
4009
- **kwargs: ta.Any,
4010
- ) -> bytes:
4011
- with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
4012
- return subprocess.check_output(cmd, **kwargs)
4291
+ print('installable:')
4292
+ for n, p in self._providers.items():
4293
+ lst = [
4294
+ si
4295
+ for si in await p.get_installable_versions(spec)
4296
+ if spec.contains(si)
4297
+ ]
4298
+ if lst:
4299
+ print(f' {n}')
4300
+ for si in lst:
4301
+ print(f' {si}')
4013
4302
 
4014
4303
 
4015
- subprocesses = Subprocesses()
4304
+ ########################################
4305
+ # ../../../omlish/subprocesses/async_.py
4016
4306
 
4017
4307
 
4018
4308
  ##
@@ -4027,7 +4317,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4027
4317
  self,
4028
4318
  *cmd: str,
4029
4319
  input: ta.Any = None, # noqa
4030
- timeout: ta.Optional[float] = None,
4320
+ timeout: ta.Optional[TimeoutLike] = None,
4031
4321
  check: bool = False,
4032
4322
  capture_output: ta.Optional[bool] = None,
4033
4323
  **kwargs: ta.Any,
@@ -4102,50 +4392,6 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4102
4392
  return ret.decode().strip()
4103
4393
 
4104
4394
 
4105
- ########################################
4106
- # ../providers/base.py
4107
- """
4108
- TODO:
4109
- - backends
4110
- - local builds
4111
- - deadsnakes?
4112
- - uv
4113
- - loose versions
4114
- """
4115
-
4116
-
4117
- ##
4118
-
4119
-
4120
- class InterpProvider(abc.ABC):
4121
- name: ta.ClassVar[str]
4122
-
4123
- def __init_subclass__(cls, **kwargs: ta.Any) -> None:
4124
- super().__init_subclass__(**kwargs)
4125
- if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
4126
- sfx = 'InterpProvider'
4127
- if not cls.__name__.endswith(sfx):
4128
- raise NameError(cls)
4129
- setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
4130
-
4131
- @abc.abstractmethod
4132
- def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
4133
- raise NotImplementedError
4134
-
4135
- @abc.abstractmethod
4136
- def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
4137
- raise NotImplementedError
4138
-
4139
- async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
4140
- return []
4141
-
4142
- async def install_version(self, version: InterpVersion) -> Interp:
4143
- raise TypeError
4144
-
4145
-
4146
- InterpProviders = ta.NewType('InterpProviders', ta.Sequence[InterpProvider])
4147
-
4148
-
4149
4395
  ########################################
4150
4396
  # ../../../omlish/asyncs/asyncio/subprocesses.py
4151
4397
 
@@ -4261,7 +4507,7 @@ class AsyncioProcessCommunicator:
4261
4507
  async def communicate(
4262
4508
  self,
4263
4509
  input: ta.Any = None, # noqa
4264
- timeout: ta.Optional[float] = None,
4510
+ timeout: ta.Optional[TimeoutLike] = None,
4265
4511
  ) -> Communication:
4266
4512
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
4267
4513
 
@@ -4274,7 +4520,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4274
4520
  self,
4275
4521
  proc: asyncio.subprocess.Process,
4276
4522
  input: ta.Any = None, # noqa
4277
- timeout: ta.Optional[float] = None,
4523
+ timeout: ta.Optional[TimeoutLike] = None,
4278
4524
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
4279
4525
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
4280
4526
 
@@ -4285,22 +4531,22 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4285
4531
  self,
4286
4532
  *cmd: str,
4287
4533
  shell: bool = False,
4288
- timeout: ta.Optional[float] = None,
4534
+ timeout: ta.Optional[TimeoutLike] = None,
4289
4535
  **kwargs: ta.Any,
4290
4536
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
4291
- fac: ta.Any
4292
- if shell:
4293
- fac = functools.partial(
4294
- asyncio.create_subprocess_shell,
4295
- check.single(cmd),
4296
- )
4297
- else:
4298
- fac = functools.partial(
4299
- asyncio.create_subprocess_exec,
4300
- *cmd,
4301
- )
4302
-
4303
4537
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
4538
+ fac: ta.Any
4539
+ if shell:
4540
+ fac = functools.partial(
4541
+ asyncio.create_subprocess_shell,
4542
+ check.single(cmd),
4543
+ )
4544
+ else:
4545
+ fac = functools.partial(
4546
+ asyncio.create_subprocess_exec,
4547
+ *cmd,
4548
+ )
4549
+
4304
4550
  proc: asyncio.subprocess.Process = await fac(**kwargs)
4305
4551
  try:
4306
4552
  yield proc
@@ -4541,94 +4787,6 @@ class Pyenv:
4541
4787
  return True
4542
4788
 
4543
4789
 
4544
- ########################################
4545
- # ../resolvers.py
4546
-
4547
-
4548
- @dc.dataclass(frozen=True)
4549
- class InterpResolverProviders:
4550
- providers: ta.Sequence[ta.Tuple[str, InterpProvider]]
4551
-
4552
-
4553
- class InterpResolver:
4554
- def __init__(
4555
- self,
4556
- providers: InterpResolverProviders,
4557
- ) -> None:
4558
- super().__init__()
4559
-
4560
- self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers.providers)
4561
-
4562
- async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
4563
- lst = [
4564
- (i, si)
4565
- for i, p in enumerate(self._providers.values())
4566
- for si in await p.get_installed_versions(spec)
4567
- if spec.contains(si)
4568
- ]
4569
-
4570
- slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
4571
- if not slst:
4572
- return None
4573
-
4574
- bi, bv = slst[-1]
4575
- bp = list(self._providers.values())[bi]
4576
- return (bp, bv)
4577
-
4578
- async def resolve(
4579
- self,
4580
- spec: InterpSpecifier,
4581
- *,
4582
- install: bool = False,
4583
- ) -> ta.Optional[Interp]:
4584
- tup = await self._resolve_installed(spec)
4585
- if tup is not None:
4586
- bp, bv = tup
4587
- return await bp.get_installed_version(bv)
4588
-
4589
- if not install:
4590
- return None
4591
-
4592
- tp = list(self._providers.values())[0] # noqa
4593
-
4594
- sv = sorted(
4595
- [s for s in await tp.get_installable_versions(spec) if s in spec],
4596
- key=lambda s: s.version,
4597
- )
4598
- if not sv:
4599
- return None
4600
-
4601
- bv = sv[-1]
4602
- return await tp.install_version(bv)
4603
-
4604
- async def list(self, spec: InterpSpecifier) -> None:
4605
- print('installed:')
4606
- for n, p in self._providers.items():
4607
- lst = [
4608
- si
4609
- for si in await p.get_installed_versions(spec)
4610
- if spec.contains(si)
4611
- ]
4612
- if lst:
4613
- print(f' {n}')
4614
- for si in lst:
4615
- print(f' {si}')
4616
-
4617
- print()
4618
-
4619
- print('installable:')
4620
- for n, p in self._providers.items():
4621
- lst = [
4622
- si
4623
- for si in await p.get_installable_versions(spec)
4624
- if spec.contains(si)
4625
- ]
4626
- if lst:
4627
- print(f' {n}')
4628
- for si in lst:
4629
- print(f' {si}')
4630
-
4631
-
4632
4790
  ########################################
4633
4791
  # ../providers/running.py
4634
4792