zstdlib 0.7.0__tar.gz → 0.8.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.
- {zstdlib-0.7.0 → zstdlib-0.8.0}/PKG-INFO +2 -1
- {zstdlib-0.7.0 → zstdlib-0.8.0}/pyproject.toml +5 -2
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/log/base.py +2 -1
- zstdlib-0.8.0/tests/test_lazy.py +25 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/__init__.py +4 -2
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/ansi.py +20 -13
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/enum.py +2 -4
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/io.py +1 -1
- zstdlib-0.8.0/zstdlib/lazy.py +31 -0
- zstdlib-0.8.0/zstdlib/macos.py +198 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib.egg-info/PKG-INFO +2 -1
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib.egg-info/SOURCES.txt +4 -0
- zstdlib-0.8.0/zstdlib.egg-info/requires.txt +1 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/LICENSE +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/README.md +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/setup.cfg +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/__init__.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/log/__init__.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/log/test_cute.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/log/test_trace.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/test_ansi.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/test_enum.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/test_frozen.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/test_io.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/test_not_set.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/tests/test_singleton.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/frozen.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/log/__init__.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/log/cute.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/log/trace.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/not_set.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/py.typed +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/signal_cm.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib/singleton.py +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.0}/zstdlib.egg-info/dependency_links.txt +0 -0
- {zstdlib-0.7.0 → zstdlib-0.8.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.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A set of useful python utilities
|
|
5
5
|
License: GPLv3
|
|
6
6
|
Project-URL: Homepage, https://github.com/zwimer/zstdlib
|
|
@@ -11,6 +11,7 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
|
11
11
|
Requires-Python: >=3.14
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: osascript2
|
|
14
15
|
Dynamic: license-file
|
|
15
16
|
|
|
16
17
|
# zstdlib
|
|
@@ -15,6 +15,9 @@ description = "A set of useful python utilities"
|
|
|
15
15
|
urls = {Homepage = "https://github.com/zwimer/zstdlib"}
|
|
16
16
|
requires-python = ">= 3.14"
|
|
17
17
|
dynamic = ["version"]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"osascript2" # For macOS
|
|
20
|
+
]
|
|
18
21
|
|
|
19
22
|
[project.readme]
|
|
20
23
|
file = "README.md"
|
|
@@ -60,7 +63,7 @@ ignore=[
|
|
|
60
63
|
"E731",
|
|
61
64
|
"I001",
|
|
62
65
|
"PLR0913", "PLR2004",
|
|
63
|
-
"S101",
|
|
66
|
+
"S101", "S603",
|
|
64
67
|
"TID252",
|
|
65
68
|
]
|
|
66
69
|
extend-select = [
|
|
@@ -87,7 +90,7 @@ convention = "google"
|
|
|
87
90
|
"tests/*" = ["DTZ005", "PGH003", "PLW0108"]
|
|
88
91
|
|
|
89
92
|
[tool.bandit]
|
|
90
|
-
skips = ["B101", "B104", "B201"]
|
|
93
|
+
skips = ["B101", "B104", "B201", "B404", "B603"]
|
|
91
94
|
|
|
92
95
|
[tool.vulture]
|
|
93
96
|
ignore_names = []
|
|
@@ -29,7 +29,8 @@ class LeftBase:
|
|
|
29
29
|
def hijack(
|
|
30
30
|
cls, name: str, reuse: bool = False, fmt: logging.Formatter | None = None
|
|
31
31
|
) -> AbstractContextManager[logging.Logger]:
|
|
32
|
-
"""
|
|
32
|
+
"""Create a hijacked logger
|
|
33
|
+
|
|
33
34
|
Returns:
|
|
34
35
|
A logger configured to write to an internal queue
|
|
35
36
|
"""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from zstdlib.lazy import Str
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _append(buf: list[str], *, obj: str) -> str:
|
|
8
|
+
buf.append(obj)
|
|
9
|
+
return str(len(buf))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestLazy(unittest.TestCase):
|
|
13
|
+
|
|
14
|
+
def test_str(self) -> None:
|
|
15
|
+
buf: list[str] = []
|
|
16
|
+
obj = "apples"
|
|
17
|
+
x = Str(_append, buf, obj=obj)
|
|
18
|
+
self.assertEqual(buf, [])
|
|
19
|
+
one = str(x) # Invoke Lazy Str
|
|
20
|
+
self.assertEqual(one, "1")
|
|
21
|
+
self.assertEqual(buf, [obj])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
unittest.main()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.8.0"
|
|
2
2
|
|
|
3
3
|
from .not_set import NotSetType, NotSet
|
|
4
4
|
from .frozen import Freezable, frozen
|
|
5
5
|
from .singleton import Singleton
|
|
6
6
|
from .signal_cm import signal_cm
|
|
7
7
|
from .enum import Enum, auto
|
|
8
|
-
from . import log, io
|
|
8
|
+
from . import macos, lazy, log, io
|
|
9
9
|
|
|
10
10
|
__all__ = (
|
|
11
11
|
"Enum",
|
|
@@ -16,6 +16,8 @@ __all__ = (
|
|
|
16
16
|
"auto",
|
|
17
17
|
"frozen",
|
|
18
18
|
"io",
|
|
19
|
+
"lazy",
|
|
19
20
|
"log",
|
|
21
|
+
"macos",
|
|
20
22
|
"signal_cm",
|
|
21
23
|
)
|
|
@@ -117,14 +117,18 @@ class Color(metaclass=_ColorMeta):
|
|
|
117
117
|
self.code = RawColor._parse_color(fmt) if isinstance(fmt, str) else fmt.code
|
|
118
118
|
|
|
119
119
|
def __call__(self, string: str) -> str:
|
|
120
|
-
"""
|
|
120
|
+
"""Args:
|
|
121
|
+
string: The string to color
|
|
122
|
+
|
|
121
123
|
Returns:
|
|
122
124
|
The string color codes with the given ansi code
|
|
123
125
|
"""
|
|
124
126
|
return f"{self.code}{string}{self.RESET}"
|
|
125
127
|
|
|
126
128
|
def __add__(self, other: Self) -> Self:
|
|
127
|
-
"""
|
|
129
|
+
"""Args:
|
|
130
|
+
other: The other string to color
|
|
131
|
+
|
|
128
132
|
Returns:
|
|
129
133
|
A new color made by concatenating these two
|
|
130
134
|
"""
|
|
@@ -133,31 +137,30 @@ class Color(metaclass=_ColorMeta):
|
|
|
133
137
|
return type(self)(code=f"{self.code[:-1]};{other.code[len(_PREFIX):]}")
|
|
134
138
|
|
|
135
139
|
def __eq__(self, other: object) -> bool:
|
|
136
|
-
"""
|
|
140
|
+
"""Args:
|
|
141
|
+
other: The color to compare against
|
|
142
|
+
|
|
137
143
|
Returns:
|
|
138
144
|
Whether the two colors are equal
|
|
139
145
|
"""
|
|
140
146
|
return isinstance(other, type(self)) and self.code == other.code
|
|
141
147
|
|
|
142
148
|
def __hash__(self) -> int:
|
|
143
|
-
"""
|
|
144
|
-
Returns:
|
|
145
|
-
A hash of the current color
|
|
146
|
-
"""
|
|
147
149
|
return hash((type(self), self.code))
|
|
148
150
|
|
|
149
151
|
def __repr__(self) -> str:
|
|
150
|
-
"""
|
|
151
|
-
Returns:
|
|
152
|
-
The ansi color code this object represents
|
|
153
|
-
"""
|
|
152
|
+
"""Get the ansi color code this object represents"""
|
|
154
153
|
return f"<Color {self.code!r}>"
|
|
155
154
|
|
|
156
155
|
@classmethod
|
|
157
156
|
def factory(
|
|
158
157
|
cls, *, foreground: RawColor | None = None, background: RawColor | None = None, **modifiers: bool
|
|
159
158
|
) -> Self:
|
|
160
|
-
"""
|
|
159
|
+
"""Args:
|
|
160
|
+
foreground: The foreground color
|
|
161
|
+
background: The background color
|
|
162
|
+
**modifiers: Color modifiers to apply
|
|
163
|
+
|
|
161
164
|
Returns:
|
|
162
165
|
The Ansi given the chosen color and various modifiers
|
|
163
166
|
"""
|
|
@@ -167,7 +170,11 @@ class Color(metaclass=_ColorMeta):
|
|
|
167
170
|
def _generate_code(
|
|
168
171
|
foreground: RawColor | None, background: RawColor | None, modifiers: dict[str, bool]
|
|
169
172
|
) -> str:
|
|
170
|
-
"""
|
|
173
|
+
"""Args:
|
|
174
|
+
foreground: The foreground color
|
|
175
|
+
background: The background color
|
|
176
|
+
**modifiers: Color modifiers to apply
|
|
177
|
+
|
|
171
178
|
Returns:
|
|
172
179
|
The Ansi given the chosen color and various modifiers
|
|
173
180
|
"""
|
|
@@ -136,8 +136,7 @@ class Enum(metaclass=EnumType, empty_ok=True):
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
def entries(enum: EnumType) -> dict[str, tuple[type | None, Any]]:
|
|
139
|
-
"""
|
|
140
|
-
Args:
|
|
139
|
+
"""Args:
|
|
141
140
|
enum: A class whose metaclass is EnumType
|
|
142
141
|
|
|
143
142
|
Returns:
|
|
@@ -150,8 +149,7 @@ def entries(enum: EnumType) -> dict[str, tuple[type | None, Any]]:
|
|
|
150
149
|
|
|
151
150
|
|
|
152
151
|
def values(enum: Any) -> tuple[Any, ...]:
|
|
153
|
-
"""
|
|
154
|
-
Args:
|
|
152
|
+
"""Args:
|
|
155
153
|
enum: A class whose metaclass is EnumType
|
|
156
154
|
|
|
157
155
|
Returns:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import ParamSpec
|
|
3
|
+
from threading import Lock
|
|
4
|
+
|
|
5
|
+
P = ParamSpec("P")
|
|
6
|
+
|
|
7
|
+
__all__ = ("Str",)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Str:
|
|
11
|
+
"""A lazily evaluated string class"""
|
|
12
|
+
|
|
13
|
+
__slots__ = ("_args", "_fn", "_kwargs", "_lock", "_result")
|
|
14
|
+
|
|
15
|
+
def __init__(self, fn: Callable[P, str], *args: P.args, **kwargs: P.kwargs) -> None:
|
|
16
|
+
"""Store a function and its arguments, on str(self) return fn(*args, **kwargs)
|
|
17
|
+
The result of the function invocation is cached and will be reused
|
|
18
|
+
"""
|
|
19
|
+
self._result: str | None = None
|
|
20
|
+
self._lock = Lock()
|
|
21
|
+
self._fn = fn
|
|
22
|
+
self._args = args
|
|
23
|
+
self._kwargs = kwargs
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
"""Evaluate the saved function"""
|
|
27
|
+
if self._result is not None:
|
|
28
|
+
return self._result
|
|
29
|
+
with self._lock:
|
|
30
|
+
self._result = self._fn(*self._args, **self._kwargs)
|
|
31
|
+
return self._result
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zstdlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: A set of useful python utilities
|
|
5
5
|
License: GPLv3
|
|
6
6
|
Project-URL: Homepage, https://github.com/zwimer/zstdlib
|
|
@@ -11,6 +11,7 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
|
11
11
|
Requires-Python: >=3.14
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: osascript2
|
|
14
15
|
Dynamic: license-file
|
|
15
16
|
|
|
16
17
|
# zstdlib
|
|
@@ -6,6 +6,7 @@ tests/test_ansi.py
|
|
|
6
6
|
tests/test_enum.py
|
|
7
7
|
tests/test_frozen.py
|
|
8
8
|
tests/test_io.py
|
|
9
|
+
tests/test_lazy.py
|
|
9
10
|
tests/test_not_set.py
|
|
10
11
|
tests/test_singleton.py
|
|
11
12
|
tests/log/__init__.py
|
|
@@ -17,6 +18,8 @@ zstdlib/ansi.py
|
|
|
17
18
|
zstdlib/enum.py
|
|
18
19
|
zstdlib/frozen.py
|
|
19
20
|
zstdlib/io.py
|
|
21
|
+
zstdlib/lazy.py
|
|
22
|
+
zstdlib/macos.py
|
|
20
23
|
zstdlib/not_set.py
|
|
21
24
|
zstdlib/py.typed
|
|
22
25
|
zstdlib/signal_cm.py
|
|
@@ -24,6 +27,7 @@ zstdlib/singleton.py
|
|
|
24
27
|
zstdlib.egg-info/PKG-INFO
|
|
25
28
|
zstdlib.egg-info/SOURCES.txt
|
|
26
29
|
zstdlib.egg-info/dependency_links.txt
|
|
30
|
+
zstdlib.egg-info/requires.txt
|
|
27
31
|
zstdlib.egg-info/top_level.txt
|
|
28
32
|
zstdlib/log/__init__.py
|
|
29
33
|
zstdlib/log/cute.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
osascript2
|
|
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
|