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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
omdev/scripts/ci.py CHANGED
@@ -64,9 +64,6 @@ T = ta.TypeVar('T')
64
64
  # ../../omlish/asyncs/asyncio/asyncio.py
65
65
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
66
66
 
67
- # ../../omlish/asyncs/asyncio/timeouts.py
68
- AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
69
-
70
67
  # ../../omlish/lite/check.py
71
68
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
72
69
  CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
@@ -75,9 +72,15 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
75
72
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
76
73
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
77
74
 
75
+ # ../../omlish/lite/timeouts.py
76
+ TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
77
+
78
78
  # ../../omlish/argparse/cli.py
79
79
  ArgparseCmdFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
80
80
 
81
+ # ../../omlish/asyncs/asyncio/timeouts.py
82
+ AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
83
+
81
84
  # ../../omlish/lite/contextmanagers.py
82
85
  ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
83
86
  AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
@@ -89,7 +92,7 @@ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
89
92
  InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
90
93
  InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
91
94
 
92
- # ../../omlish/subprocesses.py
95
+ # ../../omlish/subprocesses/base.py
93
96
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
94
97
 
95
98
 
@@ -245,19 +248,6 @@ async def asyncio_wait_concurrent(
245
248
  return [task.result() for task in done]
246
249
 
247
250
 
248
- ########################################
249
- # ../../../omlish/asyncs/asyncio/timeouts.py
250
-
251
-
252
- def asyncio_maybe_timeout(
253
- fut: AwaitableT,
254
- timeout: ta.Optional[float] = None,
255
- ) -> AwaitableT:
256
- if timeout is not None:
257
- fut = asyncio.wait_for(fut, timeout) # type: ignore
258
- return fut
259
-
260
-
261
251
  ########################################
262
252
  # ../../../omlish/lite/cached.py
263
253
 
@@ -1057,6 +1047,205 @@ def format_num_bytes(num_bytes: int) -> str:
1057
1047
  return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
1058
1048
 
1059
1049
 
1050
+ ########################################
1051
+ # ../../../omlish/lite/timeouts.py
1052
+ """
1053
+ TODO:
1054
+ - Event (/ Predicate)
1055
+ """
1056
+
1057
+
1058
+ ##
1059
+
1060
+
1061
+ class Timeout(abc.ABC):
1062
+ @property
1063
+ @abc.abstractmethod
1064
+ def can_expire(self) -> bool:
1065
+ """Indicates whether or not this timeout will ever expire."""
1066
+
1067
+ raise NotImplementedError
1068
+
1069
+ @abc.abstractmethod
1070
+ def expired(self) -> bool:
1071
+ """Return whether or not this timeout has expired."""
1072
+
1073
+ raise NotImplementedError
1074
+
1075
+ @abc.abstractmethod
1076
+ def remaining(self) -> float:
1077
+ """Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
1078
+
1079
+ raise NotImplementedError
1080
+
1081
+ @abc.abstractmethod
1082
+ def __call__(self) -> float:
1083
+ """Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
1084
+
1085
+ raise NotImplementedError
1086
+
1087
+ @abc.abstractmethod
1088
+ def or_(self, o: ta.Any) -> ta.Any:
1089
+ """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
1090
+
1091
+ raise NotImplementedError
1092
+
1093
+ #
1094
+
1095
+ @classmethod
1096
+ def _now(cls) -> float:
1097
+ return time.time()
1098
+
1099
+ #
1100
+
1101
+ class Default:
1102
+ def __new__(cls, *args, **kwargs): # noqa
1103
+ raise TypeError
1104
+
1105
+ class _NOT_SPECIFIED: # noqa
1106
+ def __new__(cls, *args, **kwargs): # noqa
1107
+ raise TypeError
1108
+
1109
+ @classmethod
1110
+ def of(
1111
+ cls,
1112
+ obj: ta.Optional[TimeoutLike],
1113
+ default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
1114
+ ) -> 'Timeout':
1115
+ if obj is None:
1116
+ return InfiniteTimeout()
1117
+
1118
+ elif isinstance(obj, Timeout):
1119
+ return obj
1120
+
1121
+ elif isinstance(obj, (float, int)):
1122
+ return DeadlineTimeout(cls._now() + obj)
1123
+
1124
+ elif isinstance(obj, ta.Iterable):
1125
+ return CompositeTimeout(*[Timeout.of(c) for c in obj])
1126
+
1127
+ elif obj is Timeout.Default:
1128
+ if default is Timeout._NOT_SPECIFIED or default is Timeout.Default:
1129
+ raise RuntimeError('Must specify a default timeout')
1130
+
1131
+ else:
1132
+ return Timeout.of(default) # type: ignore[arg-type]
1133
+
1134
+ else:
1135
+ raise TypeError(obj)
1136
+
1137
+ @classmethod
1138
+ def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
1139
+ return DeadlineTimeout(deadline)
1140
+
1141
+ @classmethod
1142
+ def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
1143
+ return PredicateTimeout(expired_fn)
1144
+
1145
+
1146
+ class DeadlineTimeout(Timeout):
1147
+ def __init__(
1148
+ self,
1149
+ deadline: float,
1150
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1151
+ ) -> None:
1152
+ super().__init__()
1153
+
1154
+ self.deadline = deadline
1155
+ self.exc = exc
1156
+
1157
+ @property
1158
+ def can_expire(self) -> bool:
1159
+ return True
1160
+
1161
+ def expired(self) -> bool:
1162
+ return not (self.remaining() > 0)
1163
+
1164
+ def remaining(self) -> float:
1165
+ return self.deadline - self._now()
1166
+
1167
+ def __call__(self) -> float:
1168
+ if (rem := self.remaining()) > 0:
1169
+ return rem
1170
+ raise self.exc
1171
+
1172
+ def or_(self, o: ta.Any) -> ta.Any:
1173
+ return self()
1174
+
1175
+
1176
+ class InfiniteTimeout(Timeout):
1177
+ @property
1178
+ def can_expire(self) -> bool:
1179
+ return False
1180
+
1181
+ def expired(self) -> bool:
1182
+ return False
1183
+
1184
+ def remaining(self) -> float:
1185
+ return float('inf')
1186
+
1187
+ def __call__(self) -> float:
1188
+ return float('inf')
1189
+
1190
+ def or_(self, o: ta.Any) -> ta.Any:
1191
+ return o
1192
+
1193
+
1194
+ class CompositeTimeout(Timeout):
1195
+ def __init__(self, *children: Timeout) -> None:
1196
+ super().__init__()
1197
+
1198
+ self.children = children
1199
+
1200
+ @property
1201
+ def can_expire(self) -> bool:
1202
+ return any(c.can_expire for c in self.children)
1203
+
1204
+ def expired(self) -> bool:
1205
+ return any(c.expired() for c in self.children)
1206
+
1207
+ def remaining(self) -> float:
1208
+ return min(c.remaining() for c in self.children)
1209
+
1210
+ def __call__(self) -> float:
1211
+ return min(c() for c in self.children)
1212
+
1213
+ def or_(self, o: ta.Any) -> ta.Any:
1214
+ if self.can_expire:
1215
+ return self()
1216
+ return o
1217
+
1218
+
1219
+ class PredicateTimeout(Timeout):
1220
+ def __init__(
1221
+ self,
1222
+ expired_fn: ta.Callable[[], bool],
1223
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1224
+ ) -> None:
1225
+ super().__init__()
1226
+
1227
+ self.expired_fn = expired_fn
1228
+ self.exc = exc
1229
+
1230
+ @property
1231
+ def can_expire(self) -> bool:
1232
+ return True
1233
+
1234
+ def expired(self) -> bool:
1235
+ return self.expired_fn()
1236
+
1237
+ def remaining(self) -> float:
1238
+ return float('inf')
1239
+
1240
+ def __call__(self) -> float:
1241
+ if not self.expired_fn():
1242
+ return float('inf')
1243
+ raise self.exc
1244
+
1245
+ def or_(self, o: ta.Any) -> ta.Any:
1246
+ return self()
1247
+
1248
+
1060
1249
  ########################################
1061
1250
  # ../../../omlish/logs/filters.py
1062
1251
 
@@ -1814,6 +2003,19 @@ class ArgparseCli:
1814
2003
  return fn()
1815
2004
 
1816
2005
 
2006
+ ########################################
2007
+ # ../../../omlish/asyncs/asyncio/timeouts.py
2008
+
2009
+
2010
+ def asyncio_maybe_timeout(
2011
+ fut: AwaitableT,
2012
+ timeout: ta.Optional[TimeoutLike] = None,
2013
+ ) -> AwaitableT:
2014
+ if timeout is not None:
2015
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
2016
+ return fut
2017
+
2018
+
1817
2019
  ########################################
1818
2020
  # ../../../omlish/lite/contextmanagers.py
1819
2021
 
@@ -3123,6 +3325,137 @@ def temp_named_file_context(
3123
3325
  shutil.rmtree(f.name, ignore_errors=True)
3124
3326
 
3125
3327
 
3328
+ ########################################
3329
+ # ../../../omlish/subprocesses/run.py
3330
+
3331
+
3332
+ ##
3333
+
3334
+
3335
+ @dc.dataclass(frozen=True)
3336
+ class SubprocessRunOutput(ta.Generic[T]):
3337
+ proc: T
3338
+
3339
+ returncode: int # noqa
3340
+
3341
+ stdout: ta.Optional[bytes] = None
3342
+ stderr: ta.Optional[bytes] = None
3343
+
3344
+
3345
+ ##
3346
+
3347
+
3348
+ @dc.dataclass(frozen=True)
3349
+ class SubprocessRun:
3350
+ cmd: ta.Sequence[str]
3351
+ input: ta.Any = None
3352
+ timeout: ta.Optional[TimeoutLike] = None
3353
+ check: bool = False
3354
+ capture_output: ta.Optional[bool] = None
3355
+ kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
3356
+
3357
+ #
3358
+
3359
+ _FIELD_NAMES: ta.ClassVar[ta.FrozenSet[str]]
3360
+
3361
+ def replace(self, **kwargs: ta.Any) -> 'SubprocessRun':
3362
+ if not kwargs:
3363
+ return self
3364
+
3365
+ field_kws = {}
3366
+ extra_kws = {}
3367
+ for k, v in kwargs.items():
3368
+ if k in self._FIELD_NAMES:
3369
+ field_kws[k] = v
3370
+ else:
3371
+ extra_kws[k] = v
3372
+
3373
+ return dc.replace(self, **{
3374
+ **dict(kwargs={
3375
+ **(self.kwargs or {}),
3376
+ **extra_kws,
3377
+ }),
3378
+ **field_kws, # passing a kwarg named 'kwargs' intentionally clobbers
3379
+ })
3380
+
3381
+ #
3382
+
3383
+ @classmethod
3384
+ def of(
3385
+ cls,
3386
+ *cmd: str,
3387
+ input: ta.Any = None, # noqa
3388
+ timeout: ta.Optional[TimeoutLike] = None,
3389
+ check: bool = False, # noqa
3390
+ capture_output: ta.Optional[bool] = None,
3391
+ **kwargs: ta.Any,
3392
+ ) -> 'SubprocessRun':
3393
+ return cls(
3394
+ cmd=cmd,
3395
+ input=input,
3396
+ timeout=timeout,
3397
+ check=check,
3398
+ capture_output=capture_output,
3399
+ kwargs=kwargs,
3400
+ )
3401
+
3402
+ #
3403
+
3404
+ _DEFAULT_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractSubprocesses
3405
+
3406
+ def run(
3407
+ self,
3408
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
3409
+ **kwargs: ta.Any,
3410
+ ) -> SubprocessRunOutput:
3411
+ if subprocesses is None:
3412
+ subprocesses = self._DEFAULT_SUBPROCESSES
3413
+ return check.not_none(subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
3414
+
3415
+ _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
3416
+
3417
+ async def async_run(
3418
+ self,
3419
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
3420
+ **kwargs: ta.Any,
3421
+ ) -> SubprocessRunOutput:
3422
+ if async_subprocesses is None:
3423
+ async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
3424
+ return await check.not_none(async_subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
3425
+
3426
+
3427
+ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessRun)) # noqa
3428
+
3429
+
3430
+ ##
3431
+
3432
+
3433
+ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
3434
+ @abc.abstractmethod
3435
+ def make_run(self) -> SubprocessRun:
3436
+ raise NotImplementedError
3437
+
3438
+ @abc.abstractmethod
3439
+ def handle_run_output(self, output: SubprocessRunOutput) -> T:
3440
+ raise NotImplementedError
3441
+
3442
+ #
3443
+
3444
+ def run(
3445
+ self,
3446
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
3447
+ **kwargs: ta.Any,
3448
+ ) -> T:
3449
+ return self.handle_run_output(self.make_run().run(subprocesses, **kwargs))
3450
+
3451
+ async def async_run(
3452
+ self,
3453
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
3454
+ **kwargs: ta.Any,
3455
+ ) -> T:
3456
+ return self.handle_run_output(await self.make_run().async_run(async_subprocesses, **kwargs))
3457
+
3458
+
3126
3459
  ########################################
3127
3460
  # ../cache.py
3128
3461
 
@@ -3989,23 +4322,7 @@ def configure_standard_logging(
3989
4322
 
3990
4323
 
3991
4324
  ########################################
3992
- # ../../../omlish/subprocesses.py
3993
-
3994
-
3995
- ##
3996
-
3997
-
3998
- # Valid channel type kwarg values:
3999
- # - A special flag negative int
4000
- # - A positive fd int
4001
- # - A file-like object
4002
- # - None
4003
-
4004
- SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
4005
- 'pipe': subprocess.PIPE,
4006
- 'stdout': subprocess.STDOUT,
4007
- 'devnull': subprocess.DEVNULL,
4008
- }
4325
+ # ../../../omlish/subprocesses/wrap.py
4009
4326
 
4010
4327
 
4011
4328
  ##
@@ -4025,29 +4342,150 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
4025
4342
  return cmd
4026
4343
 
4027
4344
 
4028
- ##
4029
-
4030
-
4031
- def subprocess_close(
4032
- proc: subprocess.Popen,
4033
- timeout: ta.Optional[float] = None,
4034
- ) -> None:
4035
- # TODO: terminate, sleep, kill
4036
- if proc.stdout:
4037
- proc.stdout.close()
4038
- if proc.stderr:
4039
- proc.stderr.close()
4040
- if proc.stdin:
4041
- proc.stdin.close()
4042
-
4043
- proc.wait(timeout)
4345
+ ########################################
4346
+ # ../github/cache.py
4044
4347
 
4045
4348
 
4046
4349
  ##
4047
4350
 
4048
4351
 
4049
- class VerboseCalledProcessError(subprocess.CalledProcessError):
4050
- @classmethod
4352
+ class GithubCache(FileCache, DataCache):
4353
+ @dc.dataclass(frozen=True)
4354
+ class Config:
4355
+ dir: str
4356
+
4357
+ def __init__(
4358
+ self,
4359
+ config: Config,
4360
+ *,
4361
+ client: ta.Optional[GithubCacheClient] = None,
4362
+ version: ta.Optional[CacheVersion] = None,
4363
+ ) -> None:
4364
+ super().__init__(
4365
+ version=version,
4366
+ )
4367
+
4368
+ self._config = config
4369
+
4370
+ if client is None:
4371
+ client = GithubCacheServiceV1Client(
4372
+ cache_version=self._version,
4373
+ )
4374
+ self._client: GithubCacheClient = client
4375
+
4376
+ self._local = DirectoryFileCache(
4377
+ DirectoryFileCache.Config(
4378
+ dir=check.non_empty_str(config.dir),
4379
+ ),
4380
+ version=self._version,
4381
+ )
4382
+
4383
+ #
4384
+
4385
+ async def get_file(self, key: str) -> ta.Optional[str]:
4386
+ local_file = self._local.get_cache_file_path(key)
4387
+ if os.path.exists(local_file):
4388
+ return local_file
4389
+
4390
+ if (entry := await self._client.get_entry(key)) is None:
4391
+ return None
4392
+
4393
+ tmp_file = self._local.format_incomplete_file(local_file)
4394
+ with unlinking_if_exists(tmp_file):
4395
+ await self._client.download_file(entry, tmp_file)
4396
+
4397
+ os.replace(tmp_file, local_file)
4398
+
4399
+ return local_file
4400
+
4401
+ async def put_file(
4402
+ self,
4403
+ key: str,
4404
+ file_path: str,
4405
+ *,
4406
+ steal: bool = False,
4407
+ ) -> str:
4408
+ cache_file_path = await self._local.put_file(
4409
+ key,
4410
+ file_path,
4411
+ steal=steal,
4412
+ )
4413
+
4414
+ await self._client.upload_file(key, cache_file_path)
4415
+
4416
+ return cache_file_path
4417
+
4418
+ #
4419
+
4420
+ async def get_data(self, key: str) -> ta.Optional[DataCache.Data]:
4421
+ local_file = self._local.get_cache_file_path(key)
4422
+ if os.path.exists(local_file):
4423
+ return DataCache.FileData(local_file)
4424
+
4425
+ if (entry := await self._client.get_entry(key)) is None:
4426
+ return None
4427
+
4428
+ return DataCache.UrlData(check.non_empty_str(self._client.get_entry_url(entry)))
4429
+
4430
+ async def put_data(self, key: str, data: DataCache.Data) -> None:
4431
+ await FileCacheDataCache(self).put_data(key, data)
4432
+
4433
+
4434
+ ########################################
4435
+ # ../github/cli.py
4436
+ """
4437
+ See:
4438
+ - https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28
4439
+ """
4440
+
4441
+
4442
+ class GithubCli(ArgparseCli):
4443
+ @argparse_cmd()
4444
+ def list_referenced_env_vars(self) -> None:
4445
+ print('\n'.join(sorted(ev.k for ev in GITHUB_ENV_VARS)))
4446
+
4447
+ @argparse_cmd(
4448
+ argparse_arg('key'),
4449
+ )
4450
+ async def get_cache_entry(self) -> None:
4451
+ client = GithubCacheServiceV1Client()
4452
+ entry = await client.get_entry(self.args.key)
4453
+ if entry is None:
4454
+ return
4455
+ print(json_dumps_pretty(dc.asdict(entry))) # noqa
4456
+
4457
+ @argparse_cmd(
4458
+ argparse_arg('repository-id'),
4459
+ )
4460
+ def list_cache_entries(self) -> None:
4461
+ raise NotImplementedError
4462
+
4463
+
4464
+ ########################################
4465
+ # ../../../omlish/subprocesses/base.py
4466
+
4467
+
4468
+ ##
4469
+
4470
+
4471
+ # Valid channel type kwarg values:
4472
+ # - A special flag negative int
4473
+ # - A positive fd int
4474
+ # - A file-like object
4475
+ # - None
4476
+
4477
+ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
4478
+ 'pipe': subprocess.PIPE,
4479
+ 'stdout': subprocess.STDOUT,
4480
+ 'devnull': subprocess.DEVNULL,
4481
+ }
4482
+
4483
+
4484
+ ##
4485
+
4486
+
4487
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
4488
+ @classmethod
4051
4489
  def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
4052
4490
  return cls(
4053
4491
  e.returncode,
@@ -4123,6 +4561,11 @@ class BaseSubprocesses(abc.ABC): # noqa
4123
4561
 
4124
4562
  #
4125
4563
 
4564
+ if 'timeout' in kwargs:
4565
+ kwargs['timeout'] = Timeout.of(kwargs['timeout']).or_(None)
4566
+
4567
+ #
4568
+
4126
4569
  return cmd, dict(
4127
4570
  env=env,
4128
4571
  shell=shell,
@@ -4227,62 +4670,52 @@ class BaseSubprocesses(abc.ABC): # noqa
4227
4670
  return e
4228
4671
 
4229
4672
 
4673
+ ########################################
4674
+ # ../github/inject.py
4675
+
4676
+
4230
4677
  ##
4231
4678
 
4232
4679
 
4233
- @dc.dataclass(frozen=True)
4234
- class SubprocessRun:
4235
- cmd: ta.Sequence[str]
4236
- input: ta.Any = None
4237
- timeout: ta.Optional[float] = None
4238
- check: bool = False
4239
- capture_output: ta.Optional[bool] = None
4240
- kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
4680
+ def bind_github(
4681
+ *,
4682
+ cache_dir: ta.Optional[str] = None,
4683
+ ) -> InjectorBindings:
4684
+ lst: ta.List[InjectorBindingOrBindings] = []
4241
4685
 
4242
- @classmethod
4243
- def of(
4244
- cls,
4245
- *cmd: str,
4246
- input: ta.Any = None, # noqa
4247
- timeout: ta.Optional[float] = None,
4248
- check: bool = False,
4249
- capture_output: ta.Optional[bool] = None,
4250
- **kwargs: ta.Any,
4251
- ) -> 'SubprocessRun':
4252
- return cls(
4253
- cmd=cmd,
4254
- input=input,
4255
- timeout=timeout,
4256
- check=check,
4257
- capture_output=capture_output,
4258
- kwargs=kwargs,
4259
- )
4686
+ if cache_dir is not None:
4687
+ lst.extend([
4688
+ inj.bind(GithubCache.Config(
4689
+ dir=cache_dir,
4690
+ )),
4691
+ inj.bind(GithubCache, singleton=True),
4692
+ inj.bind(FileCache, to_key=GithubCache),
4693
+ ])
4260
4694
 
4695
+ return inj.as_bindings(*lst)
4261
4696
 
4262
- @dc.dataclass(frozen=True)
4263
- class SubprocessRunOutput(ta.Generic[T]):
4264
- proc: T
4265
4697
 
4266
- returncode: int # noqa
4698
+ ########################################
4699
+ # ../../../omlish/subprocesses/async_.py
4267
4700
 
4268
- stdout: ta.Optional[bytes] = None
4269
- stderr: ta.Optional[bytes] = None
4270
4701
 
4702
+ ##
4271
4703
 
4272
- class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4704
+
4705
+ class AbstractAsyncSubprocesses(BaseSubprocesses):
4273
4706
  @abc.abstractmethod
4274
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4707
+ async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4275
4708
  raise NotImplementedError
4276
4709
 
4277
4710
  def run(
4278
4711
  self,
4279
4712
  *cmd: str,
4280
4713
  input: ta.Any = None, # noqa
4281
- timeout: ta.Optional[float] = None,
4714
+ timeout: ta.Optional[TimeoutLike] = None,
4282
4715
  check: bool = False,
4283
4716
  capture_output: ta.Optional[bool] = None,
4284
4717
  **kwargs: ta.Any,
4285
- ) -> SubprocessRunOutput:
4718
+ ) -> ta.Awaitable[SubprocessRunOutput]:
4286
4719
  return self.run_(SubprocessRun(
4287
4720
  cmd=cmd,
4288
4721
  input=input,
@@ -4295,7 +4728,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4295
4728
  #
4296
4729
 
4297
4730
  @abc.abstractmethod
4298
- def check_call(
4731
+ async def check_call(
4299
4732
  self,
4300
4733
  *cmd: str,
4301
4734
  stdout: ta.Any = sys.stderr,
@@ -4304,7 +4737,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4304
4737
  raise NotImplementedError
4305
4738
 
4306
4739
  @abc.abstractmethod
4307
- def check_output(
4740
+ async def check_output(
4308
4741
  self,
4309
4742
  *cmd: str,
4310
4743
  **kwargs: ta.Any,
@@ -4313,107 +4746,72 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4313
4746
 
4314
4747
  #
4315
4748
 
4316
- def check_output_str(
4749
+ async def check_output_str(
4317
4750
  self,
4318
4751
  *cmd: str,
4319
4752
  **kwargs: ta.Any,
4320
4753
  ) -> str:
4321
- return self.check_output(*cmd, **kwargs).decode().strip()
4754
+ return (await self.check_output(*cmd, **kwargs)).decode().strip()
4322
4755
 
4323
4756
  #
4324
4757
 
4325
- def try_call(
4758
+ async def try_call(
4326
4759
  self,
4327
4760
  *cmd: str,
4328
4761
  **kwargs: ta.Any,
4329
4762
  ) -> bool:
4330
- if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
4763
+ if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
4331
4764
  return False
4332
4765
  else:
4333
4766
  return True
4334
4767
 
4335
- def try_output(
4768
+ async def try_output(
4336
4769
  self,
4337
4770
  *cmd: str,
4338
4771
  **kwargs: ta.Any,
4339
4772
  ) -> ta.Optional[bytes]:
4340
- if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
4773
+ if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
4341
4774
  return None
4342
4775
  else:
4343
4776
  return ret
4344
4777
 
4345
- def try_output_str(
4778
+ async def try_output_str(
4346
4779
  self,
4347
4780
  *cmd: str,
4348
4781
  **kwargs: ta.Any,
4349
4782
  ) -> ta.Optional[str]:
4350
- if (ret := self.try_output(*cmd, **kwargs)) is None:
4783
+ if (ret := await self.try_output(*cmd, **kwargs)) is None:
4351
4784
  return None
4352
4785
  else:
4353
4786
  return ret.decode().strip()
4354
4787
 
4355
4788
 
4356
- ##
4357
-
4358
-
4359
- class Subprocesses(AbstractSubprocesses):
4360
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
4361
- proc = subprocess.run(
4362
- run.cmd,
4363
- input=run.input,
4364
- timeout=run.timeout,
4365
- check=run.check,
4366
- capture_output=run.capture_output or False,
4367
- **(run.kwargs or {}),
4368
- )
4369
-
4370
- return SubprocessRunOutput(
4371
- proc=proc,
4372
-
4373
- returncode=proc.returncode,
4374
-
4375
- stdout=proc.stdout, # noqa
4376
- stderr=proc.stderr, # noqa
4377
- )
4378
-
4379
- def check_call(
4380
- self,
4381
- *cmd: str,
4382
- stdout: ta.Any = sys.stderr,
4383
- **kwargs: ta.Any,
4384
- ) -> None:
4385
- with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
4386
- subprocess.check_call(cmd, **kwargs)
4387
-
4388
- def check_output(
4389
- self,
4390
- *cmd: str,
4391
- **kwargs: ta.Any,
4392
- ) -> bytes:
4393
- with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
4394
- return subprocess.check_output(cmd, **kwargs)
4395
-
4396
-
4397
- subprocesses = Subprocesses()
4789
+ ########################################
4790
+ # ../../../omlish/subprocesses/sync.py
4791
+ """
4792
+ TODO:
4793
+ - popen
4794
+ - route check_calls through run_?
4795
+ """
4398
4796
 
4399
4797
 
4400
4798
  ##
4401
4799
 
4402
4800
 
4403
- class AbstractAsyncSubprocesses(BaseSubprocesses):
4801
+ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4404
4802
  @abc.abstractmethod
4405
- async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4803
+ def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4406
4804
  raise NotImplementedError
4407
4805
 
4408
4806
  def run(
4409
4807
  self,
4410
4808
  *cmd: str,
4411
4809
  input: ta.Any = None, # noqa
4412
- timeout: ta.Optional[float] = None,
4810
+ timeout: ta.Optional[TimeoutLike] = None,
4413
4811
  check: bool = False,
4414
4812
  capture_output: ta.Optional[bool] = None,
4415
4813
  **kwargs: ta.Any,
4416
- ) -> ta.Awaitable[SubprocessRunOutput]:
4814
+ ) -> SubprocessRunOutput:
4417
4815
  return self.run_(SubprocessRun(
4418
4816
  cmd=cmd,
4419
4817
  input=input,
@@ -4426,7 +4824,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4426
4824
  #
4427
4825
 
4428
4826
  @abc.abstractmethod
4429
- async def check_call(
4827
+ def check_call(
4430
4828
  self,
4431
4829
  *cmd: str,
4432
4830
  stdout: ta.Any = sys.stderr,
@@ -4435,7 +4833,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4435
4833
  raise NotImplementedError
4436
4834
 
4437
4835
  @abc.abstractmethod
4438
- async def check_output(
4836
+ def check_output(
4439
4837
  self,
4440
4838
  *cmd: str,
4441
4839
  **kwargs: ta.Any,
@@ -4444,163 +4842,94 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4444
4842
 
4445
4843
  #
4446
4844
 
4447
- async def check_output_str(
4845
+ def check_output_str(
4448
4846
  self,
4449
4847
  *cmd: str,
4450
4848
  **kwargs: ta.Any,
4451
4849
  ) -> str:
4452
- return (await self.check_output(*cmd, **kwargs)).decode().strip()
4850
+ return self.check_output(*cmd, **kwargs).decode().strip()
4453
4851
 
4454
4852
  #
4455
4853
 
4456
- async def try_call(
4854
+ def try_call(
4457
4855
  self,
4458
4856
  *cmd: str,
4459
4857
  **kwargs: ta.Any,
4460
4858
  ) -> bool:
4461
- if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
4859
+ if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
4462
4860
  return False
4463
4861
  else:
4464
4862
  return True
4465
4863
 
4466
- async def try_output(
4864
+ def try_output(
4467
4865
  self,
4468
4866
  *cmd: str,
4469
4867
  **kwargs: ta.Any,
4470
4868
  ) -> ta.Optional[bytes]:
4471
- if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
4869
+ if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
4472
4870
  return None
4473
4871
  else:
4474
4872
  return ret
4475
4873
 
4476
- async def try_output_str(
4874
+ def try_output_str(
4477
4875
  self,
4478
4876
  *cmd: str,
4479
4877
  **kwargs: ta.Any,
4480
4878
  ) -> ta.Optional[str]:
4481
- if (ret := await self.try_output(*cmd, **kwargs)) is None:
4879
+ if (ret := self.try_output(*cmd, **kwargs)) is None:
4482
4880
  return None
4483
4881
  else:
4484
4882
  return ret.decode().strip()
4485
4883
 
4486
4884
 
4487
- ########################################
4488
- # ../github/cache.py
4489
-
4490
-
4491
4885
  ##
4492
4886
 
4493
4887
 
4494
- class GithubCache(FileCache, DataCache):
4495
- @dc.dataclass(frozen=True)
4496
- class Config:
4497
- dir: str
4498
-
4499
- def __init__(
4500
- self,
4501
- config: Config,
4502
- *,
4503
- client: ta.Optional[GithubCacheClient] = None,
4504
- version: ta.Optional[CacheVersion] = None,
4505
- ) -> None:
4506
- super().__init__(
4507
- version=version,
4508
- )
4888
+ class Subprocesses(AbstractSubprocesses):
4889
+ def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
4890
+ with self.prepare_and_wrap(
4891
+ *run.cmd,
4892
+ input=run.input,
4893
+ timeout=run.timeout,
4894
+ check=run.check,
4895
+ capture_output=run.capture_output or False,
4896
+ **(run.kwargs or {}),
4897
+ ) as (cmd, kwargs):
4898
+ proc = subprocess.run(cmd, **kwargs) # noqa
4509
4899
 
4510
- self._config = config
4900
+ return SubprocessRunOutput(
4901
+ proc=proc,
4511
4902
 
4512
- if client is None:
4513
- client = GithubCacheServiceV1Client(
4514
- cache_version=self._version,
4515
- )
4516
- self._client: GithubCacheClient = client
4903
+ returncode=proc.returncode,
4517
4904
 
4518
- self._local = DirectoryFileCache(
4519
- DirectoryFileCache.Config(
4520
- dir=check.non_empty_str(config.dir),
4521
- ),
4522
- version=self._version,
4905
+ stdout=proc.stdout, # noqa
4906
+ stderr=proc.stderr, # noqa
4523
4907
  )
4524
4908
 
4525
- #
4526
-
4527
- async def get_file(self, key: str) -> ta.Optional[str]:
4528
- local_file = self._local.get_cache_file_path(key)
4529
- if os.path.exists(local_file):
4530
- return local_file
4531
-
4532
- if (entry := await self._client.get_entry(key)) is None:
4533
- return None
4534
-
4535
- tmp_file = self._local.format_incomplete_file(local_file)
4536
- with unlinking_if_exists(tmp_file):
4537
- await self._client.download_file(entry, tmp_file)
4538
-
4539
- os.replace(tmp_file, local_file)
4540
-
4541
- return local_file
4542
-
4543
- async def put_file(
4909
+ def check_call(
4544
4910
  self,
4545
- key: str,
4546
- file_path: str,
4547
- *,
4548
- steal: bool = False,
4549
- ) -> str:
4550
- cache_file_path = await self._local.put_file(
4551
- key,
4552
- file_path,
4553
- steal=steal,
4554
- )
4555
-
4556
- await self._client.upload_file(key, cache_file_path)
4557
-
4558
- return cache_file_path
4559
-
4560
- #
4561
-
4562
- async def get_data(self, key: str) -> ta.Optional[DataCache.Data]:
4563
- local_file = self._local.get_cache_file_path(key)
4564
- if os.path.exists(local_file):
4565
- return DataCache.FileData(local_file)
4566
-
4567
- if (entry := await self._client.get_entry(key)) is None:
4568
- return None
4569
-
4570
- return DataCache.UrlData(check.non_empty_str(self._client.get_entry_url(entry)))
4571
-
4572
- async def put_data(self, key: str, data: DataCache.Data) -> None:
4573
- await FileCacheDataCache(self).put_data(key, data)
4911
+ *cmd: str,
4912
+ stdout: ta.Any = sys.stderr,
4913
+ **kwargs: ta.Any,
4914
+ ) -> None:
4915
+ with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
4916
+ subprocess.check_call(cmd, **kwargs)
4574
4917
 
4918
+ def check_output(
4919
+ self,
4920
+ *cmd: str,
4921
+ **kwargs: ta.Any,
4922
+ ) -> bytes:
4923
+ with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
4924
+ return subprocess.check_output(cmd, **kwargs)
4575
4925
 
4576
- ########################################
4577
- # ../github/cli.py
4578
- """
4579
- See:
4580
- - https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28
4581
- """
4582
4926
 
4927
+ ##
4583
4928
 
4584
- class GithubCli(ArgparseCli):
4585
- @argparse_cmd()
4586
- def list_referenced_env_vars(self) -> None:
4587
- print('\n'.join(sorted(ev.k for ev in GITHUB_ENV_VARS)))
4588
4929
 
4589
- @argparse_cmd(
4590
- argparse_arg('key'),
4591
- )
4592
- async def get_cache_entry(self) -> None:
4593
- client = GithubCacheServiceV1Client()
4594
- entry = await client.get_entry(self.args.key)
4595
- if entry is None:
4596
- return
4597
- print(json_dumps_pretty(dc.asdict(entry))) # noqa
4930
+ subprocesses = Subprocesses()
4598
4931
 
4599
- @argparse_cmd(
4600
- argparse_arg('repository-id'),
4601
- )
4602
- def list_cache_entries(self) -> None:
4603
- raise NotImplementedError
4932
+ SubprocessRun._DEFAULT_SUBPROCESSES = subprocesses # noqa
4604
4933
 
4605
4934
 
4606
4935
  ########################################
@@ -4789,7 +5118,7 @@ class AsyncioProcessCommunicator:
4789
5118
  async def communicate(
4790
5119
  self,
4791
5120
  input: ta.Any = None, # noqa
4792
- timeout: ta.Optional[float] = None,
5121
+ timeout: ta.Optional[TimeoutLike] = None,
4793
5122
  ) -> Communication:
4794
5123
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
4795
5124
 
@@ -4802,7 +5131,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4802
5131
  self,
4803
5132
  proc: asyncio.subprocess.Process,
4804
5133
  input: ta.Any = None, # noqa
4805
- timeout: ta.Optional[float] = None,
5134
+ timeout: ta.Optional[TimeoutLike] = None,
4806
5135
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
4807
5136
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
4808
5137
 
@@ -4813,22 +5142,22 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4813
5142
  self,
4814
5143
  *cmd: str,
4815
5144
  shell: bool = False,
4816
- timeout: ta.Optional[float] = None,
5145
+ timeout: ta.Optional[TimeoutLike] = None,
4817
5146
  **kwargs: ta.Any,
4818
5147
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
4819
- fac: ta.Any
4820
- if shell:
4821
- fac = functools.partial(
4822
- asyncio.create_subprocess_shell,
4823
- check.single(cmd),
4824
- )
4825
- else:
4826
- fac = functools.partial(
4827
- asyncio.create_subprocess_exec,
4828
- *cmd,
4829
- )
4830
-
4831
5148
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
5149
+ fac: ta.Any
5150
+ if shell:
5151
+ fac = functools.partial(
5152
+ asyncio.create_subprocess_shell,
5153
+ check.single(cmd),
5154
+ )
5155
+ else:
5156
+ fac = functools.partial(
5157
+ asyncio.create_subprocess_exec,
5158
+ *cmd,
5159
+ )
5160
+
4832
5161
  proc: asyncio.subprocess.Process = await fac(**kwargs)
4833
5162
  try:
4834
5163
  yield proc
@@ -5172,31 +5501,6 @@ async def load_docker_tar(
5172
5501
  return await load_docker_tar_cmd(ShellCmd(f'cat {shlex.quote(tar_file)}'))
5173
5502
 
5174
5503
 
5175
- ########################################
5176
- # ../github/inject.py
5177
-
5178
-
5179
- ##
5180
-
5181
-
5182
- def bind_github(
5183
- *,
5184
- cache_dir: ta.Optional[str] = None,
5185
- ) -> InjectorBindings:
5186
- lst: ta.List[InjectorBindingOrBindings] = []
5187
-
5188
- if cache_dir is not None:
5189
- lst.extend([
5190
- inj.bind(GithubCache.Config(
5191
- dir=cache_dir,
5192
- )),
5193
- inj.bind(GithubCache, singleton=True),
5194
- inj.bind(FileCache, to_key=GithubCache),
5195
- ])
5196
-
5197
- return inj.as_bindings(*lst)
5198
-
5199
-
5200
5504
  ########################################
5201
5505
  # ../docker/cache.py
5202
5506