zstdlib 0.8.1__tar.gz → 0.9.0__tar.gz

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.
Files changed (37) hide show
  1. {zstdlib-0.8.1 → zstdlib-0.9.0}/PKG-INFO +1 -1
  2. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/__init__.py +1 -1
  3. zstdlib-0.9.0/zstdlib/macos.py +252 -0
  4. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib.egg-info/PKG-INFO +1 -1
  5. zstdlib-0.8.1/zstdlib/macos.py +0 -198
  6. {zstdlib-0.8.1 → zstdlib-0.9.0}/LICENSE +0 -0
  7. {zstdlib-0.8.1 → zstdlib-0.9.0}/README.md +0 -0
  8. {zstdlib-0.8.1 → zstdlib-0.9.0}/pyproject.toml +0 -0
  9. {zstdlib-0.8.1 → zstdlib-0.9.0}/setup.cfg +0 -0
  10. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/__init__.py +0 -0
  11. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/log/__init__.py +0 -0
  12. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/log/base.py +0 -0
  13. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/log/test_cute.py +0 -0
  14. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/log/test_trace.py +0 -0
  15. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/test_ansi.py +0 -0
  16. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/test_enum.py +0 -0
  17. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/test_frozen.py +0 -0
  18. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/test_io.py +0 -0
  19. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/test_lazy.py +0 -0
  20. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/test_not_set.py +0 -0
  21. {zstdlib-0.8.1 → zstdlib-0.9.0}/tests/test_singleton.py +0 -0
  22. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/ansi.py +0 -0
  23. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/enum.py +0 -0
  24. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/frozen.py +0 -0
  25. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/io.py +0 -0
  26. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/lazy.py +0 -0
  27. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/log/__init__.py +0 -0
  28. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/log/cute.py +0 -0
  29. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/log/trace.py +0 -0
  30. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/not_set.py +0 -0
  31. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/py.typed +0 -0
  32. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/signal_cm.py +0 -0
  33. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib/singleton.py +0 -0
  34. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib.egg-info/SOURCES.txt +0 -0
  35. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib.egg-info/dependency_links.txt +0 -0
  36. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib.egg-info/requires.txt +0 -0
  37. {zstdlib-0.8.1 → zstdlib-0.9.0}/zstdlib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zstdlib
3
- Version: 0.8.1
3
+ Version: 0.9.0
4
4
  Summary: A set of useful python utilities
5
5
  License: GPLv3
6
6
  Project-URL: Homepage, https://github.com/zwimer/zstdlib
@@ -1,4 +1,4 @@
1
- __version__ = "0.8.1"
1
+ __version__ = "0.9.0"
2
2
 
3
3
  from .not_set import NotSetType, NotSet
4
4
  from .frozen import Freezable, frozen
@@ -0,0 +1,252 @@
1
+ from typing import NamedTuple, ParamSpec, ClassVar, TypeVar, Self
2
+ from collections.abc import Callable, Iterable
3
+ from subprocess import TimeoutExpired, Popen
4
+ from multiprocessing import Process
5
+ from contextlib import suppress
6
+ from logging import getLogger
7
+ from functools import wraps
8
+ from threading import Lock
9
+ from signal import Signals
10
+ from pathlib import Path
11
+ from os import getpid
12
+ import atexit
13
+
14
+ import osascript
15
+
16
+ __all__ = ("Caffeinate", "osascript")
17
+
18
+ CAFFEINATE = Path("/usr/bin/caffeinate") # This is a hard-coded location on macOS
19
+ KILL_WAIT_SECONDS: int = 60 # How long to .wait after .send_signal
20
+
21
+ P = ParamSpec("P")
22
+ R = TypeVar("R")
23
+
24
+
25
+ class Caffeinate:
26
+ """A small wrapper class around macOS' CLI tool Caffeinate"""
27
+
28
+ _finalizing: ClassVar[bool] = False
29
+ _instances_lock: ClassVar[Lock] = Lock()
30
+ _instances: ClassVar[set[Caffeinate]] = set() # Self makes mypy upset here; maybe b/c of subclassing? :/
31
+ _log = getLogger(f"{__name__}.Caffeinate")
32
+
33
+ __slots__ = ("_cmd", "_data", "_lock", "block", "signal")
34
+
35
+ class _Data(NamedTuple):
36
+ owner: int
37
+ proc: Popen
38
+
39
+ class SignalZeroError(ValueError):
40
+ """Raised when calling __enter__ or __exit__ on a Caffeinate instance with .signal == 0"""
41
+
42
+ def __init__(self) -> None:
43
+ msg = "The context manager is meant for managing the subprocess' lifetime, which is disable by .signal == 0, consider using .start() / .stop() instead."
44
+ super().__init__(msg)
45
+
46
+ # pylint: disable=too-many-arguments
47
+ def __init__(
48
+ self,
49
+ # Passed through to CLI tool
50
+ cmd: Iterable[Path | str | None] | None = None,
51
+ *,
52
+ display: bool = False,
53
+ idle: bool = False,
54
+ disk: bool = False,
55
+ system: bool = False,
56
+ user: bool = False,
57
+ timeout: int | None = None,
58
+ wait: int | Popen | Process | None = None,
59
+ # For Caeffinate class itself
60
+ signal: Signals | int = Signals.SIGTERM,
61
+ block: bool = False,
62
+ ) -> None:
63
+ """Invokes the macOS Caffeinate CLI tool to prevent sleeping.
64
+ Arguments are passed through to Caffeinate, descriptions are taken from the CLI tool; if they disagree with those found in `man caffeinate`, theirs are more likely to be correct.
65
+ No Caffeinate subprocess is started until .start() is called (or .__enter__ calls it)
66
+ All created children subprocesses will be cleaned up during atexit unless they have .signal == 0
67
+ After .stop()'d processes can .start() once more if desired
68
+ Warning: If cmd is passed, re-invoking __enter__ after an __exit__ will cause Caffeinate to run again and thus the command will run again
69
+ Warning: Trying to use a Caffeinate with signal == 0 will raise a SignalZeroError as it is likely undesired to try to manage the lifetime of a process excluded from lifetime management
70
+
71
+ Args:
72
+ cmd: An optional CLI command (and arguments) to run under the Caffeinate process
73
+ display: If True: Create an assertion to prevent the display from sleeping.
74
+ idle: If True: Create an assertion to prevent the system from idle sleeping.
75
+ disk: If True: Create an assertion to prevent the disk from idle sleeping.
76
+ system: If True: Create an assertion to prevent the system from sleeping. This assertion is valid only when system is running on AC power.
77
+ user: If True: Create an assertion to declare that user is active. If the display is off, this option turns the display on and prevents the display from going into idle sleep. If a timeout is not specified with '-t' option, then this assertion is taken with a default of 5 second timeout.
78
+ timeout: Specifies the timeout value in seconds for which this assertion has to be valid. The assertion is dropped after the specified timeout. Timeout value is not used by the Caeffinate CLI when cmd is passed. Note: this timeout is passed to the Caffeinate CLI command, it is not enforced by python
79
+ wait: Waits for the process with the specified pid to exit. Once the the process exits, the assertion is also released. This option is ignored by the Caeffinate CLI when cmd is passed.
80
+ This option conflicts with cmd.
81
+ signal: The signal used to kill the child process (if cmd likely the process Caffeinate execs into). If 0, do not kill it
82
+ block: If True: Block until cmd has finished executing before returning from __enter__
83
+ """
84
+ if not CAFFEINATE.exists():
85
+ raise NotImplementedError(f"This class is only available on macOS via: {CAFFEINATE}")
86
+ if (
87
+ isinstance(timeout, float)
88
+ or not isinstance(timeout, int | None)
89
+ or (timeout is not None and timeout < 1)
90
+ ):
91
+ raise TypeError("timeout must be a positive integer or None")
92
+ if not isinstance(wait, int | Popen | Process | None):
93
+ raise TypeError(f"wait is of the wrong type: {type(wait)}")
94
+ if not isinstance(signal, int | Signals):
95
+ msg = f"signal is of the wrong type: {type(signal)}{"; did you mean signal=0?" if signal is None else ""}"
96
+ raise TypeError(msg)
97
+ if isinstance(cmd, str | Path):
98
+ raise TypeError(f"cmd is of the wrong type: {type(cmd)}")
99
+ if cmd is None:
100
+ if block:
101
+ raise ValueError("block requires cmd")
102
+ cmd = []
103
+ else:
104
+ cmd = list(cmd)
105
+ if not cmd:
106
+ raise ValueError("Empty cmd iterable; did you mean to use cmd=None?")
107
+ self._lock = Lock()
108
+ self._data: Caffeinate._Data | None = None # Have to hardcode "Caffeinate" for mypy :/
109
+ wait = wait.pid if isinstance(wait, Popen | Process) else wait
110
+ flags = [
111
+ "-d" if display else None,
112
+ "-i" if idle else None,
113
+ "-m" if disk else None,
114
+ "-s" if system else None,
115
+ "-u" if user else None,
116
+ None if timeout is None else f"-t {timeout}",
117
+ None if wait is None else f"-w {wait}",
118
+ ]
119
+ self._cmd = list(map(str, [CAFFEINATE] + [k for i in flags if i for k in i.split(" ")] + cmd))
120
+ self.signal = signal
121
+ self.block = block
122
+
123
+ def start(self) -> None:
124
+ """Start Caffeinate-ing; nop if already started"""
125
+ with self._lock:
126
+ if self._data is not None:
127
+ return
128
+ pid = getpid()
129
+ with self._instances_lock:
130
+ if self._finalizing:
131
+ raise PythonFinalizationError("No more new Caeffinate subprocesses are allowed")
132
+ self._data = self._Data(pid, Popen(self._cmd)) # pylint: disable=consider-using-with
133
+ self._instances.add(self)
134
+ self._log.debug("Created Popen(%s), pid=%s", self._cmd, self._data.proc.pid)
135
+ if self.block:
136
+ self._data.proc.wait()
137
+
138
+ def stop(self) -> None:
139
+ """
140
+ Terminate the Caffeinate subprocess via self.signal; nop if not .stop()'d
141
+ .wait() will timeout after KILL_WAIT_SECONDS
142
+ """
143
+ self._stop(self.signal)
144
+
145
+ def __enter__(self) -> Self:
146
+ """Call .start(), if .signal == 0, raise a SignalZeroError, returns self"""
147
+ if not self.signal:
148
+ raise self.SignalZeroError()
149
+ self.start()
150
+ return self
151
+
152
+ def __exit__(self, *_: object) -> None:
153
+ """Calls .stop(); if called with .signal == 0, raise an SignalZeroError"""
154
+ signal = self.signal # So user can't change it on us from another thread!
155
+ if not signal: # Before atexit removal (so users change .signal later if desired)
156
+ raise self.SignalZeroError()
157
+ self._stop(signal)
158
+
159
+ @classmethod
160
+ def decorate(
161
+ cls, cmd: list[Path | str | None] | None = None, **init_kwargs: object
162
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
163
+ """A decorator to wrap a function with a Caffeinate instance
164
+ Example:
165
+ @decorate(idle=True)
166
+ def foo():
167
+ """
168
+ if cmd and callable(cmd[0]):
169
+ raise ValueError(f"Invalid usage of .decorate; see help({cls.__name__}.decorate)")
170
+
171
+ def outer(f: Callable[P, R]) -> Callable[P, R]:
172
+ @wraps(f)
173
+ def inner(*args: P.args, **kwargs: P.kwargs) -> R:
174
+ with cls(cmd, **init_kwargs): # type: ignore[arg-type]
175
+ return f(*args, **kwargs)
176
+
177
+ return inner
178
+
179
+ return outer
180
+
181
+ def __repr__(self) -> str:
182
+ pid = "NOT RUNNING" if self._data is None else self._data.proc.pid
183
+ return f"<Caffeinate: PID={pid}, flags={self._cmd[1:]}, signal={self.signal}, block={self.block}>"
184
+
185
+ # Rarely used methods
186
+
187
+ @classmethod
188
+ def kill_children(cls) -> None:
189
+ """
190
+ Clean up child Caffeinate subprocesses started by this class using their requested signals
191
+ If any child has .signal == 0, that process is not killed
192
+ """
193
+ with cls._instances_lock:
194
+ remove = list(cls._instances)
195
+ cls._log.debug("Caffeinate: Cleaning up child subprocesses (if any)")
196
+ for i in remove:
197
+ with suppress(cls.SignalZeroError):
198
+ i.__exit__() # modifies cls._instances
199
+
200
+ @classmethod
201
+ def forget_signal_zero_children(cls) -> None:
202
+ """
203
+ This method exists to allow long running processes that
204
+ endlessly spawns children with .signal == 0 to avoid memory leaks
205
+ Normally these instances are stored until atexit in case .signal changes before then
206
+ This methods forgets about all children with .signal == 0 (frees up the memory used storing them)
207
+ This cannot be undone; avoid callng this unless you need the memory
208
+ Warning: This does not remove any other references or destroy the instance
209
+ """
210
+ with cls._instances_lock:
211
+ cls._instances -= {i for i in cls._instances if not i.signal}
212
+
213
+ # Private methods
214
+
215
+ def _stop(self, signal: int | Signals) -> None:
216
+ """Workhose of .stop()"""
217
+ with self._lock:
218
+ if self._data is None:
219
+ return
220
+ # After this point, ensure no second removal attempt happens at atexit
221
+ with self._instances_lock:
222
+ self._instances.discard(self)
223
+ if getpid() != self._data.owner: # We don't care about these
224
+ return
225
+ if self._data.proc.poll() is None: # No need to kill it if it's already dead
226
+ self._log.debug("Cleaning up process %s with signal %s", self._data.proc.pid, signal)
227
+ self._data.proc.send_signal(signal)
228
+ try:
229
+ self._data.proc.wait(timeout=KILL_WAIT_SECONDS)
230
+ except TimeoutExpired:
231
+ # Technically, Caffeinate itself forks and its child remains Caffeinate while the
232
+ # parent becomes the cmd process, so say subprocess instead of Caffeinate processes
233
+ self._log.error(
234
+ "subprocess (PID=%s) didn't exit on .send_signal(%s)", self._data.proc.pid, signal
235
+ )
236
+ raise
237
+ self._data = None
238
+
239
+ @classmethod
240
+ def _atexit(cls) -> None:
241
+ """
242
+ Never invoke this manually; it is called by atexit
243
+ Disallows instance constructon then invokes kill_chilren()
244
+ """
245
+ with cls._instances_lock:
246
+ cls._finalizing = True
247
+ cls._log.debug("Disallowing future child Caffeinate subprocesses")
248
+ cls.kill_children()
249
+
250
+
251
+ assert 0 not in Signals, "0 not expected in signal.Signals as it's a nop"
252
+ atexit.register(Caffeinate._atexit) # pylint: disable=protected-access
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zstdlib
3
- Version: 0.8.1
3
+ Version: 0.9.0
4
4
  Summary: A set of useful python utilities
5
5
  License: GPLv3
6
6
  Project-URL: Homepage, https://github.com/zwimer/zstdlib
@@ -1,198 +0,0 @@
1
- from typing import NamedTuple, ParamSpec, ClassVar, TypeVar, Self
2
- from subprocess import TimeoutExpired, Popen
3
- from collections.abc import Callable
4
- from multiprocessing import Process
5
- from logging import getLogger
6
- from functools import wraps
7
- from threading import Lock
8
- from pathlib import Path
9
- from os import getpid
10
- import atexit
11
-
12
- import osascript
13
-
14
- CAFFEINATE = "/usr/bin/caffeinate" # This is a hard-coded location on macOS
15
- P = ParamSpec("P")
16
- R = TypeVar("R")
17
- _log = getLogger(__name__)
18
-
19
-
20
- __all__ = ("Caffeinate", "osascript")
21
-
22
-
23
- class Caffeinate:
24
- """A small wrapper class around macOS' CLI tool Caffeinate"""
25
-
26
- _armed_lock: ClassVar[Lock] = Lock()
27
- _armed: ClassVar[set[Self]] = set()
28
- __slots__ = ("_cmd", "_data", "_lock", "_wait")
29
-
30
- class _Data(NamedTuple):
31
- owner: int
32
- proc: Popen
33
-
34
- # pylint: disable=too-many-arguments
35
- def __init__(
36
- self,
37
- cmd: list[Path | str | None] | None = None,
38
- *,
39
- wait_for_cmd: bool = False,
40
- display: bool = False,
41
- idle: bool = False,
42
- disk: bool = False,
43
- system: bool = False,
44
- user: bool = False,
45
- timeout: int | None = None,
46
- wait: int | Popen | Process | None = None,
47
- ) -> None:
48
- """Invokes the macOS Caffeinate CLI tool to prevent sleeping.
49
- Arguments are passed through to Caffeinate, descriptions are taken from the CLI tool; if they disagree with those found in `man caffeinate`, theirs are more likely to be correct.
50
- Warning: If cmd is passed, re-invoking __enter__ after an __exit__ will cause Caffeinate to run again and thus the command will run again
51
-
52
- Args:
53
- cmd: An optional CLI command (and arguments) to run under the Caffeinate process
54
- wait_for_cmd: If True: Wait until cmd has finished executing before returning from __enter__
55
- display: If True: Create an assertion to prevent the display from sleeping.
56
- idle: If True: Create an assertion to prevent the system from idle sleeping.
57
- disk: If True: Create an assertion to prevent the disk from idle sleeping.
58
- system: If True: Create an assertion to prevent the system from sleeping. This assertion is valid only when system is running on AC power.
59
- user: If True: Create an assertion to declare that user is active. If the display is off, this option turns the display on and prevents the display from going into idle sleep. If a timeout is not specified with '-t' option, then this assertion is taken with a default of 5 second timeout.
60
- timeout: Specifies the timeout value in seconds for which this assertion has to be valid. The assertion is dropped after the specified timeout. This option conflicts with cmd. Note: this timeout is passed to the Caffeinate CLI command, it is not enforced by python
61
- with this command.
62
- wait: Waits for the process with the specified pid to exit. Once the the process exits, the assertion is also released. This option conflicts with cmd.
63
- """
64
- if cmd is None:
65
- if wait_for_cmd:
66
- raise ValueError("wait_for_cmd requires cmd not be None")
67
- cmd = []
68
- elif not cmd:
69
- raise ValueError("Empty cmd; did you mean to use None?")
70
- elif timeout is not None:
71
- raise ValueError("timeout may not be used with cmd")
72
- elif wait is not None:
73
- raise ValueError("wait may not be used with cmd")
74
- else:
75
- cmd = list(cmd)
76
- self._lock = Lock()
77
- self._data: Caffeinate._Data | None = None # Have to hardcode "Caffeinate" for mypy :/
78
- if isinstance(timeout, float):
79
- raise TypeError("Float timeout not supported")
80
- if timeout is not None and timeout < 1:
81
- raise ValueError("Timeout should be a positive integer")
82
- wait = wait.pid if isinstance(wait, Popen | Process) else wait
83
- flags = [
84
- "-d" if display else None,
85
- "-i" if idle else None,
86
- "-m" if disk else None,
87
- "-s" if system else None,
88
- "-u" if user else None,
89
- None if timeout is None else f"-t {timeout}",
90
- None if wait is None else f"-w {wait}",
91
- ]
92
- self._cmd = list(map(str, [CAFFEINATE] + [k for i in flags if i for k in i.split(" ")] + cmd))
93
- self._wait = wait_for_cmd
94
-
95
- def __enter__(self) -> Self:
96
- """Start Caffeinate-ing"""
97
- with self._lock:
98
- if self._data is not None:
99
- raise RuntimeError("This context manager has already been entered")
100
- self._data = self._Data(getpid(), Popen(self._cmd))
101
- _log.debug("Created Popen(%s), pid=%s", self._cmd, self._data.proc.pid)
102
- if self._wait:
103
- self._data.proc.wait()
104
- return self
105
-
106
- def __exit__(self, *_: object) -> None:
107
- with self._lock:
108
- if self._data is None:
109
- raise RuntimeError("Context managernever __enter__'d or already __exit__'d")
110
- if getpid() != self._data.owner:
111
- return
112
- _log.debug("Reaping Caffeinate process: %s", self._data.proc.pid)
113
- self._data.proc.terminate()
114
- try:
115
- self._data.proc.wait(timeout=1)
116
- except TimeoutExpired:
117
- _log.error("Caffeinate process didn't exit on .terminate()")
118
- self._data.proc.kill()
119
- self._data = None
120
-
121
- @classmethod
122
- def decorate(
123
- cls, cmd: list[Path | str | None] | None = None, **caffeinate_kwargs: object
124
- ) -> Callable[[Callable[P, R]], Callable[P, R]]:
125
- """A decorator to wrap a function with a Caffeinate instance
126
- Example:
127
- @decorate(idle=True)
128
- def foo():
129
- """
130
- if cmd and callable(cmd[0]):
131
- raise ValueError(f"Invalid usage of .decorate; see help({cls.__name__}.decorate)")
132
-
133
- def outer(f: Callable[P, R]) -> Callable[P, R]:
134
- @wraps(f)
135
- def inner(*args: P.args, **kwargs: P.kwargs) -> R:
136
- with cls(cmd, **caffeinate_kwargs): # type: ignore[arg-type]
137
- return f(*args, **kwargs)
138
-
139
- return inner
140
-
141
- return outer
142
-
143
- @classmethod
144
- def new(
145
- cls,
146
- cmd: list[Path | str | None] | None = None,
147
- *,
148
- survive_exit: bool = False,
149
- **kwargs: object,
150
- ) -> Self:
151
- """Return an __enter__'d Caffeinate, whose __exit__ will run atexit
152
- cmd and **kwargs are passed to __init__
153
-
154
- Args:
155
- cmd: Passed to Caffeinate
156
- survive_exit: Do not kill this Caffeinate instance when this process exits
157
- kwargs: Passed to Caffeinate
158
- """
159
- ret = cls(cmd, **kwargs) # type: ignore[arg-type]
160
- _ = ret.__enter__() # pylint: disable=unnecessary-dunder-call
161
- if not survive_exit:
162
- with cls._armed_lock:
163
- cls._armed.add(ret)
164
- return ret
165
-
166
- def survive_exit(self) -> None:
167
- """Disable atexit death; only permitted for instances created by .new()"""
168
- # pylint: disable=protected-access
169
- with self._armed_lock:
170
- if self not in self._armed:
171
- msg = f"This instance was not created by {self.__class__.__name__}.new() or already called .survive_exit()"
172
- raise ValueError(msg)
173
- self._armed.remove(self)
174
- assert self._data is not None
175
- _log.debug("Disabled cleanup of caffeinate subprocess PID=%s", self._data.proc.pid)
176
-
177
- @classmethod
178
- def _atexit(cls) -> None:
179
- """Clean up child Caffeinate subprocesses started by this class"""
180
- # pylint: disable=protected-access
181
- pid = getpid()
182
- _log.debug("Cleaning up all owned Caffeinate subprocesses started this process (PID=%s)", pid)
183
- with cls._armed_lock:
184
- for i in cls._armed:
185
- if i._data is None:
186
- _log.error("Unentered Caffeinate process in ._armed; this should never happen")
187
- continue
188
- if i._data.owner == pid:
189
- i.__exit__()
190
- cls._armed.clear()
191
-
192
- def __repr__(self) -> str:
193
- if self._data is None:
194
- return f"<Caffeinate: NOT RUNNING, flags={self._cmd[1:]}>"
195
- return f"<Caffeinate: PID={self._data.proc.pid}, flags={self._cmd[1:]}>"
196
-
197
-
198
- atexit.register(Caffeinate._atexit) # pylint: disable=protected-access
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes