vcti-properties
Sparse, scoped, multi-level property store with pluggable key mapping.
Overview
vcti-properties stores sparse named properties attached to entities or
collections, with optional layered resolution — a per-entity value that
falls back to a shared default. It spans the whole range from fixed metadata
(units, components, a creator — authoritative, rarely overridden) to overridable
properties (a plot's color, opacity, visibility — set as chart-wide defaults
and overridden per series). Both are just values addressed by a structured key.
Rather than a tangle of nested dictionaries, every property lives in one sparse store keyed by a flat string, and three concerns are kept cleanly separated:
- Authority — application-defined (system) properties vs. user-supplied ones, held in separate namespaces so they never collide. System names are drawn from a controlled vocabulary; user names are free-form.
- Locus — what a property is bound to: a specific entity (one column, element, node), or the whole collection.
- Resolution — a per-entity lookup can fall back to a shared default, so one value stands in for every entity that has not set its own.
Nothing here is dataset-specific: any set of entities — form fields, scene
objects, config sections — each carrying its own properties over shared
defaults, with system settings kept apart from user settings, fits the same
shape. The levels you declare encode the (authority, locus, name) parts
into one flat string key, so the store stays sparse (unset combinations cost
nothing) and lookups stay O(1). Pure standard library, zero runtime
dependencies.
Why not nested dicts or ChainMap?
Nested dictionaries — one level per authority, per entity, per name — mean a lot
of structure for little data, plus hand-rolled lookup repeated at every call
site. ChainMap gives you fallthrough for free, but you would need one dict per
(authority × locus) combination and must reassemble the chain per entity — the
layer count explodes with the number of entities. This package keeps the
unbounded axis (locus) in a flat key over one sparse dict, and reserves layered
fallthrough for the small, bounded axis where it belongs (see
Design & Concepts).
Installation
pip install vcti-properties
In pyproject.toml dependencies
dependencies = [
"vcti-properties>=1.0.0",
]
Quick Start
A store is defined by an ordered list of levels passed straight to
PropertyStore (it builds the key encoder for you). Each level is one part of
the key; the last level is the property name, and the value is stored
separately against the assembled key.
Here's a chart whose series are styled with chart-wide defaults that any series can override — exactly the overridable kind of value that is a property (unlike fixed metadata such as units or a creator):
from enum import StrEnum
from vcti.properties import PropertyStore, Level
class Style(StrEnum): # the plot properties we recognise
COLOR = "color"
OPACITY = "opacity"
VISIBLE = "visible"
chart = PropertyStore(
Level("series", sentinel="@"), # a specific series, or chart-wide (@)
Level("style", vocabulary=Style), # the property name
)
# Chart-wide defaults — apply to every series unless overridden:
chart.set(style=Style.COLOR, value="steelblue") # -> "@.color"
chart.set(style=Style.OPACITY, value=1.0)
chart.set(style=Style.VISIBLE, value=True)
# Per-series overrides:
chart.set(series="revenue", style=Style.COLOR, value="crimson")
chart.set(series="forecast", style=Style.OPACITY, value=0.4)
# resolve() = the series' own value, else the chart-wide default:
chart.resolve(series="revenue", style=Style.COLOR) # "crimson" (overridden)
chart.resolve(series="forecast", style=Style.COLOR) # "steelblue" (chart-wide default)
chart.resolve(series="forecast", style=Style.OPACITY) # 0.4 (overridden)
chart.get(series="forecast", style=Style.COLOR) # None (exact read, no fallback)
Add a system/user scope as one more level when the application's own defaults
must stay separate from a user's edits — system.* and user.* are distinct
namespaces that never collide (each resolves within itself):
from vcti.properties import PropertyStore, KeyScope, Level
chart = PropertyStore(
Level("scope", vocabulary=KeyScope), # application theme (system) vs. user edits (user)
Level("series", sentinel="@"),
Level("name"),
)
chart.set(scope=KeyScope.SYSTEM, name="color", value="steelblue") # "system.@.color"
chart.set(scope=KeyScope.USER, series="revenue", name="color", value="crimson") # "user.revenue.color"
With no levels, the store uses dot-joined paths (DefaultKeyMapper):
from vcti.properties import PropertyStore
store = PropertyStore() # or PropertyStore(separator="/")
store.set("user", "profile", value={"name": "Alice"}) # key "user.profile"
store.get("user", "profile") # {"name": "Alice"}
Key mappers
Passing levels to PropertyStore builds a KeyMapper — the codec that encodes
the (authority, locus, name) parts into a flat key and back. It's pluggable, so
one store serves every layout:
| Mapper | What it does |
|---|---|
DefaultKeyMapper |
Unstructured positional parts joined by a separator (a.b.c) — the default when no levels are given |
LeveledKeyMapper |
Structured named levels — built from the Levels you pass to PropertyStore |
Scenario-specific layouts (a system/user scoped key, a single-level config key)
are configurations of levels, not separate classes. For a bespoke scheme, pass
key_mapper= any object implementing parts_to_key(...) and key_to_parts(key).
See Extending.
Dependencies
None. Standard library only.
Metadata
Release files for vcti-properties 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vcti_properties-1.0.0.tar.gz | 16.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vcti_properties-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.2 kB
Release files / vcti_properties-1.0.0.tar.gz
| Download URL | vcti_properties-1.0.0.tar.gz |
|---|---|
| Size | 16.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c4bc2f5bcd7fc7fa101605ba0964d30c8495dd86fe2611f7a254cf3cfbfcd613
|
|
BLAKE2b-256 checksum How to use checksums |
a5b8eed6a526a9fe0da85f3b1a9f5023fd02cf674a6cbe749db50e3c4cb27593
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 4, 2026.
Transparency logRelease files / vcti_properties-1.0.0-py3-none-any.whl
| Download URL | vcti_properties-1.0.0-py3-none-any.whl |
|---|---|
| Size | 12.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a60f394c56896a78bb5bf78d55069f8cc0b30752adcc3b4c4069bf4c3e45a0ab
|
|
BLAKE2b-256 checksum How to use checksums |
46bcc153849464cfb570ef79cf194bd045619e58ef694a19a370f41ace6faa1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 4, 2026.
Transparency log