dycw-utilities 0.175.17__py3-none-any.whl → 0.185.8__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.
Files changed (94) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
  4. utilities/__init__.py +1 -1
  5. utilities/altair.py +8 -6
  6. utilities/asyncio.py +40 -56
  7. utilities/atools.py +9 -11
  8. utilities/cachetools.py +8 -6
  9. utilities/click.py +4 -3
  10. utilities/concurrent.py +1 -1
  11. utilities/constants.py +492 -0
  12. utilities/contextlib.py +23 -30
  13. utilities/contextvars.py +1 -23
  14. utilities/core.py +2581 -0
  15. utilities/dataclasses.py +16 -119
  16. utilities/docker.py +139 -45
  17. utilities/enum.py +1 -1
  18. utilities/errors.py +2 -16
  19. utilities/fastapi.py +5 -5
  20. utilities/fpdf2.py +2 -1
  21. utilities/functions.py +33 -264
  22. utilities/http.py +2 -3
  23. utilities/hypothesis.py +48 -25
  24. utilities/iterables.py +39 -575
  25. utilities/jinja2.py +3 -6
  26. utilities/jupyter.py +5 -3
  27. utilities/libcst.py +1 -1
  28. utilities/lightweight_charts.py +4 -6
  29. utilities/logging.py +17 -15
  30. utilities/math.py +1 -36
  31. utilities/more_itertools.py +4 -6
  32. utilities/numpy.py +2 -1
  33. utilities/operator.py +2 -2
  34. utilities/orjson.py +24 -25
  35. utilities/os.py +4 -185
  36. utilities/packaging.py +129 -0
  37. utilities/parse.py +33 -13
  38. utilities/pathlib.py +2 -136
  39. utilities/platform.py +8 -90
  40. utilities/polars.py +34 -31
  41. utilities/postgres.py +9 -4
  42. utilities/pottery.py +20 -18
  43. utilities/pqdm.py +3 -4
  44. utilities/psutil.py +2 -3
  45. utilities/pydantic.py +18 -4
  46. utilities/pydantic_settings.py +7 -9
  47. utilities/pydantic_settings_sops.py +3 -3
  48. utilities/pyinstrument.py +4 -4
  49. utilities/pytest.py +49 -108
  50. utilities/pytest_plugins/pytest_regressions.py +2 -2
  51. utilities/pytest_regressions.py +8 -6
  52. utilities/random.py +2 -8
  53. utilities/redis.py +98 -94
  54. utilities/reprlib.py +11 -118
  55. utilities/shellingham.py +66 -0
  56. utilities/slack_sdk.py +13 -12
  57. utilities/sqlalchemy.py +42 -30
  58. utilities/sqlalchemy_polars.py +16 -25
  59. utilities/subprocess.py +1166 -148
  60. utilities/tabulate.py +32 -0
  61. utilities/testbook.py +8 -8
  62. utilities/text.py +24 -115
  63. utilities/throttle.py +159 -0
  64. utilities/time.py +18 -0
  65. utilities/timer.py +29 -12
  66. utilities/traceback.py +15 -22
  67. utilities/types.py +38 -3
  68. utilities/typing.py +18 -12
  69. utilities/uuid.py +1 -1
  70. utilities/version.py +202 -45
  71. utilities/whenever.py +22 -150
  72. dycw_utilities-0.175.17.dist-info/METADATA +0 -34
  73. dycw_utilities-0.175.17.dist-info/RECORD +0 -103
  74. utilities/atomicwrites.py +0 -182
  75. utilities/cryptography.py +0 -41
  76. utilities/getpass.py +0 -8
  77. utilities/git.py +0 -19
  78. utilities/grp.py +0 -28
  79. utilities/gzip.py +0 -31
  80. utilities/json.py +0 -70
  81. utilities/permissions.py +0 -298
  82. utilities/pickle.py +0 -25
  83. utilities/pwd.py +0 -28
  84. utilities/re.py +0 -156
  85. utilities/sentinel.py +0 -73
  86. utilities/socket.py +0 -8
  87. utilities/string.py +0 -20
  88. utilities/tempfile.py +0 -136
  89. utilities/tzdata.py +0 -11
  90. utilities/tzlocal.py +0 -28
  91. utilities/warnings.py +0 -65
  92. utilities/zipfile.py +0 -25
  93. utilities/zoneinfo.py +0 -133
  94. {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +0 -0
utilities/constants.py ADDED
@@ -0,0 +1,492 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from getpass import getuser
6
+ from logging import getLogger
7
+ from os import cpu_count, environ
8
+ from pathlib import Path
9
+ from platform import system
10
+ from random import SystemRandom
11
+ from re import IGNORECASE
12
+ from socket import gethostname
13
+ from tempfile import gettempdir
14
+ from typing import TYPE_CHECKING, Any, assert_never, cast, override
15
+ from zoneinfo import ZoneInfo
16
+
17
+ from tzlocal import get_localzone
18
+ from whenever import (
19
+ Date,
20
+ DateDelta,
21
+ DateTimeDelta,
22
+ PlainDateTime,
23
+ Time,
24
+ TimeDelta,
25
+ ZonedDateTime,
26
+ )
27
+
28
+ if TYPE_CHECKING:
29
+ from utilities.types import System, TimeZone
30
+
31
+
32
+ # getpass
33
+
34
+
35
+ USER: str = getuser()
36
+
37
+
38
+ # math
39
+
40
+
41
+ MIN_FLOAT32, MAX_FLOAT32 = -3.4028234663852886e38, 3.4028234663852886e38
42
+ MIN_FLOAT64, MAX_FLOAT64 = -1.7976931348623157e308, 1.7976931348623157e308
43
+ MIN_INT8, MAX_INT8 = -(2 ** (8 - 1)), 2 ** (8 - 1) - 1
44
+ MIN_INT16, MAX_INT16 = -(2 ** (16 - 1)), 2 ** (16 - 1) - 1
45
+ MIN_INT32, MAX_INT32 = -(2 ** (32 - 1)), 2 ** (32 - 1) - 1
46
+ MIN_INT64, MAX_INT64 = -(2 ** (64 - 1)), 2 ** (64 - 1) - 1
47
+ MIN_UINT8, MAX_UINT8 = 0, 2**8 - 1
48
+ MIN_UINT16, MAX_UINT16 = 0, 2**16 - 1
49
+ MIN_UINT32, MAX_UINT32 = 0, 2**32 - 1
50
+ MIN_UINT64, MAX_UINT64 = 0, 2**64 - 1
51
+
52
+
53
+ # os
54
+
55
+
56
+ IS_CI: bool = "CI" in environ
57
+
58
+
59
+ def _get_cpu_count() -> int:
60
+ """Get the CPU count."""
61
+ count = cpu_count()
62
+ if count is None: # pragma: no cover
63
+ raise ValueError(count)
64
+ return count
65
+
66
+
67
+ CPU_COUNT: int = _get_cpu_count()
68
+
69
+
70
+ # platform
71
+
72
+
73
+ def _get_system() -> System:
74
+ """Get the system/OS name."""
75
+ sys = system()
76
+ if sys == "Windows": # skipif-not-windows
77
+ return "windows"
78
+ if sys == "Darwin": # skipif-not-macos
79
+ return "mac"
80
+ if sys == "Linux": # skipif-not-linux
81
+ return "linux"
82
+ raise ValueError(sys) # pragma: no cover
83
+
84
+
85
+ SYSTEM: System = _get_system()
86
+ IS_WINDOWS: bool = SYSTEM == "windows"
87
+ IS_MAC: bool = SYSTEM == "mac"
88
+ IS_LINUX: bool = SYSTEM == "linux"
89
+ IS_NOT_WINDOWS: bool = not IS_WINDOWS
90
+ IS_NOT_MAC: bool = not IS_MAC
91
+ IS_NOT_LINUX: bool = not IS_LINUX
92
+ IS_CI_AND_WINDOWS: bool = IS_CI and IS_WINDOWS
93
+ IS_CI_AND_MAC: bool = IS_CI and IS_MAC
94
+ IS_CI_AND_LINUX: bool = IS_CI and IS_LINUX
95
+ IS_CI_AND_NOT_WINDOWS: bool = IS_CI and IS_NOT_WINDOWS
96
+ IS_CI_AND_NOT_MAC: bool = IS_CI and IS_NOT_MAC
97
+ IS_CI_AND_NOT_LINUX: bool = IS_CI and IS_NOT_LINUX
98
+
99
+
100
+ def _get_max_pid() -> int | None:
101
+ """Get the system max process ID."""
102
+ match SYSTEM:
103
+ case "windows": # skipif-not-windows
104
+ return None
105
+ case "mac": # skipif-not-macos
106
+ return 99999
107
+ case "linux": # skipif-not-linux
108
+ path = Path("/proc/sys/kernel/pid_max")
109
+ try:
110
+ return int(path.read_text())
111
+ except FileNotFoundError: # pragma: no cover
112
+ return None
113
+ case never:
114
+ assert_never(never)
115
+
116
+
117
+ MAX_PID: int | None = _get_max_pid()
118
+
119
+
120
+ # pathlib
121
+
122
+
123
+ HOME: Path = Path.home()
124
+ PWD: Path = Path.cwd()
125
+
126
+
127
+ # platform -> os
128
+
129
+
130
+ def _get_effective_group_id() -> int | None:
131
+ """Get the effective group ID."""
132
+ match SYSTEM:
133
+ case "windows": # skipif-not-windows
134
+ return None
135
+ case "mac" | "linux": # skipif-windows
136
+ from os import getegid
137
+
138
+ return getegid()
139
+ case never:
140
+ assert_never(never)
141
+
142
+
143
+ EFFECTIVE_GROUP_ID: int | None = _get_effective_group_id()
144
+
145
+
146
+ def _get_effective_user_id() -> int | None:
147
+ """Get the effective user ID."""
148
+ match SYSTEM:
149
+ case "windows": # skipif-not-windows
150
+ return None
151
+ case "mac" | "linux": # skipif-windows
152
+ from os import geteuid
153
+
154
+ return geteuid()
155
+ case never:
156
+ assert_never(never)
157
+
158
+
159
+ EFFECTIVE_USER_ID: int | None = _get_effective_user_id()
160
+
161
+
162
+ # platform -> os -> grp
163
+
164
+
165
+ def _get_gid_name(gid: int, /) -> str | None:
166
+ """Get the name of a group ID."""
167
+ match SYSTEM:
168
+ case "windows": # skipif-not-windows
169
+ return None
170
+ case "mac" | "linux":
171
+ from grp import getgrgid
172
+
173
+ return getgrgid(gid).gr_name
174
+ case never:
175
+ assert_never(never)
176
+
177
+
178
+ ROOT_GROUP_NAME: str | None = _get_gid_name(0)
179
+ EFFECTIVE_GROUP_NAME: str | None = (
180
+ None if EFFECTIVE_GROUP_ID is None else _get_gid_name(EFFECTIVE_GROUP_ID)
181
+ )
182
+
183
+
184
+ # platform -> os -> pwd
185
+
186
+
187
+ def _get_uid_name(uid: int, /) -> str | None:
188
+ """Get the name of a user ID."""
189
+ match SYSTEM:
190
+ case "windows": # skipif-not-windows
191
+ return None
192
+ case "mac" | "linux": # skipif-windows
193
+ from pwd import getpwuid
194
+
195
+ return getpwuid(uid).pw_name
196
+ case never:
197
+ assert_never(never)
198
+
199
+
200
+ ROOT_USER_NAME: str | None = _get_uid_name(0)
201
+ EFFECTIVE_USER_NAME: str | None = (
202
+ None if EFFECTIVE_USER_ID is None else _get_uid_name(EFFECTIVE_USER_ID)
203
+ )
204
+
205
+
206
+ # random
207
+
208
+
209
+ SYSTEM_RANDOM: SystemRandom = SystemRandom()
210
+
211
+
212
+ # reprlib
213
+
214
+
215
+ RICH_MAX_WIDTH: int = 80
216
+ RICH_INDENT_SIZE: int = 4
217
+ RICH_MAX_LENGTH: int | None = 20
218
+ RICH_MAX_STRING: int | None = None
219
+ RICH_MAX_DEPTH: int | None = None
220
+ RICH_EXPAND_ALL: bool = False
221
+
222
+
223
+ # sentinel
224
+
225
+
226
+ class _Meta(type):
227
+ """Metaclass for the sentinel."""
228
+
229
+ instance: Any = None
230
+
231
+ @override
232
+ def __call__(cls, *args: Any, **kwargs: Any) -> Any:
233
+ if cls.instance is None:
234
+ cls.instance = super().__call__(*args, **kwargs)
235
+ return cls.instance
236
+
237
+
238
+ class Sentinel(metaclass=_Meta):
239
+ """Base class for the sentinel object."""
240
+
241
+ @override
242
+ def __repr__(self) -> str:
243
+ return _SENTINEL_REPR
244
+
245
+ @override
246
+ def __str__(self) -> str:
247
+ return repr(self)
248
+
249
+ @classmethod
250
+ def parse(cls, text: str, /) -> Sentinel:
251
+ """Parse a string into the Sentinel value."""
252
+ if _SENTINEL_PATTERN.search(text):
253
+ return sentinel
254
+ raise SentinelParseError(text=text)
255
+
256
+
257
+ _SENTINEL_PATTERN = re.compile("^(|sentinel|<sentinel>)$", flags=IGNORECASE)
258
+ _SENTINEL_REPR = "<sentinel>"
259
+
260
+
261
+ @dataclass(kw_only=True, slots=True)
262
+ class SentinelParseError(Exception):
263
+ text: str
264
+
265
+ @override
266
+ def __str__(self) -> str:
267
+ return f"Unable to parse sentinel; got {self.text!r}"
268
+
269
+
270
+ sentinel = Sentinel()
271
+
272
+
273
+ # socket
274
+
275
+
276
+ HOSTNAME = gethostname()
277
+
278
+
279
+ # tempfile
280
+
281
+
282
+ TEMP_DIR: Path = Path(gettempdir())
283
+
284
+
285
+ # text
286
+
287
+
288
+ LIST_SEPARATOR: str = ","
289
+ PAIR_SEPARATOR: str = "="
290
+ BRACKETS: set[tuple[str, str]] = {("(", ")"), ("[", "]"), ("{", "}")}
291
+
292
+
293
+ # tzlocal
294
+
295
+
296
+ def _get_local_time_zone() -> ZoneInfo:
297
+ """Get the local time zone, with the logging disabled."""
298
+ logger = getLogger("tzlocal") # avoid import cycle
299
+ init_disabled = logger.disabled
300
+ logger.disabled = True
301
+ time_zone = get_localzone()
302
+ logger.disabled = init_disabled
303
+ return time_zone
304
+
305
+
306
+ LOCAL_TIME_ZONE: ZoneInfo = _get_local_time_zone()
307
+ LOCAL_TIME_ZONE_NAME: TimeZone = cast("TimeZone", LOCAL_TIME_ZONE.key)
308
+
309
+
310
+ # tzlocal -> whenever
311
+
312
+
313
+ def _get_now_local() -> ZonedDateTime:
314
+ """Get the current zoned date-time in the local time-zone."""
315
+ return ZonedDateTime.now(LOCAL_TIME_ZONE_NAME)
316
+
317
+
318
+ NOW_LOCAL: ZonedDateTime = _get_now_local()
319
+ TODAY_LOCAL: Date = NOW_LOCAL.date()
320
+ TIME_LOCAL: Time = NOW_LOCAL.time()
321
+ NOW_LOCAL_PLAIN: PlainDateTime = NOW_LOCAL.to_plain()
322
+
323
+
324
+ # whenever
325
+
326
+
327
+ ZERO_DAYS: DateDelta = DateDelta()
328
+ ZERO_TIME: TimeDelta = TimeDelta()
329
+ NANOSECOND: TimeDelta = TimeDelta(nanoseconds=1)
330
+ MICROSECOND: TimeDelta = TimeDelta(microseconds=1)
331
+ MILLISECOND: TimeDelta = TimeDelta(milliseconds=1)
332
+ SECOND: TimeDelta = TimeDelta(seconds=1)
333
+ MINUTE: TimeDelta = TimeDelta(minutes=1)
334
+ HOUR: TimeDelta = TimeDelta(hours=1)
335
+ DAY: DateDelta = DateDelta(days=1)
336
+ WEEK: DateDelta = DateDelta(weeks=1)
337
+ MONTH: DateDelta = DateDelta(months=1)
338
+ YEAR: DateDelta = DateDelta(years=1)
339
+
340
+
341
+ DATE_DELTA_MIN: DateDelta = DateDelta(weeks=-521722, days=-5)
342
+ DATE_DELTA_MAX: DateDelta = DateDelta(weeks=521722, days=5)
343
+ TIME_DELTA_MIN: TimeDelta = TimeDelta(hours=-87831216)
344
+ TIME_DELTA_MAX: TimeDelta = TimeDelta(hours=87831216)
345
+ DATE_TIME_DELTA_MIN: DateTimeDelta = DateTimeDelta(
346
+ weeks=-521722,
347
+ days=-5,
348
+ hours=-23,
349
+ minutes=-59,
350
+ seconds=-59,
351
+ milliseconds=-999,
352
+ microseconds=-999,
353
+ nanoseconds=-999,
354
+ )
355
+ DATE_TIME_DELTA_MAX: DateTimeDelta = DateTimeDelta(
356
+ weeks=521722,
357
+ days=5,
358
+ hours=23,
359
+ minutes=59,
360
+ seconds=59,
361
+ milliseconds=999,
362
+ microseconds=999,
363
+ nanoseconds=999,
364
+ )
365
+
366
+
367
+ SECONDS_PER_DAY: int = 24 * 60 * 60
368
+ NANOSECONDS_PER_SECOND: int = 1_000_000_000
369
+ NANOSECONDS_PER_DAY: int = SECONDS_PER_DAY * NANOSECONDS_PER_SECOND
370
+
371
+
372
+ # zoneinfo
373
+
374
+
375
+ UTC: ZoneInfo = ZoneInfo("UTC")
376
+ HongKong: ZoneInfo = ZoneInfo("Asia/Hong_Kong")
377
+ Tokyo: ZoneInfo = ZoneInfo("Asia/Tokyo")
378
+ USCentral: ZoneInfo = ZoneInfo("US/Central")
379
+ USEastern: ZoneInfo = ZoneInfo("US/Eastern")
380
+
381
+
382
+ # zoneinfo -> whenever
383
+
384
+
385
+ ZONED_DATE_TIME_MIN: ZonedDateTime = PlainDateTime.MIN.assume_tz(UTC.key)
386
+ ZONED_DATE_TIME_MAX: ZonedDateTime = PlainDateTime.MAX.assume_tz(UTC.key)
387
+
388
+
389
+ def _get_now(time_zone: str = UTC.key, /) -> ZonedDateTime:
390
+ """Get the current zoned date-time."""
391
+ return ZonedDateTime.now(time_zone)
392
+
393
+
394
+ NOW_UTC: ZonedDateTime = _get_now()
395
+ TODAY_UTC: Date = NOW_UTC.date()
396
+ TIME_UTC: Time = NOW_UTC.time()
397
+ NOW_UTC_PLAIN: PlainDateTime = NOW_UTC.to_plain()
398
+
399
+
400
+ __all__ = [
401
+ "BRACKETS",
402
+ "CPU_COUNT",
403
+ "DATE_DELTA_MAX",
404
+ "DATE_DELTA_MIN",
405
+ "DATE_TIME_DELTA_MAX",
406
+ "DATE_TIME_DELTA_MIN",
407
+ "DAY",
408
+ "EFFECTIVE_GROUP_ID",
409
+ "EFFECTIVE_GROUP_NAME",
410
+ "EFFECTIVE_USER_ID",
411
+ "EFFECTIVE_USER_NAME",
412
+ "HOME",
413
+ "HOSTNAME",
414
+ "HOUR",
415
+ "IS_CI",
416
+ "IS_CI_AND_LINUX",
417
+ "IS_CI_AND_MAC",
418
+ "IS_CI_AND_NOT_LINUX",
419
+ "IS_CI_AND_NOT_MAC",
420
+ "IS_CI_AND_NOT_WINDOWS",
421
+ "IS_CI_AND_WINDOWS",
422
+ "IS_LINUX",
423
+ "IS_MAC",
424
+ "IS_NOT_LINUX",
425
+ "IS_NOT_MAC",
426
+ "IS_NOT_WINDOWS",
427
+ "IS_WINDOWS",
428
+ "LIST_SEPARATOR",
429
+ "LOCAL_TIME_ZONE",
430
+ "LOCAL_TIME_ZONE_NAME",
431
+ "MAX_FLOAT32",
432
+ "MAX_FLOAT64",
433
+ "MAX_INT8",
434
+ "MAX_INT16",
435
+ "MAX_INT32",
436
+ "MAX_INT64",
437
+ "MAX_PID",
438
+ "MAX_UINT8",
439
+ "MAX_UINT16",
440
+ "MAX_UINT32",
441
+ "MAX_UINT64",
442
+ "MICROSECOND",
443
+ "MILLISECOND",
444
+ "MINUTE",
445
+ "MIN_FLOAT32",
446
+ "MIN_FLOAT64",
447
+ "MIN_INT8",
448
+ "MIN_INT16",
449
+ "MIN_INT32",
450
+ "MIN_INT64",
451
+ "MIN_UINT8",
452
+ "MIN_UINT16",
453
+ "MIN_UINT32",
454
+ "MIN_UINT64",
455
+ "MONTH",
456
+ "NANOSECOND",
457
+ "NANOSECONDS_PER_DAY",
458
+ "NANOSECONDS_PER_SECOND",
459
+ "NOW_LOCAL",
460
+ "NOW_LOCAL_PLAIN",
461
+ "NOW_UTC",
462
+ "NOW_UTC_PLAIN",
463
+ "PAIR_SEPARATOR",
464
+ "PWD",
465
+ "ROOT_GROUP_NAME",
466
+ "ROOT_USER_NAME",
467
+ "SECOND",
468
+ "SECONDS_PER_DAY",
469
+ "SYSTEM",
470
+ "SYSTEM_RANDOM",
471
+ "TEMP_DIR",
472
+ "TIME_DELTA_MAX",
473
+ "TIME_DELTA_MIN",
474
+ "TIME_LOCAL",
475
+ "TIME_UTC",
476
+ "TODAY_LOCAL",
477
+ "TODAY_UTC",
478
+ "USER",
479
+ "UTC",
480
+ "WEEK",
481
+ "YEAR",
482
+ "ZERO_DAYS",
483
+ "ZERO_TIME",
484
+ "ZONED_DATE_TIME_MAX",
485
+ "ZONED_DATE_TIME_MIN",
486
+ "HongKong",
487
+ "Sentinel",
488
+ "Tokyo",
489
+ "USCentral",
490
+ "USEastern",
491
+ "sentinel",
492
+ ]
utilities/contextlib.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import re
4
3
  from asyncio import create_task, get_event_loop
5
4
  from contextlib import (
6
5
  _AsyncGeneratorContextManager,
@@ -90,12 +89,13 @@ def enhanced_context_manager[**P, T_co](
90
89
  with gcm as value:
91
90
  yield value
92
91
  finally:
93
- _ = signal(SIGABRT, sigabrt0) if sigabrt else None
94
- _ = signal(SIGFPE, sigfpe0) if sigfpe else None
95
- _ = signal(SIGILL, sigill0) if sigill else None
96
- _ = signal(SIGINT, sigint0) if sigint else None
97
- _ = signal(SIGSEGV, sigsegv0) if sigsegv else None
98
- _ = signal(SIGTERM, sigterm0) if sigterm else None
92
+ with _suppress_signal_error():
93
+ _ = signal(SIGABRT, sigabrt0) if sigabrt else None
94
+ _ = signal(SIGFPE, sigfpe0) if sigfpe else None
95
+ _ = signal(SIGILL, sigill0) if sigill else None
96
+ _ = signal(SIGINT, sigint0) if sigint else None
97
+ _ = signal(SIGSEGV, sigsegv0) if sigsegv else None
98
+ _ = signal(SIGTERM, sigterm0) if sigterm else None
99
99
 
100
100
  return wrapped
101
101
 
@@ -173,12 +173,13 @@ def enhanced_async_context_manager[**P, T_co](
173
173
  async with agcm as value:
174
174
  yield value
175
175
  finally:
176
- _ = signal(SIGABRT, sigabrt0) if sigabrt else None
177
- _ = signal(SIGFPE, sigfpe0) if sigfpe else None
178
- _ = signal(SIGILL, sigill0) if sigill else None
179
- _ = signal(SIGINT, sigint0) if sigint else None
180
- _ = signal(SIGSEGV, sigsegv0) if sigsegv else None
181
- _ = signal(SIGTERM, sigterm0) if sigterm else None
176
+ with _suppress_signal_error():
177
+ _ = signal(SIGABRT, sigabrt0) if sigabrt else None
178
+ _ = signal(SIGFPE, sigfpe0) if sigfpe else None
179
+ _ = signal(SIGILL, sigill0) if sigill else None
180
+ _ = signal(SIGINT, sigint0) if sigint else None
181
+ _ = signal(SIGSEGV, sigsegv0) if sigsegv else None
182
+ _ = signal(SIGTERM, sigterm0) if sigterm else None
182
183
 
183
184
  return wrapped
184
185
 
@@ -191,7 +192,8 @@ def _swap_handler(
191
192
  ) -> _HANDLER:
192
193
  orig_handler = getsignal(signum)
193
194
  new_handler = _make_handler(signum, obj)
194
- _ = signal(signum, new_handler)
195
+ with _suppress_signal_error():
196
+ _ = signal(signum, new_handler)
195
197
  return orig_handler
196
198
 
197
199
 
@@ -220,24 +222,15 @@ def _make_handler(
220
222
  return new_handler
221
223
 
222
224
 
223
- ##
224
-
225
-
226
- _SUPER_OBJECT_HAS_NO_ATTRIBUTE = re.compile(r"'super' object has no attribute '\w+'")
227
-
228
-
229
225
  @contextmanager
230
- def suppress_super_object_attribute_error() -> Iterator[None]:
231
- """Suppress the super() attribute error, for mix-ins."""
226
+ def _suppress_signal_error() -> Iterator[None]:
232
227
  try:
233
228
  yield
234
- except AttributeError as error:
235
- if not _SUPER_OBJECT_HAS_NO_ATTRIBUTE.search(error.args[0]):
236
- raise
229
+ except ValueError as error:
230
+ (msg,) = error.args
231
+ if msg == "signal only works in main thread of the main interpreter":
232
+ return
233
+ raise # pragma: no cover
237
234
 
238
235
 
239
- __all__ = [
240
- "enhanced_async_context_manager",
241
- "enhanced_context_manager",
242
- "suppress_super_object_attribute_error",
243
- ]
236
+ __all__ = ["enhanced_async_context_manager", "enhanced_context_manager"]
utilities/contextvars.py CHANGED
@@ -1,15 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from contextlib import contextmanager
4
3
  from contextvars import ContextVar
5
- from typing import TYPE_CHECKING
6
-
7
- if TYPE_CHECKING:
8
- from collections.abc import Iterator
9
-
10
-
11
- ##
12
-
13
4
 
14
5
  _GLOBAL_BREAKPOINT = ContextVar("GLOBAL_BREAKPOINT", default=False)
15
6
 
@@ -25,17 +16,4 @@ def set_global_breakpoint() -> None:
25
16
  _ = _GLOBAL_BREAKPOINT.set(True)
26
17
 
27
18
 
28
- ##
29
-
30
-
31
- @contextmanager
32
- def yield_set_context(var: ContextVar[bool], /) -> Iterator[None]:
33
- """Yield a context var as being set."""
34
- token = var.set(True)
35
- try:
36
- yield
37
- finally:
38
- _ = var.reset(token)
39
-
40
-
41
- __all__ = ["global_breakpoint", "set_global_breakpoint", "yield_set_context"]
19
+ __all__ = ["global_breakpoint", "set_global_breakpoint"]