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.
@@ -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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.5
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.