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: str

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:PendingId) of the expression this layer annotates.

TYPE: str

kind

The layer kind (token-tag, span, relation, tree, graph, tier, or document-tag; an open vocabulary).

TYPE: str

created_at

The layer creation timestamp.

TYPE: datetime

subkind

The layer subkind, when applicable.

TYPE: str or None DEFAULT: None

formalism

The linguistic formalism or annotation standard, when applicable.

TYPE: str or None DEFAULT: None

tokenization_id

For token-aligned layers, the tokenization UUID these annotations align to.

TYPE: str or None DEFAULT: None

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: Anchor or None DEFAULT: None

label

The primary label (POS tag, entity type, relation, etc.).

TYPE: str or None DEFAULT: None

token_index

For token-level annotations, the 0-based token index.

TYPE: int or None DEFAULT: None

confidence

A confidence score scaled 0-1000, validated against the model range.

TYPE: int or None DEFAULT: None

annotation_uuid

An explicit UUID; a fresh one is minted when omitted.

TYPE: Uuid or None DEFAULT: None

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: int

byte_end

The UTF-8 byte end offset (exclusive).

TYPE: int

char_start

The Unicode character start offset (0-indexed, inclusive). Must be supplied together with char_end.

TYPE: int or None DEFAULT: None

char_end

The Unicode character end offset (exclusive). Must be supplied together with char_start.

TYPE: int or None DEFAULT: None

RETURNS DESCRIPTION
Anchor

An anchor whose textSpan property carries the span.

RAISES DESCRIPTION
BuildError

If an offset is negative, a byte or character span is not well ordered, or exactly one of char_start/char_end is supplied.

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: str

token_index

The zero-based token index.

TYPE: int

RETURNS DESCRIPTION
Anchor

An anchor whose tokenRef property carries the reference.

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: int

end_ms

The end offset in milliseconds.

TYPE: int

RETURNS DESCRIPTION
Anchor

An anchor whose temporalSpan property carries the span.

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: int

y

The y coordinate of the top-left corner in pixels.

TYPE: int

width

The box width in pixels (at least one).

TYPE: int

height

The box height in pixels (at least one).

TYPE: int

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: int

box

The bounding box at this time (see :func:bbox).

TYPE: BoundingBox

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: TemporalSpan

keyframes

The keyframe boxes (see :func:keyframe).

TYPE: collections.abc.Sequence of lairs.records.defs.Keyframe

interpolation

The interpolation mode (linear, step, or cubic; an open vocabulary). Defaults to linear.

TYPE: str DEFAULT: 'linear'

RETURNS DESCRIPTION
Anchor

An anchor whose spatioTemporalAnchor property carries the value.

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: Model or PendingId or str

uri_field

The model field that holds the AT-URI, when target is a model.

TYPE: str DEFAULT: 'uri'

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

MAX_BLOB_SIZE = 100 * 1024 * 1024

The 100MB blob size cap honoured before upload.

APPLY_WRITES_CHUNK module-attribute

APPLY_WRITES_CHUNK = 200

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: create, update, or delete.

TYPE: str

collection

The collection NSID the record belongs to.

TYPE: str

rkey

The record key the operation targets.

TYPE: str

uri

The AT-URI the operation targets, when known.

TYPE: str

cid

The content identifier of the record value, when known.

TYPE: (str or None, optional)

value

The record value for create and update operations.

TYPE: JsonValue

WriteResult

Bases: Model

The per-record outcome of a write.

ATTRIBUTE DESCRIPTION
uri

The AT-URI of the record.

TYPE: str

status

The outcome: created, updated, deleted, or failed.

TYPE: str

cid

The content identifier returned by the PDS, when the write succeeded.

TYPE: (str or None, optional)

reason

A human-readable reason for a failed write.

TYPE: (str or None, optional)

PublishPlan

Bases: Model

The minimal write plan to make a PDS match a revision.

ATTRIBUTE DESCRIPTION
repo

The target repository DID.

TYPE: str

revision

The local revision the plan was computed from.

TYPE: str

creates

Records present in the revision but not on the PDS.

TYPE: tuple of WriteOp

updates

Records present in both whose value (CID) differs.

TYPE: tuple of WriteOp

deletes

Records on the PDS but absent from the revision.

TYPE: tuple of WriteOp

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

is_empty() -> bool

Return whether the plan contains no writes.

RETURNS DESCRIPTION
bool

True when there is nothing to create, update, or delete.

WriteClient

WriteClient(
    endpoint: str, repo: str, client: Client | None = None
)

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 https://pds.example).

TYPE: str

repo

The authenticated repository DID; all writes target this repository.

TYPE: str

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: Client or None DEFAULT: None

repo property

repo: str

Return the authenticated repository DID this client writes to.

RETURNS DESCRIPTION
str

The owning repository DID.

close

close() -> None

Close the underlying HTTP client if this client owns it.

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: bytes

mime_type

The MIME type of the blob.

TYPE: str

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: str

value

The record value.

TYPE: Mapping

rkey

An explicit rkey; the PDS assigns a TID when omitted.

TYPE: str or None DEFAULT: None

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: str

rkey

The record key.

TYPE: str

value

The record value.

TYPE: Mapping

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: str

rkey

The record key.

TYPE: str

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: collections.abc.Sequence of WriteOp

RETURNS DESCRIPTION
tuple of WriteResult

One outcome per input write.

collection_of

collection_of(uri: str) -> str

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: str

RETURNS DESCRIPTION
str

The collection NSID, or the empty string when none is present.

content_address

content_address(data: bytes) -> str

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: bytes

RETURNS DESCRIPTION
str

The hex SHA-256 digest of the bytes.

order_writes

order_writes(
    writes: Sequence[WriteOp],
) -> tuple[WriteOp, ...]

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: collections.abc.Sequence of WriteOp

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: str

writes

The create, update, and delete operations to apply.

TYPE: collections.abc.Sequence of WriteOp

endpoint

The base URL of the PDS.

TYPE: str

client

An injected, authenticated HTTP client.

TYPE: Client or None DEFAULT: None

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: Repository

revision

The revision (commit or tag) to publish.

TYPE: str

to

The target repository DID.

TYPE: str

pds_cids

The AT-URI to CID map currently on the PDS.

TYPE: Mapping

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: Repository

revision

The revision (commit or tag) to publish.

TYPE: str

to

The target repository DID.

TYPE: str

endpoint

The base URL of the PDS; required when dry_run is False or when diffing against the live PDS.

TYPE: str or None DEFAULT: None

client

An injected, authenticated HTTP client used for the diff and the writes.

TYPE: Client or None DEFAULT: None

dry_run

If True, compute and return the plan without sending writes.

TYPE: bool DEFAULT: False

changelog

If True, augment the plan with a changelog entry per changed record.

TYPE: bool DEFAULT: False

changelog_base

The base revision for the changelog field diff; defaults to the revision's first parent. Ignored unless changelog is True.

TYPE: str or None DEFAULT: None

RETURNS DESCRIPTION
PublishPlan

The computed plan; its writes are applied when dry_run is False.

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: str

endpoint

The base URL of the account's PDS.

TYPE: str

into

The repository to populate; its working tree is staged in place.

TYPE: Repository

client

An injected HTTP client for the PDS reads; a private one is created when omitted.

TYPE: Client or None DEFAULT: None

RETURNS DESCRIPTION
Repository

The populated repository (the same handle as into).