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

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