ominfra 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev225__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,107 @@ 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
+ """
1957
+
1958
+
1959
+ ##
1949
1960
 
1950
1961
 
1951
1962
  class Pidfile:
1952
- def __init__(self, path: str) -> None:
1963
+ def __init__(
1964
+ self,
1965
+ path: str,
1966
+ *,
1967
+ inheritable: bool = True,
1968
+ ) -> None:
1953
1969
  super().__init__()
1954
- self._path = path
1955
1970
 
1956
- _f: ta.TextIO
1971
+ self._path = path
1972
+ self._inheritable = inheritable
1957
1973
 
1958
1974
  def __repr__(self) -> str:
1959
1975
  return f'{self.__class__.__name__}({self._path!r})'
1960
1976
 
1977
+ #
1978
+
1979
+ _f: ta.TextIO
1980
+
1981
+ def fileno(self) -> ta.Optional[int]:
1982
+ if hasattr(self, '_f'):
1983
+ return self._f.fileno()
1984
+ else:
1985
+ return None
1986
+
1987
+ #
1988
+
1961
1989
  def __enter__(self) -> 'Pidfile':
1962
1990
  fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
1991
+
1963
1992
  try:
1964
- os.set_inheritable(fd, True)
1993
+ if self._inheritable:
1994
+ os.set_inheritable(fd, True)
1995
+
1965
1996
  f = os.fdopen(fd, 'r+')
1997
+
1966
1998
  except Exception:
1967
1999
  try:
1968
2000
  os.close(fd)
1969
2001
  except Exception: # noqa
1970
2002
  pass
1971
2003
  raise
2004
+
1972
2005
  self._f = f
1973
2006
  return self
1974
2007
 
1975
2008
  def __exit__(self, exc_type, exc_val, exc_tb):
1976
- if hasattr(self, '_f'):
1977
- self._f.close()
1978
- del self._f
2009
+ self.close()
2010
+
2011
+ #
2012
+
2013
+ def __getstate__(self):
2014
+ state = self.__dict__.copy()
2015
+
2016
+ if '_f' in state:
2017
+ if os.get_inheritable(fd := state.pop('_f').fileno()):
2018
+ state['__fd'] = fd
2019
+
2020
+ return state
2021
+
2022
+ def __setstate__(self, state):
2023
+ if '_f' in state:
2024
+ raise RuntimeError
2025
+
2026
+ if '__fd' in state:
2027
+ state['_f'] = os.fdopen(state.pop('__fd'), 'r+')
2028
+
2029
+ self.__dict__.update(state)
2030
+
2031
+ #
2032
+
2033
+ def close(self) -> bool:
2034
+ if not hasattr(self, '_f'):
2035
+ return False
2036
+
2037
+ self._f.close()
2038
+ del self._f
2039
+ return True
1979
2040
 
1980
2041
  def try_lock(self) -> bool:
1981
2042
  try:
1982
2043
  fcntl.flock(self._f, fcntl.LOCK_EX | fcntl.LOCK_NB)
1983
2044
  return True
2045
+
1984
2046
  except OSError:
1985
2047
  return False
1986
2048
 
@@ -1988,27 +2050,57 @@ class Pidfile:
1988
2050
  if not self.try_lock():
1989
2051
  raise RuntimeError('Could not get lock')
1990
2052
 
2053
+ #
2054
+
1991
2055
  def write(self, pid: ta.Optional[int] = None) -> None:
1992
2056
  self.ensure_locked()
2057
+
1993
2058
  if pid is None:
1994
2059
  pid = os.getpid()
2060
+
2061
+ self._f.seek(0)
2062
+ self._f.truncate()
1995
2063
  self._f.write(f'{pid}\n')
1996
2064
  self._f.flush()
1997
2065
 
1998
2066
  def clear(self) -> None:
1999
2067
  self.ensure_locked()
2068
+
2000
2069
  self._f.seek(0)
2001
2070
  self._f.truncate()
2002
2071
 
2003
2072
  def read(self) -> int:
2004
2073
  if self.try_lock():
2005
2074
  raise RuntimeError('Got lock')
2075
+
2006
2076
  self._f.seek(0)
2007
- return int(self._f.read())
2077
+ return int(self._f.read()) # FIXME: could be empty or hold old value, race w proc start
2008
2078
 
2009
2079
  def kill(self, sig: int = signal.SIGTERM) -> None:
2010
2080
  pid = self.read()
2011
- os.kill(pid, sig) # FIXME: Still racy
2081
+ os.kill(pid, sig) # FIXME: Still racy - pidfd_send_signal?
2082
+
2083
+
2084
+ ########################################
2085
+ # ../../../../../omlish/subprocesses/utils.py
2086
+
2087
+
2088
+ ##
2089
+
2090
+
2091
+ def subprocess_close(
2092
+ proc: subprocess.Popen,
2093
+ timeout: ta.Optional[float] = None,
2094
+ ) -> None:
2095
+ # TODO: terminate, sleep, kill
2096
+ if proc.stdout:
2097
+ proc.stdout.close()
2098
+ if proc.stderr:
2099
+ proc.stderr.close()
2100
+ if proc.stdin:
2101
+ proc.stdin.close()
2102
+
2103
+ proc.wait(timeout)
2012
2104
 
2013
2105
 
2014
2106
  ########################################
@@ -4245,23 +4337,7 @@ def configure_standard_logging(
4245
4337
 
4246
4338
 
4247
4339
  ########################################
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
- }
4340
+ # ../../../../../omlish/subprocesses/wrap.py
4265
4341
 
4266
4342
 
4267
4343
  ##
@@ -4281,465 +4357,6 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
4281
4357
  return cmd
4282
4358
 
4283
4359
 
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
4360
  ########################################
4744
4361
  # ../poster.py
4745
4362
  """