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.
@@ -88,9 +88,6 @@ VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalTy
88
88
  VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
89
89
  VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
90
90
 
91
- # ../../omlish/asyncs/asyncio/timeouts.py
92
- AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
93
-
94
91
  # ../../omlish/formats/toml/parser.py
95
92
  TomlParseFloat = ta.Callable[[str], ta.Any]
96
93
  TomlKey = ta.Tuple[str, ...]
@@ -108,6 +105,9 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
108
105
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
109
106
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
110
107
 
108
+ # ../../omlish/lite/timeouts.py
109
+ TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
110
+
111
111
  # ../../omlish/lite/typing.py
112
112
  A0 = ta.TypeVar('A0')
113
113
  A1 = ta.TypeVar('A1')
@@ -121,6 +121,9 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
121
121
  # ../../omlish/argparse/cli.py
122
122
  ArgparseCmdFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
123
123
 
124
+ # ../../omlish/asyncs/asyncio/timeouts.py
125
+ AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
126
+
124
127
  # ../../omlish/lite/inject.py
125
128
  U = ta.TypeVar('U')
126
129
  InjectorKeyCls = ta.Union[type, ta.NewType]
@@ -128,7 +131,7 @@ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
128
131
  InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
129
132
  InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
130
133
 
131
- # ../../omlish/subprocesses.py
134
+ # ../../omlish/subprocesses/base.py
132
135
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
133
136
 
134
137
 
@@ -858,19 +861,6 @@ class WheelFile(zipfile.ZipFile):
858
861
  super().close()
859
862
 
860
863
 
861
- ########################################
862
- # ../../../omlish/asyncs/asyncio/timeouts.py
863
-
864
-
865
- def asyncio_maybe_timeout(
866
- fut: AwaitableT,
867
- timeout: ta.Optional[float] = None,
868
- ) -> AwaitableT:
869
- if timeout is not None:
870
- fut = asyncio.wait_for(fut, timeout) # type: ignore
871
- return fut
872
-
873
-
874
864
  ########################################
875
865
  # ../../../omlish/formats/toml/parser.py
876
866
  # SPDX-License-Identifier: MIT
@@ -2611,6 +2601,205 @@ def format_num_bytes(num_bytes: int) -> str:
2611
2601
  return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
2612
2602
 
2613
2603
 
2604
+ ########################################
2605
+ # ../../../omlish/lite/timeouts.py
2606
+ """
2607
+ TODO:
2608
+ - Event (/ Predicate)
2609
+ """
2610
+
2611
+
2612
+ ##
2613
+
2614
+
2615
+ class Timeout(abc.ABC):
2616
+ @property
2617
+ @abc.abstractmethod
2618
+ def can_expire(self) -> bool:
2619
+ """Indicates whether or not this timeout will ever expire."""
2620
+
2621
+ raise NotImplementedError
2622
+
2623
+ @abc.abstractmethod
2624
+ def expired(self) -> bool:
2625
+ """Return whether or not this timeout has expired."""
2626
+
2627
+ raise NotImplementedError
2628
+
2629
+ @abc.abstractmethod
2630
+ def remaining(self) -> float:
2631
+ """Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
2632
+
2633
+ raise NotImplementedError
2634
+
2635
+ @abc.abstractmethod
2636
+ def __call__(self) -> float:
2637
+ """Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
2638
+
2639
+ raise NotImplementedError
2640
+
2641
+ @abc.abstractmethod
2642
+ def or_(self, o: ta.Any) -> ta.Any:
2643
+ """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
2644
+
2645
+ raise NotImplementedError
2646
+
2647
+ #
2648
+
2649
+ @classmethod
2650
+ def _now(cls) -> float:
2651
+ return time.time()
2652
+
2653
+ #
2654
+
2655
+ class Default:
2656
+ def __new__(cls, *args, **kwargs): # noqa
2657
+ raise TypeError
2658
+
2659
+ class _NOT_SPECIFIED: # noqa
2660
+ def __new__(cls, *args, **kwargs): # noqa
2661
+ raise TypeError
2662
+
2663
+ @classmethod
2664
+ def of(
2665
+ cls,
2666
+ obj: ta.Optional[TimeoutLike],
2667
+ default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
2668
+ ) -> 'Timeout':
2669
+ if obj is None:
2670
+ return InfiniteTimeout()
2671
+
2672
+ elif isinstance(obj, Timeout):
2673
+ return obj
2674
+
2675
+ elif isinstance(obj, (float, int)):
2676
+ return DeadlineTimeout(cls._now() + obj)
2677
+
2678
+ elif isinstance(obj, ta.Iterable):
2679
+ return CompositeTimeout(*[Timeout.of(c) for c in obj])
2680
+
2681
+ elif obj is Timeout.Default:
2682
+ if default is Timeout._NOT_SPECIFIED or default is Timeout.Default:
2683
+ raise RuntimeError('Must specify a default timeout')
2684
+
2685
+ else:
2686
+ return Timeout.of(default) # type: ignore[arg-type]
2687
+
2688
+ else:
2689
+ raise TypeError(obj)
2690
+
2691
+ @classmethod
2692
+ def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
2693
+ return DeadlineTimeout(deadline)
2694
+
2695
+ @classmethod
2696
+ def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
2697
+ return PredicateTimeout(expired_fn)
2698
+
2699
+
2700
+ class DeadlineTimeout(Timeout):
2701
+ def __init__(
2702
+ self,
2703
+ deadline: float,
2704
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
2705
+ ) -> None:
2706
+ super().__init__()
2707
+
2708
+ self.deadline = deadline
2709
+ self.exc = exc
2710
+
2711
+ @property
2712
+ def can_expire(self) -> bool:
2713
+ return True
2714
+
2715
+ def expired(self) -> bool:
2716
+ return not (self.remaining() > 0)
2717
+
2718
+ def remaining(self) -> float:
2719
+ return self.deadline - self._now()
2720
+
2721
+ def __call__(self) -> float:
2722
+ if (rem := self.remaining()) > 0:
2723
+ return rem
2724
+ raise self.exc
2725
+
2726
+ def or_(self, o: ta.Any) -> ta.Any:
2727
+ return self()
2728
+
2729
+
2730
+ class InfiniteTimeout(Timeout):
2731
+ @property
2732
+ def can_expire(self) -> bool:
2733
+ return False
2734
+
2735
+ def expired(self) -> bool:
2736
+ return False
2737
+
2738
+ def remaining(self) -> float:
2739
+ return float('inf')
2740
+
2741
+ def __call__(self) -> float:
2742
+ return float('inf')
2743
+
2744
+ def or_(self, o: ta.Any) -> ta.Any:
2745
+ return o
2746
+
2747
+
2748
+ class CompositeTimeout(Timeout):
2749
+ def __init__(self, *children: Timeout) -> None:
2750
+ super().__init__()
2751
+
2752
+ self.children = children
2753
+
2754
+ @property
2755
+ def can_expire(self) -> bool:
2756
+ return any(c.can_expire for c in self.children)
2757
+
2758
+ def expired(self) -> bool:
2759
+ return any(c.expired() for c in self.children)
2760
+
2761
+ def remaining(self) -> float:
2762
+ return min(c.remaining() for c in self.children)
2763
+
2764
+ def __call__(self) -> float:
2765
+ return min(c() for c in self.children)
2766
+
2767
+ def or_(self, o: ta.Any) -> ta.Any:
2768
+ if self.can_expire:
2769
+ return self()
2770
+ return o
2771
+
2772
+
2773
+ class PredicateTimeout(Timeout):
2774
+ def __init__(
2775
+ self,
2776
+ expired_fn: ta.Callable[[], bool],
2777
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
2778
+ ) -> None:
2779
+ super().__init__()
2780
+
2781
+ self.expired_fn = expired_fn
2782
+ self.exc = exc
2783
+
2784
+ @property
2785
+ def can_expire(self) -> bool:
2786
+ return True
2787
+
2788
+ def expired(self) -> bool:
2789
+ return self.expired_fn()
2790
+
2791
+ def remaining(self) -> float:
2792
+ return float('inf')
2793
+
2794
+ def __call__(self) -> float:
2795
+ if not self.expired_fn():
2796
+ return float('inf')
2797
+ raise self.exc
2798
+
2799
+ def or_(self, o: ta.Any) -> ta.Any:
2800
+ return self()
2801
+
2802
+
2614
2803
  ########################################
2615
2804
  # ../../../omlish/lite/typing.py
2616
2805
 
@@ -3882,6 +4071,19 @@ class ArgparseCli:
3882
4071
  return fn()
3883
4072
 
3884
4073
 
4074
+ ########################################
4075
+ # ../../../omlish/asyncs/asyncio/timeouts.py
4076
+
4077
+
4078
+ def asyncio_maybe_timeout(
4079
+ fut: AwaitableT,
4080
+ timeout: ta.Optional[TimeoutLike] = None,
4081
+ ) -> AwaitableT:
4082
+ if timeout is not None:
4083
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
4084
+ return fut
4085
+
4086
+
3885
4087
  ########################################
3886
4088
  # ../../../omlish/lite/inject.py
3887
4089
 
@@ -5625,6 +5827,137 @@ class JsonLogFormatter(logging.Formatter):
5625
5827
  return self._json_dumps(dct)
5626
5828
 
5627
5829
 
5830
+ ########################################
5831
+ # ../../../omlish/subprocesses/run.py
5832
+
5833
+
5834
+ ##
5835
+
5836
+
5837
+ @dc.dataclass(frozen=True)
5838
+ class SubprocessRunOutput(ta.Generic[T]):
5839
+ proc: T
5840
+
5841
+ returncode: int # noqa
5842
+
5843
+ stdout: ta.Optional[bytes] = None
5844
+ stderr: ta.Optional[bytes] = None
5845
+
5846
+
5847
+ ##
5848
+
5849
+
5850
+ @dc.dataclass(frozen=True)
5851
+ class SubprocessRun:
5852
+ cmd: ta.Sequence[str]
5853
+ input: ta.Any = None
5854
+ timeout: ta.Optional[TimeoutLike] = None
5855
+ check: bool = False
5856
+ capture_output: ta.Optional[bool] = None
5857
+ kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
5858
+
5859
+ #
5860
+
5861
+ _FIELD_NAMES: ta.ClassVar[ta.FrozenSet[str]]
5862
+
5863
+ def replace(self, **kwargs: ta.Any) -> 'SubprocessRun':
5864
+ if not kwargs:
5865
+ return self
5866
+
5867
+ field_kws = {}
5868
+ extra_kws = {}
5869
+ for k, v in kwargs.items():
5870
+ if k in self._FIELD_NAMES:
5871
+ field_kws[k] = v
5872
+ else:
5873
+ extra_kws[k] = v
5874
+
5875
+ return dc.replace(self, **{
5876
+ **dict(kwargs={
5877
+ **(self.kwargs or {}),
5878
+ **extra_kws,
5879
+ }),
5880
+ **field_kws, # passing a kwarg named 'kwargs' intentionally clobbers
5881
+ })
5882
+
5883
+ #
5884
+
5885
+ @classmethod
5886
+ def of(
5887
+ cls,
5888
+ *cmd: str,
5889
+ input: ta.Any = None, # noqa
5890
+ timeout: ta.Optional[TimeoutLike] = None,
5891
+ check: bool = False, # noqa
5892
+ capture_output: ta.Optional[bool] = None,
5893
+ **kwargs: ta.Any,
5894
+ ) -> 'SubprocessRun':
5895
+ return cls(
5896
+ cmd=cmd,
5897
+ input=input,
5898
+ timeout=timeout,
5899
+ check=check,
5900
+ capture_output=capture_output,
5901
+ kwargs=kwargs,
5902
+ )
5903
+
5904
+ #
5905
+
5906
+ _DEFAULT_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractSubprocesses
5907
+
5908
+ def run(
5909
+ self,
5910
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
5911
+ **kwargs: ta.Any,
5912
+ ) -> SubprocessRunOutput:
5913
+ if subprocesses is None:
5914
+ subprocesses = self._DEFAULT_SUBPROCESSES
5915
+ return check.not_none(subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
5916
+
5917
+ _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
5918
+
5919
+ async def async_run(
5920
+ self,
5921
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
5922
+ **kwargs: ta.Any,
5923
+ ) -> SubprocessRunOutput:
5924
+ if async_subprocesses is None:
5925
+ async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
5926
+ return await check.not_none(async_subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
5927
+
5928
+
5929
+ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessRun)) # noqa
5930
+
5931
+
5932
+ ##
5933
+
5934
+
5935
+ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
5936
+ @abc.abstractmethod
5937
+ def make_run(self) -> SubprocessRun:
5938
+ raise NotImplementedError
5939
+
5940
+ @abc.abstractmethod
5941
+ def handle_run_output(self, output: SubprocessRunOutput) -> T:
5942
+ raise NotImplementedError
5943
+
5944
+ #
5945
+
5946
+ def run(
5947
+ self,
5948
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
5949
+ **kwargs: ta.Any,
5950
+ ) -> T:
5951
+ return self.handle_run_output(self.make_run().run(subprocesses, **kwargs))
5952
+
5953
+ async def async_run(
5954
+ self,
5955
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
5956
+ **kwargs: ta.Any,
5957
+ ) -> T:
5958
+ return self.handle_run_output(await self.make_run().async_run(async_subprocesses, **kwargs))
5959
+
5960
+
5628
5961
  ########################################
5629
5962
  # ../../interp/types.py
5630
5963
 
@@ -5863,23 +6196,7 @@ def configure_standard_logging(
5863
6196
 
5864
6197
 
5865
6198
  ########################################
5866
- # ../../../omlish/subprocesses.py
5867
-
5868
-
5869
- ##
5870
-
5871
-
5872
- # Valid channel type kwarg values:
5873
- # - A special flag negative int
5874
- # - A positive fd int
5875
- # - A file-like object
5876
- # - None
5877
-
5878
- SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
5879
- 'pipe': subprocess.PIPE,
5880
- 'stdout': subprocess.STDOUT,
5881
- 'devnull': subprocess.DEVNULL,
5882
- }
6199
+ # ../../../omlish/subprocesses/wrap.py
5883
6200
 
5884
6201
 
5885
6202
  ##
@@ -5899,28 +6216,74 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
5899
6216
  return cmd
5900
6217
 
5901
6218
 
5902
- ##
5903
-
5904
-
5905
- def subprocess_close(
5906
- proc: subprocess.Popen,
5907
- timeout: ta.Optional[float] = None,
5908
- ) -> None:
5909
- # TODO: terminate, sleep, kill
5910
- if proc.stdout:
5911
- proc.stdout.close()
5912
- if proc.stderr:
5913
- proc.stderr.close()
5914
- if proc.stdin:
5915
- proc.stdin.close()
5916
-
5917
- proc.wait(timeout)
6219
+ ########################################
6220
+ # ../../interp/providers/base.py
6221
+ """
6222
+ TODO:
6223
+ - backends
6224
+ - local builds
6225
+ - deadsnakes?
6226
+ - uv
6227
+ - loose versions
6228
+ """
5918
6229
 
5919
6230
 
5920
6231
  ##
5921
6232
 
5922
6233
 
5923
- class VerboseCalledProcessError(subprocess.CalledProcessError):
6234
+ class InterpProvider(abc.ABC):
6235
+ name: ta.ClassVar[str]
6236
+
6237
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
6238
+ super().__init_subclass__(**kwargs)
6239
+ if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
6240
+ sfx = 'InterpProvider'
6241
+ if not cls.__name__.endswith(sfx):
6242
+ raise NameError(cls)
6243
+ setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
6244
+
6245
+ @abc.abstractmethod
6246
+ def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
6247
+ raise NotImplementedError
6248
+
6249
+ @abc.abstractmethod
6250
+ def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
6251
+ raise NotImplementedError
6252
+
6253
+ async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
6254
+ return []
6255
+
6256
+ async def install_version(self, version: InterpVersion) -> Interp:
6257
+ raise TypeError
6258
+
6259
+
6260
+ InterpProviders = ta.NewType('InterpProviders', ta.Sequence[InterpProvider])
6261
+
6262
+
6263
+ ########################################
6264
+ # ../../../omlish/subprocesses/base.py
6265
+
6266
+
6267
+ ##
6268
+
6269
+
6270
+ # Valid channel type kwarg values:
6271
+ # - A special flag negative int
6272
+ # - A positive fd int
6273
+ # - A file-like object
6274
+ # - None
6275
+
6276
+ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
6277
+ 'pipe': subprocess.PIPE,
6278
+ 'stdout': subprocess.STDOUT,
6279
+ 'devnull': subprocess.DEVNULL,
6280
+ }
6281
+
6282
+
6283
+ ##
6284
+
6285
+
6286
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
5924
6287
  @classmethod
5925
6288
  def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
5926
6289
  return cls(
@@ -5997,6 +6360,11 @@ class BaseSubprocesses(abc.ABC): # noqa
5997
6360
 
5998
6361
  #
5999
6362
 
6363
+ if 'timeout' in kwargs:
6364
+ kwargs['timeout'] = Timeout.of(kwargs['timeout']).or_(None)
6365
+
6366
+ #
6367
+
6000
6368
  return cmd, dict(
6001
6369
  env=env,
6002
6370
  shell=shell,
@@ -6101,62 +6469,115 @@ class BaseSubprocesses(abc.ABC): # noqa
6101
6469
  return e
6102
6470
 
6103
6471
 
6104
- ##
6472
+ ########################################
6473
+ # ../../interp/resolvers.py
6105
6474
 
6106
6475
 
6107
6476
  @dc.dataclass(frozen=True)
6108
- class SubprocessRun:
6109
- cmd: ta.Sequence[str]
6110
- input: ta.Any = None
6111
- timeout: ta.Optional[float] = None
6112
- check: bool = False
6113
- capture_output: ta.Optional[bool] = None
6114
- kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
6477
+ class InterpResolverProviders:
6478
+ providers: ta.Sequence[ta.Tuple[str, InterpProvider]]
6115
6479
 
6116
- @classmethod
6117
- def of(
6118
- cls,
6119
- *cmd: str,
6120
- input: ta.Any = None, # noqa
6121
- timeout: ta.Optional[float] = None,
6122
- check: bool = False,
6123
- capture_output: ta.Optional[bool] = None,
6124
- **kwargs: ta.Any,
6125
- ) -> 'SubprocessRun':
6126
- return cls(
6127
- cmd=cmd,
6128
- input=input,
6129
- timeout=timeout,
6130
- check=check,
6131
- capture_output=capture_output,
6132
- kwargs=kwargs,
6480
+
6481
+ class InterpResolver:
6482
+ def __init__(
6483
+ self,
6484
+ providers: InterpResolverProviders,
6485
+ ) -> None:
6486
+ super().__init__()
6487
+
6488
+ self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers.providers)
6489
+
6490
+ async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
6491
+ lst = [
6492
+ (i, si)
6493
+ for i, p in enumerate(self._providers.values())
6494
+ for si in await p.get_installed_versions(spec)
6495
+ if spec.contains(si)
6496
+ ]
6497
+
6498
+ slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
6499
+ if not slst:
6500
+ return None
6501
+
6502
+ bi, bv = slst[-1]
6503
+ bp = list(self._providers.values())[bi]
6504
+ return (bp, bv)
6505
+
6506
+ async def resolve(
6507
+ self,
6508
+ spec: InterpSpecifier,
6509
+ *,
6510
+ install: bool = False,
6511
+ ) -> ta.Optional[Interp]:
6512
+ tup = await self._resolve_installed(spec)
6513
+ if tup is not None:
6514
+ bp, bv = tup
6515
+ return await bp.get_installed_version(bv)
6516
+
6517
+ if not install:
6518
+ return None
6519
+
6520
+ tp = list(self._providers.values())[0] # noqa
6521
+
6522
+ sv = sorted(
6523
+ [s for s in await tp.get_installable_versions(spec) if s in spec],
6524
+ key=lambda s: s.version,
6133
6525
  )
6526
+ if not sv:
6527
+ return None
6134
6528
 
6529
+ bv = sv[-1]
6530
+ return await tp.install_version(bv)
6135
6531
 
6136
- @dc.dataclass(frozen=True)
6137
- class SubprocessRunOutput(ta.Generic[T]):
6138
- proc: T
6532
+ async def list(self, spec: InterpSpecifier) -> None:
6533
+ print('installed:')
6534
+ for n, p in self._providers.items():
6535
+ lst = [
6536
+ si
6537
+ for si in await p.get_installed_versions(spec)
6538
+ if spec.contains(si)
6539
+ ]
6540
+ if lst:
6541
+ print(f' {n}')
6542
+ for si in lst:
6543
+ print(f' {si}')
6139
6544
 
6140
- returncode: int # noqa
6545
+ print()
6141
6546
 
6142
- stdout: ta.Optional[bytes] = None
6143
- stderr: ta.Optional[bytes] = None
6547
+ print('installable:')
6548
+ for n, p in self._providers.items():
6549
+ lst = [
6550
+ si
6551
+ for si in await p.get_installable_versions(spec)
6552
+ if spec.contains(si)
6553
+ ]
6554
+ if lst:
6555
+ print(f' {n}')
6556
+ for si in lst:
6557
+ print(f' {si}')
6144
6558
 
6145
6559
 
6146
- class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
6560
+ ########################################
6561
+ # ../../../omlish/subprocesses/async_.py
6562
+
6563
+
6564
+ ##
6565
+
6566
+
6567
+ class AbstractAsyncSubprocesses(BaseSubprocesses):
6147
6568
  @abc.abstractmethod
6148
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
6569
+ async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
6149
6570
  raise NotImplementedError
6150
6571
 
6151
6572
  def run(
6152
6573
  self,
6153
6574
  *cmd: str,
6154
6575
  input: ta.Any = None, # noqa
6155
- timeout: ta.Optional[float] = None,
6576
+ timeout: ta.Optional[TimeoutLike] = None,
6156
6577
  check: bool = False,
6157
6578
  capture_output: ta.Optional[bool] = None,
6158
6579
  **kwargs: ta.Any,
6159
- ) -> SubprocessRunOutput:
6580
+ ) -> ta.Awaitable[SubprocessRunOutput]:
6160
6581
  return self.run_(SubprocessRun(
6161
6582
  cmd=cmd,
6162
6583
  input=input,
@@ -6169,7 +6590,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
6169
6590
  #
6170
6591
 
6171
6592
  @abc.abstractmethod
6172
- def check_call(
6593
+ async def check_call(
6173
6594
  self,
6174
6595
  *cmd: str,
6175
6596
  stdout: ta.Any = sys.stderr,
@@ -6178,7 +6599,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
6178
6599
  raise NotImplementedError
6179
6600
 
6180
6601
  @abc.abstractmethod
6181
- def check_output(
6602
+ async def check_output(
6182
6603
  self,
6183
6604
  *cmd: str,
6184
6605
  **kwargs: ta.Any,
@@ -6187,107 +6608,72 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
6187
6608
 
6188
6609
  #
6189
6610
 
6190
- def check_output_str(
6611
+ async def check_output_str(
6191
6612
  self,
6192
6613
  *cmd: str,
6193
6614
  **kwargs: ta.Any,
6194
6615
  ) -> str:
6195
- return self.check_output(*cmd, **kwargs).decode().strip()
6616
+ return (await self.check_output(*cmd, **kwargs)).decode().strip()
6196
6617
 
6197
6618
  #
6198
6619
 
6199
- def try_call(
6620
+ async def try_call(
6200
6621
  self,
6201
6622
  *cmd: str,
6202
6623
  **kwargs: ta.Any,
6203
6624
  ) -> bool:
6204
- if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
6625
+ if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
6205
6626
  return False
6206
6627
  else:
6207
6628
  return True
6208
6629
 
6209
- def try_output(
6630
+ async def try_output(
6210
6631
  self,
6211
6632
  *cmd: str,
6212
6633
  **kwargs: ta.Any,
6213
6634
  ) -> ta.Optional[bytes]:
6214
- if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
6635
+ if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
6215
6636
  return None
6216
6637
  else:
6217
6638
  return ret
6218
6639
 
6219
- def try_output_str(
6640
+ async def try_output_str(
6220
6641
  self,
6221
6642
  *cmd: str,
6222
6643
  **kwargs: ta.Any,
6223
6644
  ) -> ta.Optional[str]:
6224
- if (ret := self.try_output(*cmd, **kwargs)) is None:
6645
+ if (ret := await self.try_output(*cmd, **kwargs)) is None:
6225
6646
  return None
6226
6647
  else:
6227
6648
  return ret.decode().strip()
6228
6649
 
6229
6650
 
6230
- ##
6231
-
6232
-
6233
- class Subprocesses(AbstractSubprocesses):
6234
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
6235
- proc = subprocess.run(
6236
- run.cmd,
6237
- input=run.input,
6238
- timeout=run.timeout,
6239
- check=run.check,
6240
- capture_output=run.capture_output or False,
6241
- **(run.kwargs or {}),
6242
- )
6243
-
6244
- return SubprocessRunOutput(
6245
- proc=proc,
6246
-
6247
- returncode=proc.returncode,
6248
-
6249
- stdout=proc.stdout, # noqa
6250
- stderr=proc.stderr, # noqa
6251
- )
6252
-
6253
- def check_call(
6254
- self,
6255
- *cmd: str,
6256
- stdout: ta.Any = sys.stderr,
6257
- **kwargs: ta.Any,
6258
- ) -> None:
6259
- with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
6260
- subprocess.check_call(cmd, **kwargs)
6261
-
6262
- def check_output(
6263
- self,
6264
- *cmd: str,
6265
- **kwargs: ta.Any,
6266
- ) -> bytes:
6267
- with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
6268
- return subprocess.check_output(cmd, **kwargs)
6269
-
6270
-
6271
- subprocesses = Subprocesses()
6651
+ ########################################
6652
+ # ../../../omlish/subprocesses/sync.py
6653
+ """
6654
+ TODO:
6655
+ - popen
6656
+ - route check_calls through run_?
6657
+ """
6272
6658
 
6273
6659
 
6274
6660
  ##
6275
6661
 
6276
6662
 
6277
- class AbstractAsyncSubprocesses(BaseSubprocesses):
6663
+ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
6278
6664
  @abc.abstractmethod
6279
- async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
6665
+ def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
6280
6666
  raise NotImplementedError
6281
6667
 
6282
6668
  def run(
6283
6669
  self,
6284
6670
  *cmd: str,
6285
6671
  input: ta.Any = None, # noqa
6286
- timeout: ta.Optional[float] = None,
6672
+ timeout: ta.Optional[TimeoutLike] = None,
6287
6673
  check: bool = False,
6288
6674
  capture_output: ta.Optional[bool] = None,
6289
6675
  **kwargs: ta.Any,
6290
- ) -> ta.Awaitable[SubprocessRunOutput]:
6676
+ ) -> SubprocessRunOutput:
6291
6677
  return self.run_(SubprocessRun(
6292
6678
  cmd=cmd,
6293
6679
  input=input,
@@ -6300,7 +6686,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
6300
6686
  #
6301
6687
 
6302
6688
  @abc.abstractmethod
6303
- async def check_call(
6689
+ def check_call(
6304
6690
  self,
6305
6691
  *cmd: str,
6306
6692
  stdout: ta.Any = sys.stderr,
@@ -6309,7 +6695,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
6309
6695
  raise NotImplementedError
6310
6696
 
6311
6697
  @abc.abstractmethod
6312
- async def check_output(
6698
+ def check_output(
6313
6699
  self,
6314
6700
  *cmd: str,
6315
6701
  **kwargs: ta.Any,
@@ -6318,46 +6704,96 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
6318
6704
 
6319
6705
  #
6320
6706
 
6321
- async def check_output_str(
6707
+ def check_output_str(
6322
6708
  self,
6323
6709
  *cmd: str,
6324
6710
  **kwargs: ta.Any,
6325
6711
  ) -> str:
6326
- return (await self.check_output(*cmd, **kwargs)).decode().strip()
6712
+ return self.check_output(*cmd, **kwargs).decode().strip()
6327
6713
 
6328
6714
  #
6329
6715
 
6330
- async def try_call(
6716
+ def try_call(
6331
6717
  self,
6332
6718
  *cmd: str,
6333
6719
  **kwargs: ta.Any,
6334
6720
  ) -> bool:
6335
- if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
6721
+ if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
6336
6722
  return False
6337
6723
  else:
6338
6724
  return True
6339
6725
 
6340
- async def try_output(
6726
+ def try_output(
6341
6727
  self,
6342
6728
  *cmd: str,
6343
6729
  **kwargs: ta.Any,
6344
6730
  ) -> ta.Optional[bytes]:
6345
- if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
6731
+ if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
6346
6732
  return None
6347
6733
  else:
6348
6734
  return ret
6349
6735
 
6350
- async def try_output_str(
6736
+ def try_output_str(
6351
6737
  self,
6352
6738
  *cmd: str,
6353
6739
  **kwargs: ta.Any,
6354
6740
  ) -> ta.Optional[str]:
6355
- if (ret := await self.try_output(*cmd, **kwargs)) is None:
6741
+ if (ret := self.try_output(*cmd, **kwargs)) is None:
6356
6742
  return None
6357
6743
  else:
6358
6744
  return ret.decode().strip()
6359
6745
 
6360
6746
 
6747
+ ##
6748
+
6749
+
6750
+ class Subprocesses(AbstractSubprocesses):
6751
+ def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
6752
+ with self.prepare_and_wrap(
6753
+ *run.cmd,
6754
+ input=run.input,
6755
+ timeout=run.timeout,
6756
+ check=run.check,
6757
+ capture_output=run.capture_output or False,
6758
+ **(run.kwargs or {}),
6759
+ ) as (cmd, kwargs):
6760
+ proc = subprocess.run(cmd, **kwargs) # noqa
6761
+
6762
+ return SubprocessRunOutput(
6763
+ proc=proc,
6764
+
6765
+ returncode=proc.returncode,
6766
+
6767
+ stdout=proc.stdout, # noqa
6768
+ stderr=proc.stderr, # noqa
6769
+ )
6770
+
6771
+ def check_call(
6772
+ self,
6773
+ *cmd: str,
6774
+ stdout: ta.Any = sys.stderr,
6775
+ **kwargs: ta.Any,
6776
+ ) -> None:
6777
+ with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
6778
+ subprocess.check_call(cmd, **kwargs)
6779
+
6780
+ def check_output(
6781
+ self,
6782
+ *cmd: str,
6783
+ **kwargs: ta.Any,
6784
+ ) -> bytes:
6785
+ with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
6786
+ return subprocess.check_output(cmd, **kwargs)
6787
+
6788
+
6789
+ ##
6790
+
6791
+
6792
+ subprocesses = Subprocesses()
6793
+
6794
+ SubprocessRun._DEFAULT_SUBPROCESSES = subprocesses # noqa
6795
+
6796
+
6361
6797
  ########################################
6362
6798
  # ../../git/revisions.py
6363
6799
 
@@ -6404,50 +6840,6 @@ def get_git_revision(
6404
6840
  return dirty_rev + ('-untracked' if has_untracked else '')
6405
6841
 
6406
6842
 
6407
- ########################################
6408
- # ../../interp/providers/base.py
6409
- """
6410
- TODO:
6411
- - backends
6412
- - local builds
6413
- - deadsnakes?
6414
- - uv
6415
- - loose versions
6416
- """
6417
-
6418
-
6419
- ##
6420
-
6421
-
6422
- class InterpProvider(abc.ABC):
6423
- name: ta.ClassVar[str]
6424
-
6425
- def __init_subclass__(cls, **kwargs: ta.Any) -> None:
6426
- super().__init_subclass__(**kwargs)
6427
- if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
6428
- sfx = 'InterpProvider'
6429
- if not cls.__name__.endswith(sfx):
6430
- raise NameError(cls)
6431
- setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
6432
-
6433
- @abc.abstractmethod
6434
- def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
6435
- raise NotImplementedError
6436
-
6437
- @abc.abstractmethod
6438
- def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
6439
- raise NotImplementedError
6440
-
6441
- async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
6442
- return []
6443
-
6444
- async def install_version(self, version: InterpVersion) -> Interp:
6445
- raise TypeError
6446
-
6447
-
6448
- InterpProviders = ta.NewType('InterpProviders', ta.Sequence[InterpProvider])
6449
-
6450
-
6451
6843
  ########################################
6452
6844
  # ../../../omlish/asyncs/asyncio/subprocesses.py
6453
6845
 
@@ -6563,7 +6955,7 @@ class AsyncioProcessCommunicator:
6563
6955
  async def communicate(
6564
6956
  self,
6565
6957
  input: ta.Any = None, # noqa
6566
- timeout: ta.Optional[float] = None,
6958
+ timeout: ta.Optional[TimeoutLike] = None,
6567
6959
  ) -> Communication:
6568
6960
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
6569
6961
 
@@ -6576,7 +6968,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
6576
6968
  self,
6577
6969
  proc: asyncio.subprocess.Process,
6578
6970
  input: ta.Any = None, # noqa
6579
- timeout: ta.Optional[float] = None,
6971
+ timeout: ta.Optional[TimeoutLike] = None,
6580
6972
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
6581
6973
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
6582
6974
 
@@ -6587,22 +6979,22 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
6587
6979
  self,
6588
6980
  *cmd: str,
6589
6981
  shell: bool = False,
6590
- timeout: ta.Optional[float] = None,
6982
+ timeout: ta.Optional[TimeoutLike] = None,
6591
6983
  **kwargs: ta.Any,
6592
6984
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
6593
- fac: ta.Any
6594
- if shell:
6595
- fac = functools.partial(
6596
- asyncio.create_subprocess_shell,
6597
- check.single(cmd),
6598
- )
6599
- else:
6600
- fac = functools.partial(
6601
- asyncio.create_subprocess_exec,
6602
- *cmd,
6603
- )
6604
-
6605
6985
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
6986
+ fac: ta.Any
6987
+ if shell:
6988
+ fac = functools.partial(
6989
+ asyncio.create_subprocess_shell,
6990
+ check.single(cmd),
6991
+ )
6992
+ else:
6993
+ fac = functools.partial(
6994
+ asyncio.create_subprocess_exec,
6995
+ *cmd,
6996
+ )
6997
+
6606
6998
  proc: asyncio.subprocess.Process = await fac(**kwargs)
6607
6999
  try:
6608
7000
  yield proc
@@ -6843,94 +7235,6 @@ class Pyenv:
6843
7235
  return True
6844
7236
 
6845
7237
 
6846
- ########################################
6847
- # ../../interp/resolvers.py
6848
-
6849
-
6850
- @dc.dataclass(frozen=True)
6851
- class InterpResolverProviders:
6852
- providers: ta.Sequence[ta.Tuple[str, InterpProvider]]
6853
-
6854
-
6855
- class InterpResolver:
6856
- def __init__(
6857
- self,
6858
- providers: InterpResolverProviders,
6859
- ) -> None:
6860
- super().__init__()
6861
-
6862
- self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers.providers)
6863
-
6864
- async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
6865
- lst = [
6866
- (i, si)
6867
- for i, p in enumerate(self._providers.values())
6868
- for si in await p.get_installed_versions(spec)
6869
- if spec.contains(si)
6870
- ]
6871
-
6872
- slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
6873
- if not slst:
6874
- return None
6875
-
6876
- bi, bv = slst[-1]
6877
- bp = list(self._providers.values())[bi]
6878
- return (bp, bv)
6879
-
6880
- async def resolve(
6881
- self,
6882
- spec: InterpSpecifier,
6883
- *,
6884
- install: bool = False,
6885
- ) -> ta.Optional[Interp]:
6886
- tup = await self._resolve_installed(spec)
6887
- if tup is not None:
6888
- bp, bv = tup
6889
- return await bp.get_installed_version(bv)
6890
-
6891
- if not install:
6892
- return None
6893
-
6894
- tp = list(self._providers.values())[0] # noqa
6895
-
6896
- sv = sorted(
6897
- [s for s in await tp.get_installable_versions(spec) if s in spec],
6898
- key=lambda s: s.version,
6899
- )
6900
- if not sv:
6901
- return None
6902
-
6903
- bv = sv[-1]
6904
- return await tp.install_version(bv)
6905
-
6906
- async def list(self, spec: InterpSpecifier) -> None:
6907
- print('installed:')
6908
- for n, p in self._providers.items():
6909
- lst = [
6910
- si
6911
- for si in await p.get_installed_versions(spec)
6912
- if spec.contains(si)
6913
- ]
6914
- if lst:
6915
- print(f' {n}')
6916
- for si in lst:
6917
- print(f' {si}')
6918
-
6919
- print()
6920
-
6921
- print('installable:')
6922
- for n, p in self._providers.items():
6923
- lst = [
6924
- si
6925
- for si in await p.get_installable_versions(spec)
6926
- if spec.contains(si)
6927
- ]
6928
- if lst:
6929
- print(f' {n}')
6930
- for si in lst:
6931
- print(f' {si}')
6932
-
6933
-
6934
7238
  ########################################
6935
7239
  # ../../revisions.py
6936
7240
  """