omdev 0.0.0.dev225__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')
@@ -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
 
@@ -3147,17 +3349,43 @@ class SubprocessRunOutput(ta.Generic[T]):
3147
3349
  class SubprocessRun:
3148
3350
  cmd: ta.Sequence[str]
3149
3351
  input: ta.Any = None
3150
- timeout: ta.Optional[float] = None
3352
+ timeout: ta.Optional[TimeoutLike] = None
3151
3353
  check: bool = False
3152
3354
  capture_output: ta.Optional[bool] = None
3153
3355
  kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
3154
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
+
3155
3383
  @classmethod
3156
3384
  def of(
3157
3385
  cls,
3158
3386
  *cmd: str,
3159
3387
  input: ta.Any = None, # noqa
3160
- timeout: ta.Optional[float] = None,
3388
+ timeout: ta.Optional[TimeoutLike] = None,
3161
3389
  check: bool = False, # noqa
3162
3390
  capture_output: ta.Optional[bool] = None,
3163
3391
  **kwargs: ta.Any,
@@ -3178,20 +3406,25 @@ class SubprocessRun:
3178
3406
  def run(
3179
3407
  self,
3180
3408
  subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
3409
+ **kwargs: ta.Any,
3181
3410
  ) -> SubprocessRunOutput:
3182
3411
  if subprocesses is None:
3183
3412
  subprocesses = self._DEFAULT_SUBPROCESSES
3184
- return check.not_none(subprocesses).run_(self) # type: ignore[attr-defined]
3413
+ return check.not_none(subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
3185
3414
 
3186
3415
  _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
3187
3416
 
3188
3417
  async def async_run(
3189
3418
  self,
3190
3419
  async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
3420
+ **kwargs: ta.Any,
3191
3421
  ) -> SubprocessRunOutput:
3192
3422
  if async_subprocesses is None:
3193
3423
  async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
3194
- return await check.not_none(async_subprocesses).run_(self) # type: ignore[attr-defined]
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
3195
3428
 
3196
3429
 
3197
3430
  ##
@@ -3208,11 +3441,19 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
3208
3441
 
3209
3442
  #
3210
3443
 
3211
- def run(self, subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractSubprocesses
3212
- return self.handle_run_output(self.make_run().run(subprocesses))
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))
3213
3450
 
3214
- async def async_run(self, async_subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractAsyncSubprocesses
3215
- return self.handle_run_output(await self.make_run().async_run(async_subprocesses))
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))
3216
3457
 
3217
3458
 
3218
3459
  ########################################
@@ -4320,6 +4561,11 @@ class BaseSubprocesses(abc.ABC): # noqa
4320
4561
 
4321
4562
  #
4322
4563
 
4564
+ if 'timeout' in kwargs:
4565
+ kwargs['timeout'] = Timeout.of(kwargs['timeout']).or_(None)
4566
+
4567
+ #
4568
+
4323
4569
  return cmd, dict(
4324
4570
  env=env,
4325
4571
  shell=shell,
@@ -4465,7 +4711,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4465
4711
  self,
4466
4712
  *cmd: str,
4467
4713
  input: ta.Any = None, # noqa
4468
- timeout: ta.Optional[float] = None,
4714
+ timeout: ta.Optional[TimeoutLike] = None,
4469
4715
  check: bool = False,
4470
4716
  capture_output: ta.Optional[bool] = None,
4471
4717
  **kwargs: ta.Any,
@@ -4542,6 +4788,11 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4542
4788
 
4543
4789
  ########################################
4544
4790
  # ../../../omlish/subprocesses/sync.py
4791
+ """
4792
+ TODO:
4793
+ - popen
4794
+ - route check_calls through run_?
4795
+ """
4545
4796
 
4546
4797
 
4547
4798
  ##
@@ -4556,7 +4807,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4556
4807
  self,
4557
4808
  *cmd: str,
4558
4809
  input: ta.Any = None, # noqa
4559
- timeout: ta.Optional[float] = None,
4810
+ timeout: ta.Optional[TimeoutLike] = None,
4560
4811
  check: bool = False,
4561
4812
  capture_output: ta.Optional[bool] = None,
4562
4813
  **kwargs: ta.Any,
@@ -4867,7 +5118,7 @@ class AsyncioProcessCommunicator:
4867
5118
  async def communicate(
4868
5119
  self,
4869
5120
  input: ta.Any = None, # noqa
4870
- timeout: ta.Optional[float] = None,
5121
+ timeout: ta.Optional[TimeoutLike] = None,
4871
5122
  ) -> Communication:
4872
5123
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
4873
5124
 
@@ -4880,7 +5131,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4880
5131
  self,
4881
5132
  proc: asyncio.subprocess.Process,
4882
5133
  input: ta.Any = None, # noqa
4883
- timeout: ta.Optional[float] = None,
5134
+ timeout: ta.Optional[TimeoutLike] = None,
4884
5135
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
4885
5136
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
4886
5137
 
@@ -4891,7 +5142,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4891
5142
  self,
4892
5143
  *cmd: str,
4893
5144
  shell: bool = False,
4894
- timeout: ta.Optional[float] = None,
5145
+ timeout: ta.Optional[TimeoutLike] = None,
4895
5146
  **kwargs: ta.Any,
4896
5147
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
4897
5148
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
omdev/scripts/interp.py CHANGED
@@ -57,9 +57,6 @@ VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalTy
57
57
  VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
58
58
  VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
59
59
 
60
- # ../../omlish/asyncs/asyncio/timeouts.py
61
- AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
62
-
63
60
  # ../../omlish/lite/cached.py
64
61
  T = ta.TypeVar('T')
65
62
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
@@ -72,6 +69,9 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
72
69
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
73
70
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
74
71
 
72
+ # ../../omlish/lite/timeouts.py
73
+ TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
74
+
75
75
  # ../packaging/specifiers.py
76
76
  UnparsedVersion = ta.Union['Version', str]
77
77
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
@@ -80,6 +80,9 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
80
80
  # ../../omlish/argparse/cli.py
81
81
  ArgparseCmdFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
82
82
 
83
+ # ../../omlish/asyncs/asyncio/timeouts.py
84
+ AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
85
+
83
86
  # ../../omlish/lite/inject.py
84
87
  U = ta.TypeVar('U')
85
88
  InjectorKeyCls = ta.Union[type, ta.NewType]
@@ -498,19 +501,6 @@ def canonicalize_version(
498
501
  return ''.join(parts)
499
502
 
500
503
 
501
- ########################################
502
- # ../../../omlish/asyncs/asyncio/timeouts.py
503
-
504
-
505
- def asyncio_maybe_timeout(
506
- fut: AwaitableT,
507
- timeout: ta.Optional[float] = None,
508
- ) -> AwaitableT:
509
- if timeout is not None:
510
- fut = asyncio.wait_for(fut, timeout) # type: ignore
511
- return fut
512
-
513
-
514
504
  ########################################
515
505
  # ../../../omlish/lite/cached.py
516
506
 
@@ -1303,6 +1293,205 @@ def format_num_bytes(num_bytes: int) -> str:
1303
1293
  return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
1304
1294
 
1305
1295
 
1296
+ ########################################
1297
+ # ../../../omlish/lite/timeouts.py
1298
+ """
1299
+ TODO:
1300
+ - Event (/ Predicate)
1301
+ """
1302
+
1303
+
1304
+ ##
1305
+
1306
+
1307
+ class Timeout(abc.ABC):
1308
+ @property
1309
+ @abc.abstractmethod
1310
+ def can_expire(self) -> bool:
1311
+ """Indicates whether or not this timeout will ever expire."""
1312
+
1313
+ raise NotImplementedError
1314
+
1315
+ @abc.abstractmethod
1316
+ def expired(self) -> bool:
1317
+ """Return whether or not this timeout has expired."""
1318
+
1319
+ raise NotImplementedError
1320
+
1321
+ @abc.abstractmethod
1322
+ def remaining(self) -> float:
1323
+ """Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
1324
+
1325
+ raise NotImplementedError
1326
+
1327
+ @abc.abstractmethod
1328
+ def __call__(self) -> float:
1329
+ """Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
1330
+
1331
+ raise NotImplementedError
1332
+
1333
+ @abc.abstractmethod
1334
+ def or_(self, o: ta.Any) -> ta.Any:
1335
+ """Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
1336
+
1337
+ raise NotImplementedError
1338
+
1339
+ #
1340
+
1341
+ @classmethod
1342
+ def _now(cls) -> float:
1343
+ return time.time()
1344
+
1345
+ #
1346
+
1347
+ class Default:
1348
+ def __new__(cls, *args, **kwargs): # noqa
1349
+ raise TypeError
1350
+
1351
+ class _NOT_SPECIFIED: # noqa
1352
+ def __new__(cls, *args, **kwargs): # noqa
1353
+ raise TypeError
1354
+
1355
+ @classmethod
1356
+ def of(
1357
+ cls,
1358
+ obj: ta.Optional[TimeoutLike],
1359
+ default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
1360
+ ) -> 'Timeout':
1361
+ if obj is None:
1362
+ return InfiniteTimeout()
1363
+
1364
+ elif isinstance(obj, Timeout):
1365
+ return obj
1366
+
1367
+ elif isinstance(obj, (float, int)):
1368
+ return DeadlineTimeout(cls._now() + obj)
1369
+
1370
+ elif isinstance(obj, ta.Iterable):
1371
+ return CompositeTimeout(*[Timeout.of(c) for c in obj])
1372
+
1373
+ elif obj is Timeout.Default:
1374
+ if default is Timeout._NOT_SPECIFIED or default is Timeout.Default:
1375
+ raise RuntimeError('Must specify a default timeout')
1376
+
1377
+ else:
1378
+ return Timeout.of(default) # type: ignore[arg-type]
1379
+
1380
+ else:
1381
+ raise TypeError(obj)
1382
+
1383
+ @classmethod
1384
+ def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
1385
+ return DeadlineTimeout(deadline)
1386
+
1387
+ @classmethod
1388
+ def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
1389
+ return PredicateTimeout(expired_fn)
1390
+
1391
+
1392
+ class DeadlineTimeout(Timeout):
1393
+ def __init__(
1394
+ self,
1395
+ deadline: float,
1396
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1397
+ ) -> None:
1398
+ super().__init__()
1399
+
1400
+ self.deadline = deadline
1401
+ self.exc = exc
1402
+
1403
+ @property
1404
+ def can_expire(self) -> bool:
1405
+ return True
1406
+
1407
+ def expired(self) -> bool:
1408
+ return not (self.remaining() > 0)
1409
+
1410
+ def remaining(self) -> float:
1411
+ return self.deadline - self._now()
1412
+
1413
+ def __call__(self) -> float:
1414
+ if (rem := self.remaining()) > 0:
1415
+ return rem
1416
+ raise self.exc
1417
+
1418
+ def or_(self, o: ta.Any) -> ta.Any:
1419
+ return self()
1420
+
1421
+
1422
+ class InfiniteTimeout(Timeout):
1423
+ @property
1424
+ def can_expire(self) -> bool:
1425
+ return False
1426
+
1427
+ def expired(self) -> bool:
1428
+ return False
1429
+
1430
+ def remaining(self) -> float:
1431
+ return float('inf')
1432
+
1433
+ def __call__(self) -> float:
1434
+ return float('inf')
1435
+
1436
+ def or_(self, o: ta.Any) -> ta.Any:
1437
+ return o
1438
+
1439
+
1440
+ class CompositeTimeout(Timeout):
1441
+ def __init__(self, *children: Timeout) -> None:
1442
+ super().__init__()
1443
+
1444
+ self.children = children
1445
+
1446
+ @property
1447
+ def can_expire(self) -> bool:
1448
+ return any(c.can_expire for c in self.children)
1449
+
1450
+ def expired(self) -> bool:
1451
+ return any(c.expired() for c in self.children)
1452
+
1453
+ def remaining(self) -> float:
1454
+ return min(c.remaining() for c in self.children)
1455
+
1456
+ def __call__(self) -> float:
1457
+ return min(c() for c in self.children)
1458
+
1459
+ def or_(self, o: ta.Any) -> ta.Any:
1460
+ if self.can_expire:
1461
+ return self()
1462
+ return o
1463
+
1464
+
1465
+ class PredicateTimeout(Timeout):
1466
+ def __init__(
1467
+ self,
1468
+ expired_fn: ta.Callable[[], bool],
1469
+ exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
1470
+ ) -> None:
1471
+ super().__init__()
1472
+
1473
+ self.expired_fn = expired_fn
1474
+ self.exc = exc
1475
+
1476
+ @property
1477
+ def can_expire(self) -> bool:
1478
+ return True
1479
+
1480
+ def expired(self) -> bool:
1481
+ return self.expired_fn()
1482
+
1483
+ def remaining(self) -> float:
1484
+ return float('inf')
1485
+
1486
+ def __call__(self) -> float:
1487
+ if not self.expired_fn():
1488
+ return float('inf')
1489
+ raise self.exc
1490
+
1491
+ def or_(self, o: ta.Any) -> ta.Any:
1492
+ return self()
1493
+
1494
+
1306
1495
  ########################################
1307
1496
  # ../../../omlish/logs/filters.py
1308
1497
 
@@ -2224,6 +2413,19 @@ class ArgparseCli:
2224
2413
  return fn()
2225
2414
 
2226
2415
 
2416
+ ########################################
2417
+ # ../../../omlish/asyncs/asyncio/timeouts.py
2418
+
2419
+
2420
+ def asyncio_maybe_timeout(
2421
+ fut: AwaitableT,
2422
+ timeout: ta.Optional[TimeoutLike] = None,
2423
+ ) -> AwaitableT:
2424
+ if timeout is not None:
2425
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
2426
+ return fut
2427
+
2428
+
2227
2429
  ########################################
2228
2430
  # ../../../omlish/lite/inject.py
2229
2431
 
@@ -3393,17 +3595,43 @@ class SubprocessRunOutput(ta.Generic[T]):
3393
3595
  class SubprocessRun:
3394
3596
  cmd: ta.Sequence[str]
3395
3597
  input: ta.Any = None
3396
- timeout: ta.Optional[float] = None
3598
+ timeout: ta.Optional[TimeoutLike] = None
3397
3599
  check: bool = False
3398
3600
  capture_output: ta.Optional[bool] = None
3399
3601
  kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
3400
3602
 
3603
+ #
3604
+
3605
+ _FIELD_NAMES: ta.ClassVar[ta.FrozenSet[str]]
3606
+
3607
+ def replace(self, **kwargs: ta.Any) -> 'SubprocessRun':
3608
+ if not kwargs:
3609
+ return self
3610
+
3611
+ field_kws = {}
3612
+ extra_kws = {}
3613
+ for k, v in kwargs.items():
3614
+ if k in self._FIELD_NAMES:
3615
+ field_kws[k] = v
3616
+ else:
3617
+ extra_kws[k] = v
3618
+
3619
+ return dc.replace(self, **{
3620
+ **dict(kwargs={
3621
+ **(self.kwargs or {}),
3622
+ **extra_kws,
3623
+ }),
3624
+ **field_kws, # passing a kwarg named 'kwargs' intentionally clobbers
3625
+ })
3626
+
3627
+ #
3628
+
3401
3629
  @classmethod
3402
3630
  def of(
3403
3631
  cls,
3404
3632
  *cmd: str,
3405
3633
  input: ta.Any = None, # noqa
3406
- timeout: ta.Optional[float] = None,
3634
+ timeout: ta.Optional[TimeoutLike] = None,
3407
3635
  check: bool = False, # noqa
3408
3636
  capture_output: ta.Optional[bool] = None,
3409
3637
  **kwargs: ta.Any,
@@ -3424,20 +3652,25 @@ class SubprocessRun:
3424
3652
  def run(
3425
3653
  self,
3426
3654
  subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
3655
+ **kwargs: ta.Any,
3427
3656
  ) -> SubprocessRunOutput:
3428
3657
  if subprocesses is None:
3429
3658
  subprocesses = self._DEFAULT_SUBPROCESSES
3430
- return check.not_none(subprocesses).run_(self) # type: ignore[attr-defined]
3659
+ return check.not_none(subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
3431
3660
 
3432
3661
  _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
3433
3662
 
3434
3663
  async def async_run(
3435
3664
  self,
3436
3665
  async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
3666
+ **kwargs: ta.Any,
3437
3667
  ) -> SubprocessRunOutput:
3438
3668
  if async_subprocesses is None:
3439
3669
  async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
3440
- return await check.not_none(async_subprocesses).run_(self) # type: ignore[attr-defined]
3670
+ return await check.not_none(async_subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
3671
+
3672
+
3673
+ SubprocessRun._FIELD_NAMES = frozenset(fld.name for fld in dc.fields(SubprocessRun)) # noqa
3441
3674
 
3442
3675
 
3443
3676
  ##
@@ -3454,11 +3687,19 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
3454
3687
 
3455
3688
  #
3456
3689
 
3457
- def run(self, subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractSubprocesses
3458
- return self.handle_run_output(self.make_run().run(subprocesses))
3690
+ def run(
3691
+ self,
3692
+ subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
3693
+ **kwargs: ta.Any,
3694
+ ) -> T:
3695
+ return self.handle_run_output(self.make_run().run(subprocesses, **kwargs))
3459
3696
 
3460
- async def async_run(self, async_subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractAsyncSubprocesses
3461
- return self.handle_run_output(await self.make_run().async_run(async_subprocesses))
3697
+ async def async_run(
3698
+ self,
3699
+ async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
3700
+ **kwargs: ta.Any,
3701
+ ) -> T:
3702
+ return self.handle_run_output(await self.make_run().async_run(async_subprocesses, **kwargs))
3462
3703
 
3463
3704
 
3464
3705
  ########################################
@@ -3863,6 +4104,11 @@ class BaseSubprocesses(abc.ABC): # noqa
3863
4104
 
3864
4105
  #
3865
4106
 
4107
+ if 'timeout' in kwargs:
4108
+ kwargs['timeout'] = Timeout.of(kwargs['timeout']).or_(None)
4109
+
4110
+ #
4111
+
3866
4112
  return cmd, dict(
3867
4113
  env=env,
3868
4114
  shell=shell,
@@ -4071,7 +4317,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
4071
4317
  self,
4072
4318
  *cmd: str,
4073
4319
  input: ta.Any = None, # noqa
4074
- timeout: ta.Optional[float] = None,
4320
+ timeout: ta.Optional[TimeoutLike] = None,
4075
4321
  check: bool = False,
4076
4322
  capture_output: ta.Optional[bool] = None,
4077
4323
  **kwargs: ta.Any,
@@ -4261,7 +4507,7 @@ class AsyncioProcessCommunicator:
4261
4507
  async def communicate(
4262
4508
  self,
4263
4509
  input: ta.Any = None, # noqa
4264
- timeout: ta.Optional[float] = None,
4510
+ timeout: ta.Optional[TimeoutLike] = None,
4265
4511
  ) -> Communication:
4266
4512
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
4267
4513
 
@@ -4274,7 +4520,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4274
4520
  self,
4275
4521
  proc: asyncio.subprocess.Process,
4276
4522
  input: ta.Any = None, # noqa
4277
- timeout: ta.Optional[float] = None,
4523
+ timeout: ta.Optional[TimeoutLike] = None,
4278
4524
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
4279
4525
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
4280
4526
 
@@ -4285,7 +4531,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
4285
4531
  self,
4286
4532
  *cmd: str,
4287
4533
  shell: bool = False,
4288
- timeout: ta.Optional[float] = None,
4534
+ timeout: ta.Optional[TimeoutLike] = None,
4289
4535
  **kwargs: ta.Any,
4290
4536
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
4291
4537
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
@@ -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]
@@ -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
 
@@ -5649,17 +5851,43 @@ class SubprocessRunOutput(ta.Generic[T]):
5649
5851
  class SubprocessRun:
5650
5852
  cmd: ta.Sequence[str]
5651
5853
  input: ta.Any = None
5652
- timeout: ta.Optional[float] = None
5854
+ timeout: ta.Optional[TimeoutLike] = None
5653
5855
  check: bool = False
5654
5856
  capture_output: ta.Optional[bool] = None
5655
5857
  kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
5656
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
+
5657
5885
  @classmethod
5658
5886
  def of(
5659
5887
  cls,
5660
5888
  *cmd: str,
5661
5889
  input: ta.Any = None, # noqa
5662
- timeout: ta.Optional[float] = None,
5890
+ timeout: ta.Optional[TimeoutLike] = None,
5663
5891
  check: bool = False, # noqa
5664
5892
  capture_output: ta.Optional[bool] = None,
5665
5893
  **kwargs: ta.Any,
@@ -5680,20 +5908,25 @@ class SubprocessRun:
5680
5908
  def run(
5681
5909
  self,
5682
5910
  subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
5911
+ **kwargs: ta.Any,
5683
5912
  ) -> SubprocessRunOutput:
5684
5913
  if subprocesses is None:
5685
5914
  subprocesses = self._DEFAULT_SUBPROCESSES
5686
- return check.not_none(subprocesses).run_(self) # type: ignore[attr-defined]
5915
+ return check.not_none(subprocesses).run_(self.replace(**kwargs)) # type: ignore[attr-defined]
5687
5916
 
5688
5917
  _DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
5689
5918
 
5690
5919
  async def async_run(
5691
5920
  self,
5692
5921
  async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
5922
+ **kwargs: ta.Any,
5693
5923
  ) -> SubprocessRunOutput:
5694
5924
  if async_subprocesses is None:
5695
5925
  async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
5696
- return await check.not_none(async_subprocesses).run_(self) # type: ignore[attr-defined]
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
5697
5930
 
5698
5931
 
5699
5932
  ##
@@ -5710,11 +5943,19 @@ class SubprocessRunnable(abc.ABC, ta.Generic[T]):
5710
5943
 
5711
5944
  #
5712
5945
 
5713
- def run(self, subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractSubprocesses
5714
- return self.handle_run_output(self.make_run().run(subprocesses))
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))
5715
5952
 
5716
- async def async_run(self, async_subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractAsyncSubprocesses
5717
- return self.handle_run_output(await self.make_run().async_run(async_subprocesses))
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))
5718
5959
 
5719
5960
 
5720
5961
  ########################################
@@ -6119,6 +6360,11 @@ class BaseSubprocesses(abc.ABC): # noqa
6119
6360
 
6120
6361
  #
6121
6362
 
6363
+ if 'timeout' in kwargs:
6364
+ kwargs['timeout'] = Timeout.of(kwargs['timeout']).or_(None)
6365
+
6366
+ #
6367
+
6122
6368
  return cmd, dict(
6123
6369
  env=env,
6124
6370
  shell=shell,
@@ -6327,7 +6573,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
6327
6573
  self,
6328
6574
  *cmd: str,
6329
6575
  input: ta.Any = None, # noqa
6330
- timeout: ta.Optional[float] = None,
6576
+ timeout: ta.Optional[TimeoutLike] = None,
6331
6577
  check: bool = False,
6332
6578
  capture_output: ta.Optional[bool] = None,
6333
6579
  **kwargs: ta.Any,
@@ -6404,6 +6650,11 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
6404
6650
 
6405
6651
  ########################################
6406
6652
  # ../../../omlish/subprocesses/sync.py
6653
+ """
6654
+ TODO:
6655
+ - popen
6656
+ - route check_calls through run_?
6657
+ """
6407
6658
 
6408
6659
 
6409
6660
  ##
@@ -6418,7 +6669,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
6418
6669
  self,
6419
6670
  *cmd: str,
6420
6671
  input: ta.Any = None, # noqa
6421
- timeout: ta.Optional[float] = None,
6672
+ timeout: ta.Optional[TimeoutLike] = None,
6422
6673
  check: bool = False,
6423
6674
  capture_output: ta.Optional[bool] = None,
6424
6675
  **kwargs: ta.Any,
@@ -6704,7 +6955,7 @@ class AsyncioProcessCommunicator:
6704
6955
  async def communicate(
6705
6956
  self,
6706
6957
  input: ta.Any = None, # noqa
6707
- timeout: ta.Optional[float] = None,
6958
+ timeout: ta.Optional[TimeoutLike] = None,
6708
6959
  ) -> Communication:
6709
6960
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
6710
6961
 
@@ -6717,7 +6968,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
6717
6968
  self,
6718
6969
  proc: asyncio.subprocess.Process,
6719
6970
  input: ta.Any = None, # noqa
6720
- timeout: ta.Optional[float] = None,
6971
+ timeout: ta.Optional[TimeoutLike] = None,
6721
6972
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
6722
6973
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
6723
6974
 
@@ -6728,7 +6979,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
6728
6979
  self,
6729
6980
  *cmd: str,
6730
6981
  shell: bool = False,
6731
- timeout: ta.Optional[float] = None,
6982
+ timeout: ta.Optional[TimeoutLike] = None,
6732
6983
  **kwargs: ta.Any,
6733
6984
  ) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
6734
6985
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: omdev
3
- Version: 0.0.0.dev225
3
+ Version: 0.0.0.dev226
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev225
15
+ Requires-Dist: omlish==0.0.0.dev226
16
16
  Provides-Extra: all
17
17
  Requires-Dist: black~=24.10; extra == "all"
18
18
  Requires-Dist: pycparser~=2.22; extra == "all"
@@ -206,12 +206,12 @@ omdev/pyproject/resources/docker-dev.sh,sha256=DHkz5D18jok_oDolfg2mqrvGRWFoCe9GQ
206
206
  omdev/pyproject/resources/python.sh,sha256=rFaN4SiJ9hdLDXXsDTwugI6zsw6EPkgYMmtacZeTbvw,749
207
207
  omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
208
208
  omdev/scripts/bumpversion.py,sha256=Kn7fo73Hs8uJh3Hi3EIyLOlzLPWAC6dwuD_lZ3cIzuY,1064
209
- omdev/scripts/ci.py,sha256=8Ql1CIgySkpuc15O2FOxjxOtroQHgV7rJkWMHe4t2E0,153766
209
+ omdev/scripts/ci.py,sha256=IZYSToZ9UyNZKiyOnbrk7pfuJhlyamMLmkQX6nwYKP8,159949
210
210
  omdev/scripts/execrss.py,sha256=mR0G0wERBYtQmVIn63lCIIFb5zkCM6X_XOENDFYDBKc,651
211
211
  omdev/scripts/exectime.py,sha256=S2O4MgtzTsFOY2IUJxsrnOIame9tEFc6aOlKP-F1JSg,1541
212
212
  omdev/scripts/importtrace.py,sha256=oa7CtcWJVMNDbyIEiRHej6ICfABfErMeo4_haIqe18Q,14041
213
- omdev/scripts/interp.py,sha256=agxBP3gyOt5tWNybFOBUv-26gTHiIfxVxRM1izF_1eE,144286
214
- omdev/scripts/pyproject.py,sha256=TPqYRfB0Quw5lVPSirbB4tCrqfVi4dRL4nnNGSTrFPE,251895
213
+ omdev/scripts/interp.py,sha256=AYxF2dftxs1anS211-qWmJKzw_B_HIbZIyP6OC5eFdY,150405
214
+ omdev/scripts/pyproject.py,sha256=D8GgeTEOC8pKEwUX8whjOPuMGsYGRRKA8nIzXOAli_Y,258078
215
215
  omdev/scripts/slowcat.py,sha256=lssv4yrgJHiWfOiHkUut2p8E8Tq32zB-ujXESQxFFHY,2728
216
216
  omdev/scripts/tmpexec.py,sha256=WTYcf56Tj2qjYV14AWmV8SfT0u6Y8eIU6cKgQRvEK3c,1442
217
217
  omdev/tokens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -243,9 +243,9 @@ omdev/tools/json/rendering.py,sha256=tMcjOW5edfozcMSTxxvF7WVTsbYLoe9bCKFh50qyaGw
243
243
  omdev/tools/pawk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
244
244
  omdev/tools/pawk/__main__.py,sha256=VCqeRVnqT1RPEoIrqHFSu4PXVMg4YEgF4qCQm90-eRI,66
245
245
  omdev/tools/pawk/pawk.py,sha256=zsEkfQX0jF5bn712uqPAyBSdJt2dno1LH2oeSMNfXQI,11424
246
- omdev-0.0.0.dev225.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
247
- omdev-0.0.0.dev225.dist-info/METADATA,sha256=tvO-_klI5r4zKjd4fZzvqZtUdo_2S4nEXEwIjvI5QG4,1638
248
- omdev-0.0.0.dev225.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
249
- omdev-0.0.0.dev225.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
250
- omdev-0.0.0.dev225.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
251
- omdev-0.0.0.dev225.dist-info/RECORD,,
246
+ omdev-0.0.0.dev226.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
247
+ omdev-0.0.0.dev226.dist-info/METADATA,sha256=XK1jVJw9D5_tEULYgkiKcbQYJBjQJE0QVddRh7NsU4c,1638
248
+ omdev-0.0.0.dev226.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
249
+ omdev-0.0.0.dev226.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
250
+ omdev-0.0.0.dev226.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
251
+ omdev-0.0.0.dev226.dist-info/RECORD,,