pyuncommonkit 0.1.0__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.
- commonkit/__init__.py +19 -0
- commonkit/cache.py +125 -0
- commonkit/keys.py +229 -0
- commonkit/read.py +49 -0
- commonkit/shared.py +227 -0
- commonkit/store.py +963 -0
- pyuncommonkit-0.1.0.dist-info/METADATA +200 -0
- pyuncommonkit-0.1.0.dist-info/RECORD +10 -0
- pyuncommonkit-0.1.0.dist-info/WHEEL +4 -0
- pyuncommonkit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyuncommonkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Named shared-memory segments for numpy arrays: publish once, attach from any process.
|
|
5
|
+
Keywords: numpy,npy,mmap,shared-memory,cache
|
|
6
|
+
Author: Sean D. True
|
|
7
|
+
Author-email: Sean D. True <sean.true@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Filesystems
|
|
24
|
+
Requires-Dist: numpy>=1.26
|
|
25
|
+
Requires-Dist: pytest>=8 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.6 ; extra == 'dev'
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Project-URL: Homepage, https://github.com/seantrue/pycommonkit
|
|
29
|
+
Project-URL: Repository, https://github.com/seantrue/pycommonkit
|
|
30
|
+
Project-URL: Issues, https://github.com/seantrue/pycommonkit/issues
|
|
31
|
+
Project-URL: Contract, https://github.com/seantrue/pycommonkit/blob/main/docs/contract.md
|
|
32
|
+
Project-URL: Rust crate, https://crates.io/crates/commonkit
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="https://raw.githubusercontent.com/seantrue/pycommonkit/main/docs/images/commons-banner.jpg"
|
|
38
|
+
alt="Illustrated aerial view of farmland: square fields and center-pivot circles on a shared road grid"
|
|
39
|
+
width="100%">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
# pycommonkit
|
|
43
|
+
|
|
44
|
+
Named shared-memory segments for numpy arrays. One process decodes and
|
|
45
|
+
publishes; every other process attaches the same physical pages.
|
|
46
|
+
|
|
47
|
+
Installed as **`pyuncommonkit`**, imported as **`commonkit`** (the PyPI name `commonkit` belongs
|
|
48
|
+
to an unrelated project, and PyPI refused `pycommonkit` as too similar to it). A Rust crate, `commonkit`, implementing the same on-disk contract is
|
|
49
|
+
in this repository under `rust/` (`docs/rust-crate-plan.md`): keys, segments, the store and the
|
|
50
|
+
reaper are done, and a cross-language conformance suite runs both on one tree.
|
|
51
|
+
|
|
52
|
+
The name is for Fortran's **named `COMMON` block**. `COMMON /GRID/ X, Y` in one
|
|
53
|
+
subroutine and the same name in another, compiled separately, give both the same
|
|
54
|
+
storage: the linker joins them by the name alone, and no unit owns the block. The
|
|
55
|
+
name is the whole interface. A commonkit key is the same idea: any process that
|
|
56
|
+
can spell the key reaches the same bytes, without knowing who wrote them.
|
|
57
|
+
|
|
58
|
+
Where the analogy stops is where commonkit had to be stricter:
|
|
59
|
+
|
|
60
|
+
* **Across processes, not within one program.** A `COMMON` block is shared by the
|
|
61
|
+
program units of one executable; a segment is shared by separate processes.
|
|
62
|
+
* **The layout travels with the data.** Each Fortran unit declares its own view
|
|
63
|
+
of a block, and a unit that declares it differently silently reads other
|
|
64
|
+
variables' bits. A segment carries its dtype, shape and order in its `.npy`
|
|
65
|
+
header, so every reader sees the same layout, and publishing a different array
|
|
66
|
+
under an existing key is refused loudly (`KeyCollision`).
|
|
67
|
+
* **Written once, then read-only.** Any unit may assign into a `COMMON` block at
|
|
68
|
+
any time. A segment is written once, whole, and readers map it read-only.
|
|
69
|
+
* **It may not be there.** A `COMMON` block exists for the whole run. A segment
|
|
70
|
+
exists only until the reaper needs the space, which is why every caller must be
|
|
71
|
+
able to recompute it (below).
|
|
72
|
+
|
|
73
|
+
## The contract
|
|
74
|
+
|
|
75
|
+
`docs/contract.md` (version 1) is the authority on the on-disk format: keys, layout, the `.npy`
|
|
76
|
+
segment, publishing, the write-once rules, touch, the reaper, policy and counters. Python is the
|
|
77
|
+
reference implementation and `conformance/vectors/` decides any disagreement
|
|
78
|
+
(`conformance/generate.py` regenerates the fixtures byte for byte). Both the Python package and
|
|
79
|
+
the Rust crate pass every vector.
|
|
80
|
+
|
|
81
|
+
## Status
|
|
82
|
+
|
|
83
|
+
See `docs/STATUS.md`. Nothing is published to PyPI or crates.io yet.
|
|
84
|
+
|
|
85
|
+
## What it is
|
|
86
|
+
|
|
87
|
+
* `SharedArrayStore` — publish/attach `.npy` segments under a root directory,
|
|
88
|
+
with admission gates, a TTL reaper and a byte budget.
|
|
89
|
+
* `KeyScheme` — the addressing vocabulary for one *domain*. A scheme fixes the
|
|
90
|
+
domain, a version, the legal forms and the legal variants, and turns
|
|
91
|
+
`(digest, variant, form)` into both a key and a relative path.
|
|
92
|
+
* `shared_store()` — the ONE store per host that every user shares. Its byte budget, TTL,
|
|
93
|
+
free-space floor, fsync and republish verification come only from the tree's `.policy.json`
|
|
94
|
+
(which wins), then `COMMONKIT_STORE_*` environment variables, then the defaults (macOS:
|
|
95
|
+
`~/Library/Caches/commonkit/store`, 2 GB, 900 s, 8 GB free), because the reaping process applies
|
|
96
|
+
its own policy to the whole tree; the root comes from the environment or the default. A caller
|
|
97
|
+
chooses only `mode` and the smallest array worth storing.
|
|
98
|
+
* `TieredPixelCache` — an in-process L1 memo in front of a store, so a loop
|
|
99
|
+
that looks up the same digest repeatedly pays one attach rather than one per
|
|
100
|
+
call.
|
|
101
|
+
|
|
102
|
+
## A cache, not a store of record
|
|
103
|
+
|
|
104
|
+
**A missing segment is never an error, only a slower path.** The store promises
|
|
105
|
+
that a key which is present names the right bytes. It never promises that a key
|
|
106
|
+
is present. So every caller must be able to compute the value again when
|
|
107
|
+
`attach` returns `None`, and must treat a `publish` that returns `False` as
|
|
108
|
+
normal.
|
|
109
|
+
|
|
110
|
+
A segment can be absent at any moment, for reasons the caller doesn't control:
|
|
111
|
+
|
|
112
|
+
* **Its publish was declined.** Under pressure the store refuses rather than
|
|
113
|
+
blocks: the array was smaller than worth storing or larger than allowed, the
|
|
114
|
+
volume was below its free-space floor, or the tree was over its byte budget.
|
|
115
|
+
A decline is how the store survives back-pressure, not a fault.
|
|
116
|
+
* **The reaper removed it.** Segments are evicted when they expire, least
|
|
117
|
+
recently used first to get back under the byte budget, and to restore the
|
|
118
|
+
free-space floor. The reaper doesn't know who might still want a segment, and
|
|
119
|
+
doesn't ask: no reference counts, no coordination between processes.
|
|
120
|
+
* **It was torn.** A file shorter than its header promises, after a crash with
|
|
121
|
+
`fsync` off (the default) or damage from outside the contract, is a miss.
|
|
122
|
+
* **The store is disabled.** The mode is `off`, or the root can't be created or
|
|
123
|
+
written: every attach misses and every publish declines.
|
|
124
|
+
* **The tree is gone.** On Linux it lives in `/dev/shm` and does not survive a
|
|
125
|
+
reboot; any root can be cleared; another machine has its own tree.
|
|
126
|
+
|
|
127
|
+
That one rule is what makes the rest cheap. The reaper can unlink freely,
|
|
128
|
+
publish never blocks, writes need no durability, and a process that fails
|
|
129
|
+
costs the others only time: the next reader recomputes.
|
|
130
|
+
|
|
131
|
+
**What does raise.** Exactly two things, both bugs at the call site rather than
|
|
132
|
+
conditions of the store: building a key that isn't valid for its scheme
|
|
133
|
+
(`ValueError`), and publishing a *different* array under a key that already
|
|
134
|
+
holds one (`KeyCollision`, a `ValueError` subclass), which means a key scheme or
|
|
135
|
+
a digest is broken. Everything else (an absent root, a full filesystem, a
|
|
136
|
+
reaped or torn segment, a hostile `.usage.json` or `.policy.json`) returns
|
|
137
|
+
`None` or `False` and ticks a counter. The constructor never raises: an
|
|
138
|
+
unusable root leaves the store `disabled` and every method a no-op.
|
|
139
|
+
|
|
140
|
+
**Replacing a value under the same key.** A segment is written once: publishing
|
|
141
|
+
the same array again only refreshes it, and a different array raises
|
|
142
|
+
`KeyCollision`. A caller that must replace a value whose key hasn't changed (a
|
|
143
|
+
recompute after a fix the key doesn't capture) calls `remove(key)` first, then
|
|
144
|
+
publishes. `remove` only unlinks, so a reader still holding the old segment keeps
|
|
145
|
+
its bytes; there is no in-use check.
|
|
146
|
+
|
|
147
|
+
**A value that can't be recomputed doesn't belong here alone.** Keep its
|
|
148
|
+
durable copy somewhere you own and use the store to share it fast. (A pinned
|
|
149
|
+
subtree the reaper skips is a possible later addition; it isn't built.)
|
|
150
|
+
|
|
151
|
+
**Callers never need `.usage.json`.** It is the reaper's record of how much the
|
|
152
|
+
tree holds, read only by the byte-budget gate. When it is missing or stale,
|
|
153
|
+
usage is unknown, that gate passes, and the free-space floor still applies;
|
|
154
|
+
the next complete reap writes a fresh one.
|
|
155
|
+
|
|
156
|
+
## Keys
|
|
157
|
+
|
|
158
|
+
{domain}:{version}:{form}:{digest}:{variant}
|
|
159
|
+
|
|
160
|
+
and the path it names:
|
|
161
|
+
|
|
162
|
+
{root}/{form}/{digest[:2]}/{digest}.{variant}.npy
|
|
163
|
+
|
|
164
|
+
Two-level sharding, so no directory holds a million entries. The `variant` axis
|
|
165
|
+
is whatever distinguishes two renderings of the same source: for images it is
|
|
166
|
+
orientation (`o0`, `o1`, `oa`), for audio it would be sample rate (`sr16000`).
|
|
167
|
+
Schemes are registered by domain so `parse()` and `relpath()` can dispatch on a
|
|
168
|
+
key alone, which is what lets one store hold several domains.
|
|
169
|
+
|
|
170
|
+
## Tests
|
|
171
|
+
|
|
172
|
+
uv run --extra dev pytest -q # includes the conformance vectors
|
|
173
|
+
|
|
174
|
+
The suite addresses audio and video rather than images, to show the package is
|
|
175
|
+
not pixel-specific. Two modules run end to end against real media and skip
|
|
176
|
+
themselves when their tool is not on `PATH`:
|
|
177
|
+
|
|
178
|
+
* `tests/test_sox.py` — `sox` synthesizes a sine wave and dumps it as raw
|
|
179
|
+
int16 (and float32) samples. The samples are published, attached back as a
|
|
180
|
+
read-only `np.memmap`, attached again from a separate process, and stored at
|
|
181
|
+
two sample rates as two variants (`sr16000`, `sr44100`) of one digest.
|
|
182
|
+
* `tests/test_ffmpeg.py` — `ffmpeg` renders its `testsrc` pattern to
|
|
183
|
+
`rawvideo` frames, stored as one `(T, H, W, C)` array. The same checks, with
|
|
184
|
+
the frame rate as the variant (`fr10`, `fr25`), plus a clip over
|
|
185
|
+
`max_array_bytes` being declined whole rather than truncated.
|
|
186
|
+
|
|
187
|
+
Both use their tool's built-in generators, so no media files are checked in.
|
|
188
|
+
Both also exercise per-form dtypes: `dtypes` maps a *form* to the dtype it
|
|
189
|
+
requires (`{"wav16k": "int16"}`), and a float dump is refused under the
|
|
190
|
+
integer form and accepted under the float one. A form's name is only a label —
|
|
191
|
+
the store enforces a dtype only for forms listed in `dtypes`.
|
|
192
|
+
|
|
193
|
+
## Origin
|
|
194
|
+
|
|
195
|
+
Lifted on 2026-10-27 from `seantrue/commonkit` (`main` at `4dbfc7d`, plus the contract and
|
|
196
|
+
vectors from its `rust-crate-plan` branch at `2b6bcf9` and after), with a fresh history. That
|
|
197
|
+
package was extracted from the pixel-cache layer of a private image-corpus
|
|
198
|
+
application, where it ran in production first. The measurements in the module
|
|
199
|
+
docstrings were taken there; the framework-coupled half (the settings wiring
|
|
200
|
+
and the decode entry point) deliberately stayed behind.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
commonkit/__init__.py,sha256=lYuo5uXsaJqQIZ1aLzbKTSzztgoVxByxAtmYRXQ3Gcw,765
|
|
2
|
+
commonkit/cache.py,sha256=zjyRS-RvTCwteaEty8L7SpClcIP2V9tl15TLmIsnBhg,5550
|
|
3
|
+
commonkit/keys.py,sha256=0sA00GXOUVleTvKrXfkgaLDhjFt_hXXUiUUqQ_E7xgE,10239
|
|
4
|
+
commonkit/read.py,sha256=VaP14S0K-nvPkgsIWSupbRHpwoTNqznuZGOT-LyZ7Gw,1964
|
|
5
|
+
commonkit/shared.py,sha256=kaS3duagm0InnFYm907ABXQQFPhT0twI6JZ4qp2XIuQ,9205
|
|
6
|
+
commonkit/store.py,sha256=BFDqTyPi4lGS9XV3uDmlAMHXFBurAbU2qikgRZ5P1mM,44008
|
|
7
|
+
pyuncommonkit-0.1.0.dist-info/licenses/LICENSE,sha256=MQpVKT9wlLIb4SIMkLUdVcCwsdM_rIMZwdhqAFM9PfU,1069
|
|
8
|
+
pyuncommonkit-0.1.0.dist-info/WHEEL,sha256=4OL6Foqnnp3xRY5wMkjgc25_i5YJC6dKsC6LPcjqEoU,80
|
|
9
|
+
pyuncommonkit-0.1.0.dist-info/METADATA,sha256=ZmJWiGwdeUGGOaET_-fpWSZJ7Hs7Euc-3S-TsieB0Ok,10743
|
|
10
|
+
pyuncommonkit-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sean D. True
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|