omdev 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev226__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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