pyuncommonkit 0.1.0__tar.gz → 0.1.2__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.
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/PKG-INFO +1 -1
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/pyproject.toml +1 -1
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/pyproject.toml.orig +3 -2
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/src/commonkit/keys.py +5 -4
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/src/commonkit/shared.py +8 -2
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/src/commonkit/store.py +6 -4
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/LICENSE +0 -0
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/README.md +0 -0
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/src/commonkit/__init__.py +0 -0
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/src/commonkit/cache.py +0 -0
- {pyuncommonkit-0.1.0 → pyuncommonkit-0.1.2}/src/commonkit/read.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pyuncommonkit"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "Named shared-memory segments for numpy arrays: publish once, attach from any process."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -16,7 +16,8 @@ dependencies = [
|
|
|
16
16
|
]
|
|
17
17
|
keywords = ["numpy", "npy", "mmap", "shared-memory", "cache"]
|
|
18
18
|
# No License :: classifier: PEP 639's `license` expression replaces it. Linux and macOS only:
|
|
19
|
-
# Windows
|
|
19
|
+
# Windows has a default root since 0.1.1 and passes the suite on a Windows 11 machine, but has no
|
|
20
|
+
# CI yet, so no Windows classifier.
|
|
20
21
|
classifiers = [
|
|
21
22
|
"Development Status :: 4 - Beta",
|
|
22
23
|
"Intended Audience :: Developers",
|
|
@@ -42,10 +42,11 @@ from __future__ import annotations
|
|
|
42
42
|
import re
|
|
43
43
|
from collections.abc import Iterable
|
|
44
44
|
|
|
45
|
-
#: Segments are addressed by
|
|
45
|
+
#: Segments are addressed by a lowercase hex digest: 32 digits (md5) or 64 (BLAKE3,
|
|
46
|
+
#: contract §3.4); the length tells them apart, so one key still names one content. Every pattern here is used with
|
|
46
47
|
#: ``fullmatch``, never ``match`` with ``$``: ``$`` also matches before a final
|
|
47
48
|
#: newline, which let ``"...\n"`` through as a digest or token (contract §3.1).
|
|
48
|
-
_DIGEST_RE = re.compile(r"[0-9a-f]{32}")
|
|
49
|
+
_DIGEST_RE = re.compile(r"[0-9a-f]{32}|[0-9a-f]{64}")
|
|
49
50
|
|
|
50
51
|
#: Every other component is a bare lowercase token. Restrictive on purpose:
|
|
51
52
|
#: these become PATH components, and a key is the only thing standing between
|
|
@@ -110,7 +111,7 @@ class KeyScheme:
|
|
|
110
111
|
|
|
111
112
|
self._key_re = re.compile(
|
|
112
113
|
rf"{re.escape(self.domain)}:{re.escape(self.version)}:"
|
|
113
|
-
r"(?P<form>[a-z0-9]+):(?P<digest>[0-9a-f]{32}):"
|
|
114
|
+
r"(?P<form>[a-z0-9]+):(?P<digest>[0-9a-f]{32}|[0-9a-f]{64}):"
|
|
114
115
|
r"(?P<variant>[a-z0-9]+)")
|
|
115
116
|
|
|
116
117
|
# ------------------------------------------------------------- building
|
|
@@ -140,7 +141,7 @@ class KeyScheme:
|
|
|
140
141
|
"""The store key for one array. Raises on anything it cannot address."""
|
|
141
142
|
digest = str(digest).translate(_ASCII_LOWER)
|
|
142
143
|
if not _DIGEST_RE.fullmatch(digest):
|
|
143
|
-
raise ValueError(f"not
|
|
144
|
+
raise ValueError(f"not a 32- or 64-digit hex digest: {digest!r}")
|
|
144
145
|
form = self.default_form if form is None else str(form)
|
|
145
146
|
if form not in self.forms:
|
|
146
147
|
raise ValueError(
|
|
@@ -44,15 +44,21 @@ from .store import SharedArrayStore
|
|
|
44
44
|
|
|
45
45
|
ENV_PREFIX = "COMMONKIT_STORE_"
|
|
46
46
|
_DARWIN = sys.platform == "darwin"
|
|
47
|
+
_WINDOWS = sys.platform == "win32"
|
|
47
48
|
|
|
48
49
|
#: Per-platform defaults. macOS: the user cache directory on the internal SSD -- it has no
|
|
49
50
|
#: /dev/shm, the SSD beat a RAM disk on first attach (0.69 ms against 0.76 ms), and a
|
|
50
51
|
#: ``ram://`` disk commits its whole size up front as dirty memory. Linux: /dev/shm, which
|
|
51
|
-
#: is RAM, so the budget is its only real bound.
|
|
52
|
+
#: is RAM, so the budget is its only real bound. Windows: no /dev/shm either (it would
|
|
53
|
+
#: resolve to ``\dev\shm`` on the current drive), so the per-user local cache, on disk like
|
|
54
|
+
#: macOS and with its budget (contract 10.1; from seantrue/commonkit d8198ae).
|
|
52
55
|
DEFAULTS = {
|
|
53
56
|
"ROOT": (os.path.expanduser("~/Library/Caches/commonkit/store") if _DARWIN
|
|
57
|
+
else os.path.join(os.environ.get("LOCALAPPDATA")
|
|
58
|
+
or os.path.expanduser("~/AppData/Local"), "commonkit", "store")
|
|
59
|
+
if _WINDOWS
|
|
54
60
|
else "/dev/shm/commonkit"),
|
|
55
|
-
"MAX_GB": 2.0 if _DARWIN else 8.0,
|
|
61
|
+
"MAX_GB": 2.0 if _DARWIN or _WINDOWS else 8.0,
|
|
56
62
|
"MIN_FREE_GB": 8.0,
|
|
57
63
|
"TTL_S": 900.0,
|
|
58
64
|
"REAP_S": 30.0,
|
|
@@ -97,7 +97,7 @@ _NPY_MAGIC = b"\x93NUMPY"
|
|
|
97
97
|
#: The reaper's candidates (contract §9.4): ``{form}/{xx}/{digest}.{variant}.npy`` with
|
|
98
98
|
#: the shard equal to the digest's first two characters. Matched per path component.
|
|
99
99
|
_SHARD_RE = re.compile(r"[0-9a-f]{2}")
|
|
100
|
-
_SEGMENT_NAME_RE = re.compile(r"([0-9a-f]{2})[0-9a-f]{30}\.[a-z0-9]+\.npy")
|
|
100
|
+
_SEGMENT_NAME_RE = re.compile(r"([0-9a-f]{2})(?:[0-9a-f]{30}|[0-9a-f]{62})\.[a-z0-9]+\.npy")
|
|
101
101
|
|
|
102
102
|
#: An orphaned temporary file is swept once it is this old (contract §9.5). A writer
|
|
103
103
|
#: holds its ``.tmp`` for one ``np.save`` -- milliseconds -- so an hour old means the
|
|
@@ -356,7 +356,9 @@ class SharedArrayStore:
|
|
|
356
356
|
``keys.relpath`` is strict about the key shape, so a malformed key raises
|
|
357
357
|
here rather than producing a path the reaper might later unlink.
|
|
358
358
|
"""
|
|
359
|
-
|
|
359
|
+
# The relative path is `/`-separated (contract 3.6); joined with this platform's
|
|
360
|
+
# separator, so a Windows path isn't `...\store\plumarr/91/...`.
|
|
361
|
+
return os.path.join(self.root, *_keys.relpath(key).split("/"))
|
|
360
362
|
|
|
361
363
|
# ---------------------------------------------------------------- reading
|
|
362
364
|
|
|
@@ -812,8 +814,8 @@ def _measure_free(root: str) -> int:
|
|
|
812
814
|
"""Free bytes on ``root``'s volume, 0 on any failure (contract §9.9)."""
|
|
813
815
|
# Any failure is 0 free, not an exception: catching only OSError let AttributeError
|
|
814
816
|
# out on a platform without os.statvfs. Windows has none, so it asks shutil, whose
|
|
815
|
-
# POSIX branch is this same f_bavail * f_frsize
|
|
816
|
-
#
|
|
817
|
+
# POSIX branch is this same f_bavail * f_frsize and whose Windows one is
|
|
818
|
+
# GetDiskFreeSpaceExW's free-to-caller figure (§9.9).
|
|
817
819
|
try:
|
|
818
820
|
if hasattr(os, "statvfs"):
|
|
819
821
|
st = os.statvfs(root)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|