Authoring¶
Builders over the generated models and the write path to a PDS. The builders construct anchors, layers, and cross-references that validate against the lexicons at construction time. The publish module diffs a local Repository revision against a PDS and applies the minimal write set. For the workflow see Guides > Authoring and publishing.
Builders¶
lairs.author.builders ¶
Ergonomic anchor, layer, and record builders over the generated models.
These builders are behaviour over the generated models, not replacements for them; authoring is validated against the lexicons by construction. The builders add three conveniences over raw model construction.
Anchor builders (:func:span, :func:token_ref, :func:temporal,
:func:bbox, :func:spatio_temporal) construct the correct anchor sub-model
and wrap it in an :class:~lairs.records.defs.Anchor object. The Layers anchor
is an object with mutually-exclusive optional reference properties rather than a
formal tagged union, so each builder sets exactly one property.
Layer builders (:class:LayerBuilder) assemble an
:class:~lairs.records.annotation.AnnotationLayer and append annotations
without hand-writing UUIDs.
Cross-reference helpers (:class:PendingId, :func:reference) resolve a
reference target from a published model, a pending local identifier, or an
AT-URI string, so authoring works whether or not the target is published yet.
All builders validate construction-time arguments against the dx.field
metadata carried by the generated models (knownValues, numeric ranges,
required-ness), raising a clear :class:BuildError rather than deferring to a
post-hoc PDS rejection. didactic does not enforce these lexicon constraints at
construction time, so the builders enforce them here.
BuildError ¶
Bases: ValueError
Raised when a builder argument violates a lexicon constraint.
Carries a construction-time message so authoring fails fast with a helpful diagnostic rather than at PDS-write time.
PendingId ¶
Bases: str
A placeholder for a record not yet published to a PDS.
Authoring frequently references a record (an expression, a media record,
an ontology) before it has an AT-URI, because the whole graph is published
as one batch. A :class:PendingId carries a stable local identifier that
the publish path resolves to a real AT-URI once the referenced record
commits. It subclasses :class:str so it can be carried in the same
AT-URI-typed string fields and still be recognised by the publisher.
| ATTRIBUTE | DESCRIPTION |
|---|---|
local_id |
A stable local identifier, unique within an authoring session.
TYPE:
|
LayerBuilder ¶
LayerBuilder(
expression: str,
kind: str,
created_at: datetime,
*,
subkind: str | None = None,
formalism: str | None = None,
tokenization_id: str | None = None,
)
An ergonomic assembler for an annotation layer.
Collects annotations (auto-minting a UUID for each one that lacks it) and
finalises them into a single
:class:~lairs.records.annotation.AnnotationLayer, validating the layer's
kind against the generated model's open vocabulary.
| PARAMETER | DESCRIPTION |
|---|---|
expression
|
The AT-URI (or :class:
TYPE:
|
kind
|
The layer kind (
TYPE:
|
created_at
|
The layer creation timestamp.
TYPE:
|
subkind
|
The layer subkind, when applicable.
TYPE:
|
formalism
|
The linguistic formalism or annotation standard, when applicable.
TYPE:
|
tokenization_id
|
For token-aligned layers, the tokenization UUID these annotations align to.
TYPE:
|
add ¶
add(
*,
anchor: Anchor | None = None,
label: str | None = None,
token_index: int | None = None,
confidence: int | None = None,
annotation_uuid: Uuid | None = None,
) -> Annotation
Append an annotation, minting a UUID if none is supplied.
| PARAMETER | DESCRIPTION |
|---|---|
anchor
|
How this annotation attaches to the source data.
TYPE:
|
label
|
The primary label (POS tag, entity type, relation, etc.).
TYPE:
|
token_index
|
For token-level annotations, the 0-based token index.
TYPE:
|
confidence
|
A confidence score scaled 0-1000, validated against the model range.
TYPE:
|
annotation_uuid
|
An explicit UUID; a fresh one is minted when omitted.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Annotation
|
The appended annotation. |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If the token index is negative or the confidence is out of range. |
build ¶
build() -> AnnotationLayer
Finalise the collected annotations into an annotation layer.
| RETURNS | DESCRIPTION |
|---|---|
AnnotationLayer
|
The assembled annotation layer. |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If no annotations were added. |
new_uuid ¶
new_uuid() -> Uuid
Mint a fresh annotation UUID.
| RETURNS | DESCRIPTION |
|---|---|
Uuid
|
A UUID value model wrapping a random version-4 UUID string. |
span ¶
span(
byte_start: int,
byte_end: int,
*,
char_start: int | None = None,
char_end: int | None = None,
) -> Anchor
Build a byte-span anchor.
Byte offsets are required (they are the lexicon's primary, encoding-stable locator). Character offsets are optional and set together when supplied, so a character-indexed span can be authored without bypassing the builder.
| PARAMETER | DESCRIPTION |
|---|---|
byte_start
|
The UTF-8 byte start offset (0-indexed, inclusive).
TYPE:
|
byte_end
|
The UTF-8 byte end offset (exclusive).
TYPE:
|
char_start
|
The Unicode character start offset (0-indexed, inclusive). Must be
supplied together with
TYPE:
|
char_end
|
The Unicode character end offset (exclusive). Must be supplied together
with
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Anchor
|
An anchor whose |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If an offset is negative, a byte or character span is not well ordered,
or exactly one of |
token_ref ¶
token_ref(tokenization_id: str, token_index: int) -> Anchor
Build a token-reference anchor.
| PARAMETER | DESCRIPTION |
|---|---|
tokenization_id
|
The tokenization UUID the index refers into.
TYPE:
|
token_index
|
The zero-based token index.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Anchor
|
An anchor whose |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If the token index is negative or the tokenization id is empty. |
temporal ¶
temporal(start_ms: int, end_ms: int) -> Anchor
Build a temporal-span anchor.
| PARAMETER | DESCRIPTION |
|---|---|
start_ms
|
The start offset in milliseconds.
TYPE:
|
end_ms
|
The end offset in milliseconds.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Anchor
|
An anchor whose |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If a time is negative or the span is not well ordered. |
bbox ¶
bbox(
x: int, y: int, width: int, height: int
) -> BoundingBox
Build a bounding box.
The bounding box is a standalone spatial value: it is used inside a
keyframe (see :func:keyframe) or wherever a model embeds a
:class:~lairs.records.defs.BoundingBox. The generated model uses integer
pixel coordinates with width/height of at least one pixel.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
The x coordinate of the top-left corner in pixels.
TYPE:
|
y
|
The y coordinate of the top-left corner in pixels.
TYPE:
|
width
|
The box width in pixels (at least one).
TYPE:
|
height
|
The box height in pixels (at least one).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
BoundingBox
|
The bounding box value model. |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If the width or height is below the declared minimum. |
keyframe ¶
keyframe(time_ms: int, box: BoundingBox) -> Keyframe
Build a spatio-temporal keyframe.
| PARAMETER | DESCRIPTION |
|---|---|
time_ms
|
The keyframe time in milliseconds.
TYPE:
|
box
|
The bounding box at this time (see :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Keyframe
|
The keyframe value model. |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If the keyframe time is negative. |
spatio_temporal ¶
spatio_temporal(
temporal_span: TemporalSpan,
keyframes: Sequence[Keyframe],
interpolation: str = "linear",
) -> Anchor
Build a spatio-temporal anchor.
| PARAMETER | DESCRIPTION |
|---|---|
temporal_span
|
The temporal span the keyframes range over.
TYPE:
|
keyframes
|
The keyframe boxes (see :func:
TYPE:
|
interpolation
|
The interpolation mode (
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Anchor
|
An anchor whose |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If no keyframes are supplied or the interpolation mode is empty. |
reference ¶
reference(
target: Model | PendingId | str,
*,
uri_field: str = "uri",
) -> str
Resolve a cross-reference target to a reference string.
The target may be a published model carrying its AT-URI in a field, a
:class:PendingId placeholder, or an AT-URI string. A model is consulted
for uri_field and falls back to a uri attribute; if neither is set,
the model is not yet published and the caller should pass a
:class:PendingId instead.
| PARAMETER | DESCRIPTION |
|---|---|
target
|
The reference target.
TYPE:
|
uri_field
|
The model field that holds the AT-URI, when
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
An AT-URI or a pending local id, suitable for an AT-URI-typed field. |
| RAISES | DESCRIPTION |
|---|---|
BuildError
|
If a model target carries no resolvable AT-URI. |
Publish¶
lairs.author.publish ¶
Single and bulk publishing, and the local-VCS-to-PDS workflow.
The write path mirrors the read client style of :mod:lairs.atproto but lives
here because all writes are owned by the authoring component. A
:class:WriteClient wraps com.atproto.repo.uploadBlob /
createRecord / putRecord / deleteRecord / applyWrites over an
injected, authenticated httpx client; OAuth itself is not implemented here
(the session is injected), but the surface is structured for explicit write
scopes and every write is scoped to the one authenticated repository.
The bulk path is applyWrites. Records are ordered by cross-reference
dependency (media before referrers; expressions before segmentations,
annotations, and memberships; ontologies and personas before the layers that
cite them), so a referenced record always commits before its referrer. Writes
are chunked to a PDS batch limit; when a batch call fails it is retried one
record at a time as putRecord upserts at each write's own rkey (so a record
already committed by the partially-applied batch is upserted rather than
duplicated), and reported back as a per-record result set
(created / updated / deleted / failed with reasons).
:func:publish maps a local Repository revision to the minimal applyWrites
plan by diffing the revision against what is already on the PDS (by AT-URI and
CID); dry_run returns the plan without sending it. :func:pull ingests an
account's Layers records into a Repository for a git-like round trip.
MAX_BLOB_SIZE
module-attribute
¶
The 100MB blob size cap honoured before upload.
APPLY_WRITES_CHUNK
module-attribute
¶
The maximum number of writes sent in one applyWrites batch.
WriteError ¶
Bases: RuntimeError
Raised when a write cannot be performed.
Wraps transport and validation failures behind a single error so callers do not have to discriminate transport-specific exceptions.
WriteOp ¶
Bases: Model
A single planned write operation against a repository.
| ATTRIBUTE | DESCRIPTION |
|---|---|
action |
The operation:
TYPE:
|
collection |
The collection NSID the record belongs to.
TYPE:
|
rkey |
The record key the operation targets.
TYPE:
|
uri |
The AT-URI the operation targets, when known.
TYPE:
|
cid |
The content identifier of the record value, when known.
TYPE:
|
value |
The record value for create and update operations.
TYPE:
|
WriteResult ¶
Bases: Model
The per-record outcome of a write.
| ATTRIBUTE | DESCRIPTION |
|---|---|
uri |
The AT-URI of the record.
TYPE:
|
status |
The outcome:
TYPE:
|
cid |
The content identifier returned by the PDS, when the write succeeded.
TYPE:
|
reason |
A human-readable reason for a failed write.
TYPE:
|
PublishPlan ¶
Bases: Model
The minimal write plan to make a PDS match a revision.
| ATTRIBUTE | DESCRIPTION |
|---|---|
repo |
The target repository DID.
TYPE:
|
revision |
The local revision the plan was computed from.
TYPE:
|
creates |
Records present in the revision but not on the PDS.
TYPE:
|
updates |
Records present in both whose value (CID) differs.
TYPE:
|
deletes |
Records on the PDS but absent from the revision.
TYPE:
|
ordered_writes ¶
ordered_writes() -> tuple[WriteOp, ...]
Return every write, ordered for safe application.
Deletes are emitted first in reverse dependency order (referrers before their targets), then creates and updates in forward dependency order (targets before referrers), so the PDS never holds a dangling reference during application.
| RETURNS | DESCRIPTION |
|---|---|
tuple of WriteOp
|
The full write set in application order. |
is_empty ¶
Return whether the plan contains no writes.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
WriteClient ¶
An XRPC client for writes to the authenticated user's own repository.
The client mirrors the read-client style of :mod:lairs.atproto but only
ever targets the one authenticated repository. OAuth is not implemented
here: an authenticated httpx client (carrying the session's bearer
token and write scopes) is injected. Every write call passes the owning
repository DID so the safety scope is explicit at the call site.
| PARAMETER | DESCRIPTION |
|---|---|
endpoint
|
The base URL of the PDS (for example
TYPE:
|
repo
|
The authenticated repository DID; all writes target this repository.
TYPE:
|
client
|
An injected, authenticated HTTP client. When omitted, a private, unauthenticated client is created (useful only against a mock transport); real writes require an injected authenticated client.
TYPE:
|
repo
property
¶
Return the authenticated repository DID this client writes to.
| RETURNS | DESCRIPTION |
|---|---|
str
|
The owning repository DID. |
upload_blob ¶
upload_blob(data: bytes, mime_type: str) -> JsonValue
Upload blob bytes and return the PDS blob reference.
Honors the 100MB size cap and content-addresses the upload so the same bytes uploaded twice in a session reuse the first blob reference, making re-publishing idempotent. The returned value is the raw ATProto blob reference object the PDS emits, ready to embed in a record value.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
The blob bytes to upload.
TYPE:
|
mime_type
|
The MIME type of the blob.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
JsonValue
|
The blob reference object returned by the PDS. |
| RAISES | DESCRIPTION |
|---|---|
WriteError
|
If the blob exceeds the size cap or the upload fails. |
create_record ¶
create_record(
collection: str,
value: Mapping[str, JsonValue],
*,
rkey: str | None = None,
) -> WriteResult
Create a single record, optionally at an explicit rkey.
| PARAMETER | DESCRIPTION |
|---|---|
collection
|
The collection NSID.
TYPE:
|
value
|
The record value.
TYPE:
|
rkey
|
An explicit rkey; the PDS assigns a TID when omitted.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
WriteResult
|
The per-record outcome. |
put_record ¶
put_record(
collection: str,
rkey: str,
value: Mapping[str, JsonValue],
) -> WriteResult
Upsert a single record at an explicit rkey.
| PARAMETER | DESCRIPTION |
|---|---|
collection
|
The collection NSID.
TYPE:
|
rkey
|
The record key.
TYPE:
|
value
|
The record value.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
WriteResult
|
The per-record outcome. |
delete_record ¶
delete_record(collection: str, rkey: str) -> WriteResult
Delete a single record.
| PARAMETER | DESCRIPTION |
|---|---|
collection
|
The collection NSID.
TYPE:
|
rkey
|
The record key.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
WriteResult
|
The per-record outcome. |
apply_writes ¶
apply_writes(
writes: Sequence[WriteOp],
) -> tuple[WriteResult, ...]
Apply writes in dependency-ordered, retried, chunked batches.
Writes are ordered so a referenced record precedes its referrer, sent
in chunks within the PDS batch limit, and retried once per chunk via
idempotent putRecord upserts at each write's own rkey when the batch
call fails, so a partial failure can be safely resumed. Each input
write yields exactly one :class:WriteResult.
| PARAMETER | DESCRIPTION |
|---|---|
writes
|
The writes to apply.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple of WriteResult
|
One outcome per input write. |
collection_of ¶
Return the collection NSID embedded in an AT-URI.
An AT-URI has the form at://<authority>/<collection>/<rkey>; the
collection segment is the lexicon NSID.
| PARAMETER | DESCRIPTION |
|---|---|
uri
|
The AT-URI to parse.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The collection NSID, or the empty string when none is present. |
content_address ¶
Return a stable content address for blob bytes.
Used to deduplicate blob uploads within a publish session so re-publishing the same media is idempotent.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
The blob bytes.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The hex SHA-256 digest of the bytes. |
order_writes ¶
Order create/update writes by cross-reference dependency.
Writes are sorted by their collection's dependency tier (lower first) and then by AT-URI for determinism, so a referenced record always precedes its referrer within one ordered batch.
| PARAMETER | DESCRIPTION |
|---|---|
writes
|
The writes to order.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple of WriteOp
|
The writes in dependency order. |
apply_writes ¶
apply_writes(
repo: str,
writes: Sequence[WriteOp],
*,
endpoint: str,
client: Client | None = None,
) -> tuple[WriteResult, ...]
Apply a batch of writes to a repo, ordered by dependency.
| PARAMETER | DESCRIPTION |
|---|---|
repo
|
The authenticated repository DID.
TYPE:
|
writes
|
The create, update, and delete operations to apply.
TYPE:
|
endpoint
|
The base URL of the PDS.
TYPE:
|
client
|
An injected, authenticated HTTP client.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple of WriteResult
|
A per-record result set (created, updated, deleted, or failed). |
plan_publish ¶
plan_publish(
repo: Repository,
revision: str,
*,
to: str,
pds_cids: Mapping[str, str],
) -> PublishPlan
Compute the minimal write plan against a known PDS state.
This is the offline core of :func:publish: given the PDS's current
AT-URI to CID map, it diffs the local revision and emits the create,
update, and delete operations needed to make the PDS match.
| PARAMETER | DESCRIPTION |
|---|---|
repo
|
The local repository holding the revision.
TYPE:
|
revision
|
The revision (commit or tag) to publish.
TYPE:
|
to
|
The target repository DID.
TYPE:
|
pds_cids
|
The AT-URI to CID map currently on the PDS.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PublishPlan
|
The minimal create/update/delete plan. |
publish ¶
publish(
repo: Repository,
revision: str,
*,
to: str,
endpoint: str | None = None,
client: Client | None = None,
dry_run: bool = False,
changelog: bool = False,
changelog_base: str | None = None,
) -> PublishPlan
Publish a Repository revision to a PDS as the minimal write set.
The target revision is diffed against what is already on the PDS (by AT-URI
and CID) and the minimal applyWrites plan is emitted. When dry_run
is True the plan is returned without sending any writes. Otherwise the
plan's writes are applied in dependency order (via
:meth:PublishPlan.ordered_writes) and the computed plan is returned for
inspection.
When changelog is True the plan is augmented with one
pub.layers.changelog.entry per changed record, generated from the
field-level diff between changelog_base (or the revision's first parent)
and revision and versioned monotonically from the most recently published
entry on the PDS. The default changelog=False leaves the plan unchanged.
| PARAMETER | DESCRIPTION |
|---|---|
repo
|
The local repository holding the revision.
TYPE:
|
revision
|
The revision (commit or tag) to publish.
TYPE:
|
to
|
The target repository DID.
TYPE:
|
endpoint
|
The base URL of the PDS; required when
TYPE:
|
client
|
An injected, authenticated HTTP client used for the diff and the writes.
TYPE:
|
dry_run
|
If
TYPE:
|
changelog
|
If
TYPE:
|
changelog_base
|
The base revision for the changelog field diff; defaults to the
revision's first parent. Ignored unless
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PublishPlan
|
The computed plan; its writes are applied when |
| RAISES | DESCRIPTION |
|---|---|
WriteError
|
If a live publish is requested without a PDS endpoint. |
pull ¶
pull(
did: str,
*,
endpoint: str,
into: Repository,
client: Client | None = None,
) -> Repository
Ingest a PDS account's Layers records into a Repository.
Each Layers collection is enumerated over the PDS read client and every record value is decoded against its generated model and staged into the repository under its AT-URI, giving a git-like round trip: an author can pull, branch, modify, diff, and publish back. A record that fails to validate is skipped rather than aborting the pull.
| PARAMETER | DESCRIPTION |
|---|---|
did
|
The account DID to pull from.
TYPE:
|
endpoint
|
The base URL of the account's PDS.
TYPE:
|
into
|
The repository to populate; its working tree is staged in place.
TYPE:
|
client
|
An injected HTTP client for the PDS reads; a private one is created when omitted.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Repository
|
The populated repository (the same handle as |