Skip to main content

Illustrated aerial view of farmland: square fields and center-pivot circles on a shared road grid

pycommonkit

Named shared-memory segments for numpy arrays. One process decodes and publishes; every other process attaches the same physical pages.

Installed as pyuncommonkit, imported as commonkit (the PyPI name commonkit belongs to an unrelated project, and PyPI refused pycommonkit as too similar to it). A Rust crate, commonkit, implementing the same on-disk contract is in this repository under rust/ (docs/rust-crate-plan.md): keys, segments, the store and the reaper are done, and a cross-language conformance suite runs both on one tree.

The name is for Fortran's named COMMON block. COMMON /GRID/ X, Y in one subroutine and the same name in another, compiled separately, give both the same storage: the linker joins them by the name alone, and no unit owns the block. The name is the whole interface. A commonkit key is the same idea: any process that can spell the key reaches the same bytes, without knowing who wrote them.

Where the analogy stops is where commonkit had to be stricter:

  • Across processes, not within one program. A COMMON block is shared by the program units of one executable; a segment is shared by separate processes.
  • The layout travels with the data. Each Fortran unit declares its own view of a block, and a unit that declares it differently silently reads other variables' bits. A segment carries its dtype, shape and order in its .npy header, so every reader sees the same layout, and publishing a different array under an existing key is refused loudly (KeyCollision).
  • Written once, then read-only. Any unit may assign into a COMMON block at any time. A segment is written once, whole, and readers map it read-only.
  • It may not be there. A COMMON block exists for the whole run. A segment exists only until the reaper needs the space, which is why every caller must be able to recompute it (below).

The contract

docs/contract.md (version 1) is the authority on the on-disk format: keys, layout, the .npy segment, publishing, the write-once rules, touch, the reaper, policy and counters. Python is the reference implementation and conformance/vectors/ decides any disagreement (conformance/generate.py regenerates the fixtures byte for byte). Both the Python package and the Rust crate pass every vector.

Status

See docs/STATUS.md. Nothing is published to PyPI or crates.io yet.

What it is

  • SharedArrayStore — publish/attach .npy segments under a root directory, with admission gates, a TTL reaper and a byte budget.
  • KeyScheme — the addressing vocabulary for one domain. A scheme fixes the domain, a version, the legal forms and the legal variants, and turns (digest, variant, form) into both a key and a relative path.
  • shared_store() — the ONE store per host that every user shares. Its byte budget, TTL, free-space floor, fsync and republish verification come only from the tree's .policy.json (which wins), then COMMONKIT_STORE_* environment variables, then the defaults (macOS: ~/Library/Caches/commonkit/store, 2 GB, 900 s, 8 GB free), because the reaping process applies its own policy to the whole tree; the root comes from the environment or the default. A caller chooses only mode and the smallest array worth storing.
  • TieredPixelCache — an in-process L1 memo in front of a store, so a loop that looks up the same digest repeatedly pays one attach rather than one per call.

A cache, not a store of record

A missing segment is never an error, only a slower path. The store promises that a key which is present names the right bytes. It never promises that a key is present. So every caller must be able to compute the value again when attach returns None, and must treat a publish that returns False as normal.

A segment can be absent at any moment, for reasons the caller doesn't control:

  • Its publish was declined. Under pressure the store refuses rather than blocks: the array was smaller than worth storing or larger than allowed, the volume was below its free-space floor, or the tree was over its byte budget. A decline is how the store survives back-pressure, not a fault.
  • The reaper removed it. Segments are evicted when they expire, least recently used first to get back under the byte budget, and to restore the free-space floor. The reaper doesn't know who might still want a segment, and doesn't ask: no reference counts, no coordination between processes.
  • It was torn. A file shorter than its header promises, after a crash with fsync off (the default) or damage from outside the contract, is a miss.
  • The store is disabled. The mode is off, or the root can't be created or written: every attach misses and every publish declines.
  • The tree is gone. On Linux it lives in /dev/shm and does not survive a reboot; any root can be cleared; another machine has its own tree.

That one rule is what makes the rest cheap. The reaper can unlink freely, publish never blocks, writes need no durability, and a process that fails costs the others only time: the next reader recomputes.

What does raise. Exactly two things, both bugs at the call site rather than conditions of the store: building a key that isn't valid for its scheme (ValueError), and publishing a different array under a key that already holds one (KeyCollision, a ValueError subclass), which means a key scheme or a digest is broken. Everything else (an absent root, a full filesystem, a reaped or torn segment, a hostile .usage.json or .policy.json) returns None or False and ticks a counter. The constructor never raises: an unusable root leaves the store disabled and every method a no-op.

Replacing a value under the same key. A segment is written once: publishing the same array again only refreshes it, and a different array raises KeyCollision. A caller that must replace a value whose key hasn't changed (a recompute after a fix the key doesn't capture) calls remove(key) first, then publishes. remove only unlinks, so a reader still holding the old segment keeps its bytes; there is no in-use check.

A value that can't be recomputed doesn't belong here alone. Keep its durable copy somewhere you own and use the store to share it fast. (A pinned subtree the reaper skips is a possible later addition; it isn't built.)

Callers never need .usage.json. It is the reaper's record of how much the tree holds, read only by the byte-budget gate. When it is missing or stale, usage is unknown, that gate passes, and the free-space floor still applies; the next complete reap writes a fresh one.

Keys

{domain}:{version}:{form}:{digest}:{variant}

and the path it names:

{root}/{form}/{digest[:2]}/{digest}.{variant}.npy

Two-level sharding, so no directory holds a million entries. The variant axis is whatever distinguishes two renderings of the same source: for images it is orientation (o0, o1, oa), for audio it would be sample rate (sr16000). Schemes are registered by domain so parse() and relpath() can dispatch on a key alone, which is what lets one store hold several domains.

Tests

uv run --extra dev pytest -q          # includes the conformance vectors

The suite addresses audio and video rather than images, to show the package is not pixel-specific. Two modules run end to end against real media and skip themselves when their tool is not on PATH:

  • tests/test_sox.py — sox synthesizes a sine wave and dumps it as raw int16 (and float32) samples. The samples are published, attached back as a read-only np.memmap, attached again from a separate process, and stored at two sample rates as two variants (sr16000, sr44100) of one digest.
  • tests/test_ffmpeg.py — ffmpeg renders its testsrc pattern to rawvideo frames, stored as one (T, H, W, C) array. The same checks, with the frame rate as the variant (fr10, fr25), plus a clip over max_array_bytes being declined whole rather than truncated.

Both use their tool's built-in generators, so no media files are checked in. Both also exercise per-form dtypes: dtypes maps a form to the dtype it requires ({"wav16k": "int16"}), and a float dump is refused under the integer form and accepted under the float one. A form's name is only a label — the store enforces a dtype only for forms listed in dtypes.

Origin

Lifted on 2026-10-27 from seantrue/commonkit (main at 4dbfc7d, plus the contract and vectors from its rust-crate-plan branch at 2b6bcf9 and after), with a fresh history. That package was extracted from the pixel-cache layer of a private image-corpus application, where it ran in production first. The measurements in the module docstrings were taken there; the framework-coupled half (the settings wiring and the decode entry point) deliberately stayed behind.

Metadata

Release files for pyuncommonkit 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyuncommonkit 0.1.0
File Size Uploaded Provenance Actions
pyuncommonkit-0.1.0.tar.gz 30.3 kB Without provenance Details for pyuncommonkit-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pyuncommonkit 0.1.0
File Interpreter ABI Platform Actions
pyuncommonkit-0.1.0-py3-none-any.whl Python 3 none any Details for pyuncommonkit-0.1.0-py3-none-any.whl

Total release size: 64.0 kB

Release files / pyuncommonkit-0.1.0.tar.gz

Download URL pyuncommonkit-0.1.0.tar.gz
Size 30.3 kB
Tags Source
SHA-256 checksum
How to use checksums
141ca685405d5f1c01fc41951cfc37337b7804a396b43f779eeb959f1595e33d
BLAKE2b-256 checksum
How to use checksums
2ebc7aa2f8a16af2cc2a9f27fffc0f7b7ee6c04c7591a434e379f1f3739f9e7c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Release files / pyuncommonkit-0.1.0-py3-none-any.whl

Download URL pyuncommonkit-0.1.0-py3-none-any.whl
Size 33.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9d66668413b0ea3f415257329c6122993148ce312997f2d73a57ae940fdd71f3
BLAKE2b-256 checksum
How to use checksums
00dd33ccda62392f9381e5f8fd03e83f41fa68e08a58076bce224d3a34bc4a21
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.0

Security for pyuncommonkit 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page