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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,9 +86,6 @@ AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
86
86
  # ../../../threadworkers.py
87
87
  ThreadWorkerT = ta.TypeVar('ThreadWorkerT', bound='ThreadWorker')
88
88
 
89
- # ../../../../omlish/subprocesses.py
90
- SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
91
-
92
89
 
93
90
  ########################################
94
91
  # ../../../../../omlish/configs/types.py
@@ -1945,42 +1942,117 @@ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
1945
1942
 
1946
1943
 
1947
1944
  ########################################
1948
- # ../../../../../omlish/os/pidfile.py
1945
+ # ../../../../../omlish/os/pidfiles/pidfile.py
1946
+ """
1947
+ TODO:
1948
+ - reliable pid retrieval
1949
+ - contents are *ignored*, just advisory
1950
+ - check double-check:
1951
+ - 1) get pid of flock holder
1952
+ - 2) get pidfd to that
1953
+ - 3) recheck current pid of flock holder == that pid
1954
+ - racy as to if it's a different actual process as initial check, just with same pid, but due to 'identity' / semantic
1955
+ meaning of the named pidfile the processes are considered equivalent
1956
+ - read_checked(), contextmanager
1957
+ """
1958
+
1959
+
1960
+ ##
1949
1961
 
1950
1962
 
1951
1963
  class Pidfile:
1952
- def __init__(self, path: str) -> None:
1964
+ def __init__(
1965
+ self,
1966
+ path: str,
1967
+ *,
1968
+ inheritable: bool = True,
1969
+ ) -> None:
1953
1970
  super().__init__()
1971
+
1954
1972
  self._path = path
1973
+ self._inheritable = inheritable
1955
1974
 
1956
- _f: ta.TextIO
1975
+ @property
1976
+ def path(self) -> str:
1977
+ return self._path
1978
+
1979
+ @property
1980
+ def inheritable(self) -> bool:
1981
+ return self._inheritable
1957
1982
 
1958
1983
  def __repr__(self) -> str:
1959
1984
  return f'{self.__class__.__name__}({self._path!r})'
1960
1985
 
1986
+ #
1987
+
1988
+ _f: ta.TextIO
1989
+
1990
+ def fileno(self) -> ta.Optional[int]:
1991
+ if hasattr(self, '_f'):
1992
+ return self._f.fileno()
1993
+ else:
1994
+ return None
1995
+
1996
+ #
1997
+
1961
1998
  def __enter__(self) -> 'Pidfile':
1962
1999
  fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
2000
+
1963
2001
  try:
1964
- os.set_inheritable(fd, True)
2002
+ if self._inheritable:
2003
+ os.set_inheritable(fd, True)
2004
+
1965
2005
  f = os.fdopen(fd, 'r+')
2006
+
1966
2007
  except Exception:
1967
2008
  try:
1968
2009
  os.close(fd)
1969
2010
  except Exception: # noqa
1970
2011
  pass
1971
2012
  raise
2013
+
1972
2014
  self._f = f
1973
2015
  return self
1974
2016
 
1975
2017
  def __exit__(self, exc_type, exc_val, exc_tb):
1976
- if hasattr(self, '_f'):
1977
- self._f.close()
1978
- del self._f
2018
+ self.close()
2019
+
2020
+ #
2021
+
2022
+ def __getstate__(self):
2023
+ state = self.__dict__.copy()
2024
+
2025
+ if '_f' in state:
2026
+ # self._inheritable may be decoupled from actual file inheritability - for example when using the manager.
2027
+ if os.get_inheritable(fd := state.pop('_f').fileno()):
2028
+ state['__fd'] = fd
2029
+
2030
+ return state
2031
+
2032
+ def __setstate__(self, state):
2033
+ if '_f' in state:
2034
+ raise RuntimeError
2035
+
2036
+ if '__fd' in state:
2037
+ state['_f'] = os.fdopen(state.pop('__fd'), 'r+')
2038
+
2039
+ self.__dict__.update(state)
2040
+
2041
+ #
2042
+
2043
+ def close(self) -> bool:
2044
+ if not hasattr(self, '_f'):
2045
+ return False
2046
+
2047
+ self._f.close()
2048
+ del self._f
2049
+ return True
1979
2050
 
1980
2051
  def try_lock(self) -> bool:
1981
2052
  try:
1982
2053
  fcntl.flock(self._f, fcntl.LOCK_EX | fcntl.LOCK_NB)
1983
2054
  return True
2055
+
1984
2056
  except OSError:
1985
2057
  return False
1986
2058
 
@@ -1988,27 +2060,57 @@ class Pidfile:
1988
2060
  if not self.try_lock():
1989
2061
  raise RuntimeError('Could not get lock')
1990
2062
 
2063
+ #
2064
+
1991
2065
  def write(self, pid: ta.Optional[int] = None) -> None:
1992
2066
  self.ensure_locked()
2067
+
1993
2068
  if pid is None:
1994
2069
  pid = os.getpid()
2070
+
2071
+ self._f.seek(0)
2072
+ self._f.truncate()
1995
2073
  self._f.write(f'{pid}\n')
1996
2074
  self._f.flush()
1997
2075
 
1998
2076
  def clear(self) -> None:
1999
2077
  self.ensure_locked()
2078
+
2000
2079
  self._f.seek(0)
2001
2080
  self._f.truncate()
2002
2081
 
2003
2082
  def read(self) -> int:
2004
2083
  if self.try_lock():
2005
2084
  raise RuntimeError('Got lock')
2085
+
2006
2086
  self._f.seek(0)
2007
2087
  return int(self._f.read())
2008
2088
 
2009
2089
  def kill(self, sig: int = signal.SIGTERM) -> None:
2010
2090
  pid = self.read()
2011
- os.kill(pid, sig) # FIXME: Still racy
2091
+ os.kill(pid, sig)
2092
+
2093
+
2094
+ ########################################
2095
+ # ../../../../../omlish/subprocesses/utils.py
2096
+
2097
+
2098
+ ##
2099
+
2100
+
2101
+ def subprocess_close(
2102
+ proc: subprocess.Popen,
2103
+ timeout: ta.Optional[float] = None,
2104
+ ) -> None:
2105
+ # TODO: terminate, sleep, kill
2106
+ if proc.stdout:
2107
+ proc.stdout.close()
2108
+ if proc.stderr:
2109
+ proc.stderr.close()
2110
+ if proc.stdin:
2111
+ proc.stdin.close()
2112
+
2113
+ proc.wait(timeout)
2012
2114
 
2013
2115
 
2014
2116
  ########################################
@@ -4245,23 +4347,7 @@ def configure_standard_logging(
4245
4347
 
4246
4348
 
4247
4349
  ########################################
4248
- # ../../../../../omlish/subprocesses.py
4249
-
4250
-
4251
- ##
4252
-
4253
-
4254
- # Valid channel type kwarg values:
4255
- # - A special flag negative int
4256
- # - A positive fd int
4257
- # - A file-like object
4258
- # - None
4259
-
4260
- SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
4261
- 'pipe': subprocess.PIPE,
4262
- 'stdout': subprocess.STDOUT,
4263
- 'devnull': subprocess.DEVNULL,
4264
- }
4350
+ # ../../../../../omlish/subprocesses/wrap.py
4265
4351
 
4266
4352
 
4267
4353
  ##
@@ -4281,465 +4367,6 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
4281
4367
  return cmd
4282
4368
 
4283
4369
 
4284
- ##
4285
-
4286
-
4287
- def subprocess_close(
4288
- proc: subprocess.Popen,
4289
- timeout: ta.Optional[float] = None,
4290
- ) -> None:
4291
- # TODO: terminate, sleep, kill
4292
- if proc.stdout:
4293
- proc.stdout.close()
4294
- if proc.stderr:
4295
- proc.stderr.close()
4296
- if proc.stdin:
4297
- proc.stdin.close()
4298
-
4299
- proc.wait(timeout)
4300
-
4301
-
4302
- ##
4303
-
4304
-
4305
- class VerboseCalledProcessError(subprocess.CalledProcessError):
4306
- @classmethod
4307
- def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
4308
- return cls(
4309
- e.returncode,
4310
- e.cmd,
4311
- output=e.output,
4312
- stderr=e.stderr,
4313
- )
4314
-
4315
- def __str__(self) -> str:
4316
- msg = super().__str__()
4317
- if self.output is not None:
4318
- msg += f' Output: {self.output!r}'
4319
- if self.stderr is not None:
4320
- msg += f' Stderr: {self.stderr!r}'
4321
- return msg
4322
-
4323
-
4324
- class BaseSubprocesses(abc.ABC): # noqa
4325
- DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
4326
-
4327
- def __init__(
4328
- self,
4329
- *,
4330
- log: ta.Optional[logging.Logger] = None,
4331
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
4332
- ) -> None:
4333
- super().__init__()
4334
-
4335
- self._log = log if log is not None else self.DEFAULT_LOGGER
4336
- self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
4337
-
4338
- def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
4339
- self._log = log
4340
-
4341
- #
4342
-
4343
- def prepare_args(
4344
- self,
4345
- *cmd: str,
4346
- env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
4347
- extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
4348
- quiet: bool = False,
4349
- shell: bool = False,
4350
- **kwargs: ta.Any,
4351
- ) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
4352
- if self._log:
4353
- self._log.debug('Subprocesses.prepare_args: cmd=%r', cmd)
4354
- if extra_env:
4355
- self._log.debug('Subprocesses.prepare_args: extra_env=%r', extra_env)
4356
-
4357
- #
4358
-
4359
- if extra_env:
4360
- env = {**(env if env is not None else os.environ), **extra_env}
4361
-
4362
- #
4363
-
4364
- if quiet and 'stderr' not in kwargs:
4365
- if self._log and not self._log.isEnabledFor(logging.DEBUG):
4366
- kwargs['stderr'] = subprocess.DEVNULL
4367
-
4368
- for chk in ('stdout', 'stderr'):
4369
- try:
4370
- chv = kwargs[chk]
4371
- except KeyError:
4372
- continue
4373
- kwargs[chk] = SUBPROCESS_CHANNEL_OPTION_VALUES.get(chv, chv)
4374
-
4375
- #
4376
-
4377
- if not shell:
4378
- cmd = subprocess_maybe_shell_wrap_exec(*cmd)
4379
-
4380
- #
4381
-
4382
- return cmd, dict(
4383
- env=env,
4384
- shell=shell,
4385
- **kwargs,
4386
- )
4387
-
4388
- @contextlib.contextmanager
4389
- def wrap_call(
4390
- self,
4391
- *cmd: ta.Any,
4392
- raise_verbose: bool = False,
4393
- **kwargs: ta.Any,
4394
- ) -> ta.Iterator[None]:
4395
- start_time = time.time()
4396
- try:
4397
- if self._log:
4398
- self._log.debug('Subprocesses.wrap_call.try: cmd=%r', cmd)
4399
-
4400
- yield
4401
-
4402
- except Exception as exc: # noqa
4403
- if self._log:
4404
- self._log.debug('Subprocesses.wrap_call.except: exc=%r', exc)
4405
-
4406
- if (
4407
- raise_verbose and
4408
- isinstance(exc, subprocess.CalledProcessError) and
4409
- not isinstance(exc, VerboseCalledProcessError) and
4410
- (exc.output is not None or exc.stderr is not None)
4411
- ):
4412
- raise VerboseCalledProcessError.from_std(exc) from exc
4413
-
4414
- raise
4415
-
4416
- finally:
4417
- end_time = time.time()
4418
- elapsed_s = end_time - start_time
4419
-
4420
- if self._log:
4421
- self._log.debug('Subprocesses.wrap_call.finally: elapsed_s=%f cmd=%r', elapsed_s, cmd)
4422
-
4423
- @contextlib.contextmanager
4424
- def prepare_and_wrap(
4425
- self,
4426
- *cmd: ta.Any,
4427
- raise_verbose: bool = False,
4428
- **kwargs: ta.Any,
4429
- ) -> ta.Iterator[ta.Tuple[
4430
- ta.Tuple[ta.Any, ...],
4431
- ta.Dict[str, ta.Any],
4432
- ]]:
4433
- cmd, kwargs = self.prepare_args(*cmd, **kwargs)
4434
-
4435
- with self.wrap_call(
4436
- *cmd,
4437
- raise_verbose=raise_verbose,
4438
- **kwargs,
4439
- ):
4440
- yield cmd, kwargs
4441
-
4442
- #
4443
-
4444
- DEFAULT_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
4445
- FileNotFoundError,
4446
- subprocess.CalledProcessError,
4447
- )
4448
-
4449
- def try_fn(
4450
- self,
4451
- fn: ta.Callable[..., T],
4452
- *cmd: str,
4453
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
4454
- **kwargs: ta.Any,
4455
- ) -> ta.Union[T, Exception]:
4456
- if try_exceptions is None:
4457
- try_exceptions = self._try_exceptions
4458
-
4459
- try:
4460
- return fn(*cmd, **kwargs)
4461
-
4462
- except try_exceptions as e: # noqa
4463
- if self._log and self._log.isEnabledFor(logging.DEBUG):
4464
- self._log.exception('command failed')
4465
- return e
4466
-
4467
- async def async_try_fn(
4468
- self,
4469
- fn: ta.Callable[..., ta.Awaitable[T]],
4470
- *cmd: ta.Any,
4471
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
4472
- **kwargs: ta.Any,
4473
- ) -> ta.Union[T, Exception]:
4474
- if try_exceptions is None:
4475
- try_exceptions = self._try_exceptions
4476
-
4477
- try:
4478
- return await fn(*cmd, **kwargs)
4479
-
4480
- except try_exceptions as e: # noqa
4481
- if self._log and self._log.isEnabledFor(logging.DEBUG):
4482
- self._log.exception('command failed')
4483
- return e
4484
-
4485
-
4486
- ##
4487
-
4488
-
4489
- @dc.dataclass(frozen=True)
4490
- class SubprocessRun:
4491
- cmd: ta.Sequence[str]
4492
- input: ta.Any = None
4493
- timeout: ta.Optional[float] = None
4494
- check: bool = False
4495
- capture_output: ta.Optional[bool] = None
4496
- kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
4497
-
4498
- @classmethod
4499
- def of(
4500
- cls,
4501
- *cmd: str,
4502
- input: ta.Any = None, # noqa
4503
- timeout: ta.Optional[float] = None,
4504
- check: bool = False,
4505
- capture_output: ta.Optional[bool] = None,
4506
- **kwargs: ta.Any,
4507
- ) -> 'SubprocessRun':
4508
- return cls(
4509
- cmd=cmd,
4510
- input=input,
4511
- timeout=timeout,
4512
- check=check,
4513
- capture_output=capture_output,
4514
- kwargs=kwargs,
4515
- )
4516
-
4517
-
4518
- @dc.dataclass(frozen=True)
4519
- class SubprocessRunOutput(ta.Generic[T]):
4520
- proc: T
4521
-
4522
- returncode: int # noqa
4523
-
4524
- stdout: ta.Optional[bytes] = None
4525
- stderr: ta.Optional[bytes] = None
4526
-
4527
-
4528
- class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
4529
- @abc.abstractmethod
4530
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4531
- raise NotImplementedError
4532
-
4533
- def run(
4534
- self,
4535
- *cmd: str,
4536
- input: ta.Any = None, # noqa
4537
- timeout: ta.Optional[float] = None,
4538
- check: bool = False,
4539
- capture_output: ta.Optional[bool] = None,
4540
- **kwargs: ta.Any,
4541
- ) -> SubprocessRunOutput:
4542
- return self.run_(SubprocessRun(
4543
- cmd=cmd,
4544
- input=input,
4545
- timeout=timeout,
4546
- check=check,
4547
- capture_output=capture_output,
4548
- kwargs=kwargs,
4549
- ))
4550
-
4551
- #
4552
-
4553
- @abc.abstractmethod
4554
- def check_call(
4555
- self,
4556
- *cmd: str,
4557
- stdout: ta.Any = sys.stderr,
4558
- **kwargs: ta.Any,
4559
- ) -> None:
4560
- raise NotImplementedError
4561
-
4562
- @abc.abstractmethod
4563
- def check_output(
4564
- self,
4565
- *cmd: str,
4566
- **kwargs: ta.Any,
4567
- ) -> bytes:
4568
- raise NotImplementedError
4569
-
4570
- #
4571
-
4572
- def check_output_str(
4573
- self,
4574
- *cmd: str,
4575
- **kwargs: ta.Any,
4576
- ) -> str:
4577
- return self.check_output(*cmd, **kwargs).decode().strip()
4578
-
4579
- #
4580
-
4581
- def try_call(
4582
- self,
4583
- *cmd: str,
4584
- **kwargs: ta.Any,
4585
- ) -> bool:
4586
- if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
4587
- return False
4588
- else:
4589
- return True
4590
-
4591
- def try_output(
4592
- self,
4593
- *cmd: str,
4594
- **kwargs: ta.Any,
4595
- ) -> ta.Optional[bytes]:
4596
- if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
4597
- return None
4598
- else:
4599
- return ret
4600
-
4601
- def try_output_str(
4602
- self,
4603
- *cmd: str,
4604
- **kwargs: ta.Any,
4605
- ) -> ta.Optional[str]:
4606
- if (ret := self.try_output(*cmd, **kwargs)) is None:
4607
- return None
4608
- else:
4609
- return ret.decode().strip()
4610
-
4611
-
4612
- ##
4613
-
4614
-
4615
- class Subprocesses(AbstractSubprocesses):
4616
- def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
4617
- proc = subprocess.run(
4618
- run.cmd,
4619
- input=run.input,
4620
- timeout=run.timeout,
4621
- check=run.check,
4622
- capture_output=run.capture_output or False,
4623
- **(run.kwargs or {}),
4624
- )
4625
-
4626
- return SubprocessRunOutput(
4627
- proc=proc,
4628
-
4629
- returncode=proc.returncode,
4630
-
4631
- stdout=proc.stdout, # noqa
4632
- stderr=proc.stderr, # noqa
4633
- )
4634
-
4635
- def check_call(
4636
- self,
4637
- *cmd: str,
4638
- stdout: ta.Any = sys.stderr,
4639
- **kwargs: ta.Any,
4640
- ) -> None:
4641
- with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
4642
- subprocess.check_call(cmd, **kwargs)
4643
-
4644
- def check_output(
4645
- self,
4646
- *cmd: str,
4647
- **kwargs: ta.Any,
4648
- ) -> bytes:
4649
- with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
4650
- return subprocess.check_output(cmd, **kwargs)
4651
-
4652
-
4653
- subprocesses = Subprocesses()
4654
-
4655
-
4656
- ##
4657
-
4658
-
4659
- class AbstractAsyncSubprocesses(BaseSubprocesses):
4660
- @abc.abstractmethod
4661
- async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
4662
- raise NotImplementedError
4663
-
4664
- def run(
4665
- self,
4666
- *cmd: str,
4667
- input: ta.Any = None, # noqa
4668
- timeout: ta.Optional[float] = None,
4669
- check: bool = False,
4670
- capture_output: ta.Optional[bool] = None,
4671
- **kwargs: ta.Any,
4672
- ) -> ta.Awaitable[SubprocessRunOutput]:
4673
- return self.run_(SubprocessRun(
4674
- cmd=cmd,
4675
- input=input,
4676
- timeout=timeout,
4677
- check=check,
4678
- capture_output=capture_output,
4679
- kwargs=kwargs,
4680
- ))
4681
-
4682
- #
4683
-
4684
- @abc.abstractmethod
4685
- async def check_call(
4686
- self,
4687
- *cmd: str,
4688
- stdout: ta.Any = sys.stderr,
4689
- **kwargs: ta.Any,
4690
- ) -> None:
4691
- raise NotImplementedError
4692
-
4693
- @abc.abstractmethod
4694
- async def check_output(
4695
- self,
4696
- *cmd: str,
4697
- **kwargs: ta.Any,
4698
- ) -> bytes:
4699
- raise NotImplementedError
4700
-
4701
- #
4702
-
4703
- async def check_output_str(
4704
- self,
4705
- *cmd: str,
4706
- **kwargs: ta.Any,
4707
- ) -> str:
4708
- return (await self.check_output(*cmd, **kwargs)).decode().strip()
4709
-
4710
- #
4711
-
4712
- async def try_call(
4713
- self,
4714
- *cmd: str,
4715
- **kwargs: ta.Any,
4716
- ) -> bool:
4717
- if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
4718
- return False
4719
- else:
4720
- return True
4721
-
4722
- async def try_output(
4723
- self,
4724
- *cmd: str,
4725
- **kwargs: ta.Any,
4726
- ) -> ta.Optional[bytes]:
4727
- if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
4728
- return None
4729
- else:
4730
- return ret
4731
-
4732
- async def try_output_str(
4733
- self,
4734
- *cmd: str,
4735
- **kwargs: ta.Any,
4736
- ) -> ta.Optional[str]:
4737
- if (ret := await self.try_output(*cmd, **kwargs)) is None:
4738
- return None
4739
- else:
4740
- return ret.decode().strip()
4741
-
4742
-
4743
4370
  ########################################
4744
4371
  # ../poster.py
4745
4372
  """