lacing.artifact#
Content-addressed artifact references.
An Artifact is a generated file (or remote URL) with provenance.
Anything that produces media — a fal.ai call, a ffmpeg compose, a
storyboard PDF render, an audio synthesis — returns an Artifact.
Anything that references an artifact temporally does so via
MediaRef(asset_id=artifact.asset_id, interval=…).
This type lives in lacing (not in falaw or any single producer) because:
It reuses
lacing.Provenanceandlacing.MediaRef. Theasset_idis the content hash thatMediaRefalready documents.Multiple producers (falaw, an, nw, artful, mixing) need to express “I produced a file.” Putting
Artifactin any one of them forces a wrong-direction dependency from the others.The provenance chain (
was_derived_from,was_generated_by) is the same shape for an annotation and an artifact. Reusing it unifies lineage.
Hashing#
asset_id is the SHA-256 hex digest of the artifact’s bytes. SHA-256 is
in the stdlib, deterministic, and compatible with the
MediaRef.asset_id documentation (“BLAKE3 / SHA-256”). Producers may use
BLAKE3 if they prefer; the consumer treats asset_id opaquely.
Examples
>>> from pathlib import Path
>>> from lacing.artifact import Artifact, hash_file
>>> # Create one from a path:
>>> import tempfile
>>> with tempfile.NamedTemporaryFile("wb", suffix=".bin", delete=False) as f:
... _ = f.write(b"hello world")
... p = Path(f.name)
>>> a = Artifact.from_path(p, kind="text", was_generated_by="test:doctest",
... was_attributed_to="user:thor")
>>> a.kind
'text'
>>> a.bytes_size
11
>>> len(a.asset_id) == 64 # SHA-256 hex
True
>>> a.path == p
True
An artifact that was acquired rather than made carries a Rights
record (None means “we made this”, not “unknown”):
>>> from lacing.artifact import Rights
>>> a = Artifact.from_bytes(b"png", kind="image", was_generated_by="fetch:openverse",
... was_attributed_to="user:thor",
... rights=Rights(provider="openverse", license="by-sa", author="Ada"))
>>> a.rights.license
'by-sa'
>>> Artifact.from_bytes(b"x", kind="text", was_generated_by="t",
... was_attributed_to="u").rights is None
True
Round-trip through JSON:
>>> import json
>>> data = a.model_dump_json()
>>> b = Artifact.model_validate_json(data)
>>> b.asset_id == a.asset_id
True
Module Attributes
Coarse mime-class. |
Functions
|
Return the canonical |
|
Return the canonical |
Classes
- class lacing.artifact.Artifact(**data)[source]#
Bases:
BaseModelA content-addressed generated file with provenance.
asset_idis the SHA-256 hex digest of the artifact’s bytes. Two artifacts with the sameasset_idare byte-identical regardless of where they live — so caches keyed onasset_idare safe across machines and re-runs.provenancereuseslacing.Provenanceso the lineage chain (was_derived_from,was_generated_by) is the same for artifacts and annotations. An annotation referencing an artifact does so viaMediaRef(asset_id=artifact.asset_id, …).- classmethod from_bytes(data, *, kind, was_generated_by, was_attributed_to, path=None, url=None, was_derived_from=(), activity='create', generated_at_time=None, duration_s=None, mime=None, cost_usd=None, producer_call_id=None, rights=None)[source]#
Create an Artifact from in-memory bytes.
- Return type:
- classmethod from_path(path, *, kind, was_generated_by, was_attributed_to, was_derived_from=(), activity='create', generated_at_time=None, duration_s=None, mime=None, cost_usd=None, producer_call_id=None, rights=None)[source]#
Create an Artifact from a local file. Hashes the file’s bytes.
- Return type:
- model_config: ClassVar[ConfigDict] = {'extra': 'forbid', 'frozen': True}#
Configuration for the model, should be a dictionary conforming to [
ConfigDict][pydantic.config.ConfigDict].
- to_media_ref(interval)[source]#
Return a
lacing.MediaRefpointing at this artifact.Use this to attach an annotation to a region of the artifact:
MediaRef(asset_id=artifact.asset_id, interval=…).- Return type:
MediaRef
- lacing.artifact.ArtifactKind#
Coarse mime-class. Producers should pick the closest match; consumers should switch on this before falling back to
mime/path.suffix.alias of
Literal[‘image’, ‘video’, ‘audio’, ‘json’, ‘text’, ‘binary’]
- class lacing.artifact.Rights(**data)[source]#
Bases:
BaseModelWho owns the bytes of an acquired artifact, and on what terms.
The fields a Creative-Commons-family licence needs to discharge its attribution duty (title, author, source, licence: “TASL”), named literal-for-literal after the two records the federation already keeps —
illustration.ImageResultandan.ir.assets.AssetSource— so both map onto this one without a rename table (provider,id,license,license_url,attribution,source_page_url,author,author_url,cacheable).Semantics worth the trap they avoid:
``Artifact.rights is None`` means “we made this”, not “unknown”. An acquired artifact whose terms nobody recorded is a
Rightswith onlyproviderset.``license is None`` means UNKNOWN, never unencumbered. Free of obligations is stated, e.g.
license="cc0-1.0".``cacheable is None`` means not stated;
Falseforbids keeping the bytes,Truepermits it.licenseis an SPDX id ("CC-BY-4.0") or a provider’s own code ("by-sa"); lacing records it verbatim and never classifies it (classification lives with the consumer, e.g.an’s licence classes).The record describes this artifact’s own bytes. A render that embeds a third-party image carries no
Rightsof its own; its obligations travel throughprovenance.was_derived_fromand are rolled up by the consumer.rights is Noneon a derivative is not a clearance.
provideris required, so an emptyRights()cannot exist: an artifact either has no record (we made it) or says where it came from. Text fields areNoneor non-blank; a blank string is refused.Caveat: an artifact catalog is keyed by
asset_id(the content hash), so two records for the same bytes are one row and the last write wins. A rights-less record written after an acquired one with identical bytes replaces it, rights included. Writers that re-record bytes they already hold must carry the existingrightsforward.