omdev 0.0.0.dev225__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')
@@ -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,,