foley.stores#
dol-backed storage for foley: content-addressed bytes + a metadata store.
This module provides the two persistence surfaces the whole façade stands on and the single gate that decides whether a sound is held by value or by reference:
a content-addressed byte store —
Mapping[content_key -> bytes], keyed by the sha256 hex digest of the bytes (dedup + immutability),a metadata store —
Mapping[sound_id -> SoundRecord](JSON on disk),
store_sound()— the by-value vs by-reference gate, driven byLicenseRecord.cache_bytes_ok.
Both stores are plain MutableMapping objects built from dol. Local disk is
the default (dol.Files / dol.JsonFiles); moving to the cloud is a **store
injection** — pass any dol Mapping (e.g. an S3-backed store) for sounds
and an S3/Postgres Mapping for meta — never a change to business logic.
Content keys make local↔cloud copies idempotent and dedup-safe. Foundation does
NOT implement the S3 store (a later dol-plugin concern); it only guarantees
the interface is a plain MutableMapping.
Storage layout follows app-data-lifecycle: data lives under
~/.local/share/foley (overridable via $FOLEY_DATA_DIR), never inside the
package.
- Invariants wired here (see the skill):
- #1
LicenseRecordis the SSOT for the storage mode —store_soundreads record.license.cache_bytes_okto choose by-value vs by-reference.- #2
cache_bytes_ok(TOS/operational) is DISTINCT from redistribute_standalone_ok(copyright): a Freesound CC0 item is legally redistributable yetcache_bytes_ok=False, so it is stored by-reference (URI + provenance only, NO bytes cached).- #3 The meta store is safe by construction —
sound_idvalues become on-disk filenames, and once SOURCE adapters mint external-derived ids (
freesound:123, URLs, arbitrary strings) an unescaped id could carry//../ drive letters / NUL and escape the meta dir or collide.make_meta_store()percent-encodes every id into a single, non-dot filename component (reversibly, so listing still yields the original id), andstore_sound()rejects an empty id before any write. Hex content ids are unaffected (percent-encoding is a no-op on them).
- #1
Module Attributes
Hash algorithm used for content addressing (matches |
|
Suffix given to on-disk metadata files (keys stay the bare |
|
data under |
|
|
|
|
|
|
|
Every paid generation's bytes + the request that produced them (#59). |
|
A filesystem location (path or path-like string) for a local store root. |
Functions
|
Return the content-address key for |
|
Build the content-addressable blob store: |
|
Build the paid-generations cache (local files by default; any |
|
Build the metadata store: |
|
Build the content-credential store: |
|
Build the run-artifact store: |
|
Build a per-session JSON store: |
|
Persist a sound, choosing by-value vs by-reference from |
Classes
|
Where paid generations are kept, so a paid response is never lost or paid twice (#59). |
- foley.stores.DEFAULT_GENERATIONS_DIR = PosixPath('/home/runner/.local/share/foley/generations')#
Every paid generation’s bytes + the request that produced them (#59).
- foley.stores.DEFAULT_PROVENANCE_DIR = PosixPath('/home/runner/.local/share/foley/provenance')#
Mapping[content_id -> credential dict].- Type:
Content-credential sidecars (#9b)
- foley.stores.DEFAULT_RUN_DIR = PosixPath('/home/runner/.local/share/foley/runs')#
Mapping[run_id -> RunManifest dict].- Type:
Reproducible run-manifests (#11)
- foley.stores.DEFAULT_SESSION_DIR = PosixPath('/home/runner/.local/share/foley/sessions')#
sessions/{id}/{candidates,picks,rejects}/.- Type:
Per-session audition state (#12)
- foley.stores.FOLEY_DATA_DIR = PosixPath('/home/runner/.local/share/foley')#
data under
~/.local/share, never in the package). Override with$FOLEY_DATA_DIR.- Type:
Default data root (app-data-lifecycle
- class foley.stores.GenerationsCache(audio, requests)[source]#
Bases:
objectWhere paid generations are kept, so a paid response is never lost or paid twice (#59).
Two
dolmappings:audio—content_key -> bytes: every paid response, written before QC or ingest, so a quarantined or undecodable generation is still retrievable.requests—request digest -> dict: the canonical request (backend, prompt, the native request it resolves to, the model version) mapped to the content key plus the licence and notes it was generated under. An identical request is served from here instead of calling the provider again.
- foley.stores.HASH_ALGO = 'sha256'#
Hash algorithm used for content addressing (matches
SoundRecord.hash_algo).
- foley.stores.META_FILE_SUFFIX = '.json'#
Suffix given to on-disk metadata files (keys stay the bare
sound_id).
- foley.stores.Rootdir#
A filesystem location (path or path-like string) for a local store root.
alias of
str|os.PathLike[str]
- foley.stores.content_key(data, *, algo='sha256')[source]#
Return the content-address key for
data— its hex digest.Using the hash as the key gives free deduplication (identical bytes map to the same key) and immutability (a key always names the exact same bytes).
- foley.stores.make_byte_store(rootdir=PosixPath('/home/runner/.local/share/foley/audio'))[source]#
Build the content-addressable blob store:
Mapping[content_key -> bytes].The local default is
dol.Files(bytes values on disk). For cloud storage, build the equivalent store from anydolMapping (e.g. an S3 store) and pass it directly tostore_sound()instead of calling this factory — thestore_soundgate treatssoundsas an opaqueMutableMapping.- Parameters:
rootdir (
Union[str,PathLike[str]]) – Directory that holds the blobs (created if missing).- Return type:
- Returns:
A
MutableMapping[str, bytes]keyed bycontent_key().
- foley.stores.make_generations_store(rootdir=PosixPath('/home/runner/.local/share/foley/generations'))[source]#
Build the paid-generations cache (local files by default; any
dolpair works).
- foley.stores.make_meta_store(rootdir=PosixPath('/home/runner/.local/share/foley/meta'))[source]#
Build the metadata store:
Mapping[sound_id -> SoundRecord](JSON files).SoundRecordvalues are (de)serialized transparently via theSerializableMixin(to_dict/from_dict); each record is written as a percent-encoded{sound_id}.jsonfile while the store’s keys stay the baresound_id(invariant #3 — the id is escaped at this boundary so an externally-derived id can never escaperootdiror collide via//..).
- foley.stores.make_provenance_store(rootdir=PosixPath('/home/runner/.local/share/foley/provenance'))[source]#
Build the content-credential store:
Mapping[content_id -> dict](JSON files).The by-value sidecar carrier for #9b’s portable “content credential” (a C2PA-shaped assertion dict written next to each generated clip; a
SoundRecord’slicense.c2pa_manifest_refpoints here by content id). Likemake_meta_store()it escapes the id to a safe{enc}.jsonfilename (invariant #3) while exposing barecontent_idkeys; values are plain dicts ((de)serialized natively bydol.JsonFiles). Localdol.JsonFilesby default; swap in anydolMapping to move sidecars to the cloud.
- foley.stores.make_run_store(rootdir=PosixPath('/home/runner/.local/share/foley/runs'))[source]#
Build the run-artifact store:
Mapping[run_id -> RunManifest dict](JSON files).The by-value carrier for #11’s reproducible run-manifests (one per instrumented
find()/generate()/ … ). An exact sibling ofmake_provenance_store(): escapes therun_idto a safe{enc}.jsonfilename (invariant #3) while exposing barerun_idkeys; values are plain dicts ((de)serialized bydol.JsonFiles). Local by default; swap in anydolMapping for the cloud.
- foley.stores.make_session_store(session_id='default', name='picks', *, rootdir=None)[source]#
Build a per-session JSON store:
Mapping[key -> dict]undersessions/{id}/{name}/.The by-value carrier for #12’s audition state — one store per namespace (
candidates/picks/rejects). A sibling ofmake_run_store(): percent-encodes each key to a safe{enc}.jsonfilename (invariant #3) while exposing bare keys; values are plain dicts. Local by default; swap in anydolMapping for the cloud.- Parameters:
- Return type:
- Returns:
A
MutableMapping[str, dict]keyed by the bare key.
- foley.stores.store_sound(record, data=None, *, sounds, meta, cache_bytes_ok=None)[source]#
Persist a sound, choosing by-value vs by-reference from
cache_bytes_ok.The choice is driven by the sound’s own license (invariant #1): unless
cache_bytes_okis passed explicitly, it is read fromrecord.license.cache_bytes_ok. A sound whose bytes may NOT be cached (e.g. Freesound CC0, whose TOS forbids caching even though the file is legally redistributable — invariant #2) is stored by reference: no bytes are written, only its fetchableuriplus provenance.- Parameters:
record (
SoundRecord) – TheSoundRecordto persist; its nestedlicenseis the SSOT for the storage mode. Mutated in place with the resolvedstorage_mode/uri/content_sha256and written intometa.data (
Optional[bytes]) – The canonical archive bytes (FLAC). Required for by-value storage; for by-reference it is optional — if given, its hash is recorded incontent_sha256for provenance but the bytes are NOT stored.sounds (
MutableMapping[str,bytes]) – The content-addressed byte store (seemake_byte_store()).meta (
MutableMapping[str,SoundRecord]) – The metadata store (seemake_meta_store()).cache_bytes_ok (
Optional[bool]) – Optional override.None(the default) means “userecord.license.cache_bytes_ok”.
- Return type:
- Returns:
The same (mutated)
record, after it has been written intometa.- Raises:
ValueError – If
record.idis empty/non-str(checked first, so a bad id never leaves an orphan blob), or if the sound resolves to by-reference storage butrecord.uriis empty (a by-reference sound must name a fetchable source URL).
Note
The blob is written BEFORE the record so a crash can never leave a metadata reference dangling against a missing blob.