This release is a pre-release and may not be stable for production use.
labcat
A small test-and-measurement device catalog: what a device is, where it currently is, and the full history of how it got there. Built to solve one specific problem: manually re-typing device IDs, models, and fixture positions into measurement scripts is tedious and an easy place to make a mistake (mounting two devices in the same fixture position without noticing, for instance).
This is a reference/inventory tool, deliberately kept separate from nebula (which handles measurement data provenance). labcat answers "what is this device and where is it"; nebula answers "what produced this data file." A measurement script typically asks labcat for device info, then hands the result to nebula as plain structured data:
dev = labcat.load().get("R4C1W8")
s.write_meta_for(fn, inputs={"device_id": dev.id, "model": dev.attributes["model"],
"position": dev.current_position()})
Data model
One YAML file is the source of truth — small, human-readable, and meant to be git-tracked (unlike a nebula archive, which should not go in git; see that project's README for why the two have opposite storage answers).
Each device has:
attributes— a small freeform dict for things that rarely change and don't follow a predictable pattern: model, manufacturer, hand-written design notes. Direct overwrite, no history tracked.events— a generic, typed, timestamped log. Current state (current position, current status, anything else) is always derived from this log, never stored redundantly, so there's exactly one place that can be wrong.placementis the built-in span-type event (start/end) — this is whatmount/unmount/movewrite.statusis the built-in instant-type event (timestamp/value).- Anything else — calibration runs, ownership changes, whatever comes
up later — is just a new
typestring vialog_event()/labcat log. No schema change required to add a new kind of thing to track.
R4C1W8:
attributes:
model: "v6"
notes: "15-2-15"
events:
- type: placement
position: 6
start: "2026-08-10"
end: "2027-08-14"
- type: status
timestamp: "2027-08-20"
value: "in-progress"
Where the file lives
labcat.load() resolves the catalog path from, in order: an explicit
path, $LABCAT_CATALOG, or ~/.labcat/catalog.yaml. Point it at a file
inside a git repo you sync across machines — that's the whole
multi-machine story; labcat itself has no networking.
Safety
- Position conflicts are a hard refusal by default.
mount()raisesPositionOccupiedif another device already occupies that position, andAlreadyMountedif the device you're mounting is already somewhere else. Passforce=Trueto override either check. This refusal is the actual point of the tool — it's the "centralized check against errors" a hand-maintained.envfile can't give you. - File-locked, reload-then-write mutations. Every mutating call
(
mount,unmount,move,log_event,add_device) takes a local file lock, re-reads the catalog fresh from disk, applies the change, and writes — so two processes on the same machine racing on a write can't silently clobber each other. This does not cover true concurrent edits from two different machines; that's git's job (the YAML is small and diffable specifically so a merge conflict here is something you can read and resolve by eye).
CLI
labcat init [PATH] [--force]
labcat add <device_id> [--model M] [--notes N] [--attr key=value ...]
labcat set <device_id> [key=value ...] [--model M] [--notes N] [--append key=value] [--remove key]
labcat mount <device_id> <position> [--date YYYY-MM-DD] [--force]
labcat unmount (<device_id> | --all) [--date YYYY-MM-DD]
labcat move <device_id> <position> [--date YYYY-MM-DD] [--force]
labcat log <device_id> <type> [key=value ...] [--date YYYY-MM-DD]
labcat status <device_id> <value> [--date YYYY-MM-DD]
labcat show <device_id>
labcat list [--mounted]
labcat whats-in [<position>]
labcat history <device_id> [--type TYPE]
labcat where
Every command accepts a global --catalog PATH, placed right after
labcat and before the subcommand, which overrides $LABCAT_CATALOG for
that one invocation:
labcat --catalog /path/to/other.yaml list
init creates an empty catalog file on disk (refuses to overwrite an
existing one unless --force) — not required, since add/mount/etc.
create the file on first write, but useful when you want something to
commit to git before you've added any devices. It takes an optional
path (labcat init ~/repos/lab-devices/catalog.yaml); when that isn't
the default location, it prints the line to add to your shell startup
file — in your shell's own syntax — to make it the default.
labcat where prints which catalog file is in use and which of the
three sources chose it. Worth knowing about: an unset $LABCAT_CATALOG
and a working catalog aren't a contradiction, they just mean the
built-in default is in play, and where is how you tell.
labcat whats-in with no position lists every occupied position — the
whole current layout. labcat unmount --all tears down every mounted
device in a single write. set edits attributes on a device that already exists — the clearest
entry point for "I want to start tracking a field I didn't capture when
I first added this device." It covers the whole attribute lifecycle:
labcat set R4C1W8 foundry=fab-b # set or overwrite
labcat set R4C1W8 --model v7 # same, for model/notes
labcat set R4C1W8 --append notes="re-bonded"
labcat set R4C1W8 --remove foundry
Combine them freely in one command; they apply as set, then append,
then remove, in a single write, so a bad --remove can't leave a
device half-updated. --append joins onto the existing value with
"; " (--sep changes it) and plain-sets an attribute that isn't
there yet. labcat show displays them. add --update remains the way
to edit while creating-or-updating in one step; set is for a device
you know already exists (and says so loudly if it doesn't). See examples/cli_walkthrough.txt for a full narrated
session covering all three of these.
Python API
import labcat
cat = labcat.load()
cat.add_device("R4C1W8", attributes={"model": "v6", "notes": "15-2-15"})
cat.mount("R4C1W8", position=6)
dev = cat.get("R4C1W8")
dev.current_position() # 6
dev.attributes["model"] # "v6"
dev.history() # full event log, oldest first
Picker for measurement scripts (optional PyQt5 dependency, not imported by default):
from labcat.picker import pick_device
dev = pick_device() # pops a picker of currently-mounted devices
# or, for non-interactive/batch use:
dev = pick_device(non_interactive_id="R4C1W8")
Examples
examples/ has a narrated CLI walkthrough (cli_walkthrough.txt,
covering --catalog, init, and adding new fields to an existing
device) and three runnable Python scripts: a basic device lookup
(lookup_device.py), a reverse lookup by fixture position
(whats_in_position.py), and the pattern for resolving a device at the
start of a measurement script and handing its info to nebula
(measurement_script_pattern.py).
Status
Core catalog model, CLI, and picker scaffold implemented and unit
tested (device lifecycle, position-conflict refusal and force
override, generic event logging, locking against same-machine race
conditions). Not yet tested against a real Qt event loop or a real
multi-machine git sync workflow.
Metadata
Release files for labcat 0.1.0.dev1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| labcat-0.1.0.dev1.tar.gz | 27.9 kB | Without provenance | Details for labcat-0.1.0.dev1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| labcat-0.1.0.dev1-py3-none-any.whl | Python 3 | none | any | Details for labcat-0.1.0.dev1-py3-none-any.whl |
Total release size: 49.3 kB
Release files / labcat-0.1.0.dev1.tar.gz
| Download URL | labcat-0.1.0.dev1.tar.gz |
|---|---|
| Size | 27.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
317a4614c80d272464de7df02bc678a13083f953b288185fd65b3f29c2406957
|
|
BLAKE2b-256 checksum How to use checksums |
7ba3bae64c84f8196dca4a68685095390b4493b7fe9c6e929eff8201bc460f14
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / labcat-0.1.0.dev1-py3-none-any.whl
| Download URL | labcat-0.1.0.dev1-py3-none-any.whl |
|---|---|
| Size | 21.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
46d37cf5d1bc0d705ecfdfb2ddbe8f2bbca657e0f6d9e80d1eb83576176a3a6e
|
|
BLAKE2b-256 checksum How to use checksums |
bdfa466c4c020201c5b21287999b40537c842e2f489d823b253591ca931007ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Security for labcat 0.1.0.dev1
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.