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.
@@ -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
  """