Support modules¶
The supporting surfaces — the dictionary registry, the AGS type system, and the transport (pack / encrypt) helpers. See Concepts for the ideas behind them.
laterite.registry¶
The AGS group graph projected from the single-source dictionary — group descriptors, the KEY chain, and parent/child links.
registry
¶
laterite.registry — Rust-backed AGS group registry (read-only).
Exposes typed group/heading descriptors, populated from the Rust
crate's OnceLock singleton via PyO3 (no second JSON parse in
Python).
Read-only: dictionary content is established at Rust crate load
time. Runtime extension for AGS4 passthrough groups (groups present
in a file but not in the bundled dictionary) lives in
laterite.dynamic.
HeadingStatus
module-attribute
¶
Heading
dataclass
¶
Heading(
name: str,
status: HeadingStatus,
type: str,
unit: str | None = None,
description: str = "",
)
One heading (column) of an AGS group, as the dictionary defines it.
Frozen because the registry is read-only — a Heading is a fact
about the AGS spec, not mutable state. status keeps the
dictionary's own vocabulary verbatim, including the combined
"KEY+REQUIRED" and the "DEPRECATED" marker; use is_key
rather than an == test so those combined forms are handled.
py_name
property
¶
The heading name lower-cased — the identifier the typed classes and DuckDB columns use, where the dictionary's upper-case form isn't a legal attribute/column name.
is_key
property
¶
Whether this heading is a KEY column. Tests the
+-separated parts of status so the combined
"KEY+REQUIRED" form counts, where a plain == "KEY"
would miss it.
GroupDescriptor
dataclass
¶
GroupDescriptor(
code: str,
contents: str,
parent: str | None,
headings: tuple[Heading, ...],
)
The full spec of one AGS group: its 4-letter code, the
dictionary's prose contents, the parent code that places it
in the PROJ tree (None at the root), and its headings in
dictionary order.
Frozen for the same reason as Heading — this describes the AGS
dictionary, which is fixed at Rust crate load. The table/view
getters give the conventional DuckDB table/view names for the
group.
table
property
¶
The group's physical table name (g_<code> lower-cased)
in a DuckDB store.
view
property
¶
The group's view name (v_<code> lower-cased) in a DuckDB
store — the parent-joined view that complements the raw
table.
get
¶
get(code: str) -> GroupDescriptor | None
Single-group lookup. None for unknown codes (mirrors
GROUPS.get(code)).
dictionary
¶
dictionary(
edition: Edition | None = None,
) -> dict[str, Any]
The bundled STANDARD dictionary for one AGS edition — the per-edition view of the official dictionary.
Where the module-level :data:GROUPS is the union registry across all
editions (the typed-graph / DDL model, and the default), this is a single
edition's standard dictionary: canonical group + heading names, descriptions,
UNIT/TYPE, and status. It's the same content the browser's dictionary
reference and Node's registry.dictionary() render, built from one shared
Rust builder (dict::dictionary_dto).
The shape is {ags_edition, groups: [{code, contents, parent, headings:
[{name, status, type, unit?, description}]}]} — groups sorted by code, each
group's headings in canonical dictionary order.
Args:
edition: One of "4.0.3" | "4.0.4" | "4.1" | "4.1.1" | "4.2". None
(or "auto") uses the fallback edition.
Returns:
The dictionary snapshot as a nested dict.
Raises:
ValueError: If edition is not a recognised edition.
ancestor_chain
¶
Parent chain from code to root: [code, parent, ..., root].
Raises ValueError if code isn't in the registry (so callers
can distinguish "no parent" — root groups return [code] — from
"unknown code"). Computed in Rust against the static registry.
inherited_key_names
¶
The KEY heading names this group shares with its direct parent — the keys it inherits rather than declaring fresh.
The intersection of this group's KEY headings with its immediate parent's (only the direct parent, not the whole ancestor chain), computed in Rust against the static registry. Rust returns a sorted list for determinism; we hand it back as a set since order carries no meaning to callers. Empty when the group is a root or its parent is unknown.
child_groups
¶
child_groups(parent_code: str) -> list[GroupDescriptor]
Every direct child group of parent_code, in alphabetical
order.
options: show_root_heading: false members_order: source
laterite.ags_types¶
The AGS type system — canonical types and value casting.
ags_types
¶
laterite.ags_types — Rust-backed AGS4 type system.
Exposes parse_value / canonical_type / display_hint semantics
backed by the Rust engine in rust-packages/laterite-ags4-core/src/ags_types.rs.
The CanonicalType StrEnum lives Python-side so existing
canonical_type(x) is CanonicalType.DECIMAL checks keep their
identity. Rust returns the lowercase label; we coerce back to the
enum.
parse_value returns native Python types — int / float / bool /
datetime.datetime / datetime.date / datetime.time / str / None —
matching the pure-Python implementation. Returns the trimmed string
for unknown AGS codes (passthrough), matching the prior behaviour.
CanonicalType
¶
Bases: StrEnum
Cross-system target types. Values match the lowercase labels the
Rust side returns from canonical_type so the enum doubles as the
string interchange representation (e.g. for _spec_headings).
canonical_type
¶
canonical_type(ags_type: str) -> CanonicalType
AGS spec type code → canonical category.
Raises ValueError for unknown codes — the AGS4 codec coerces
passthrough groups' heading types to 'X' before calling, so
in practice this never raises on dictionary-resident headings.
display_hint
¶
Presentation hint for a numeric AGS type, or None.
'2DP' → '%.2f', '3SF' → '%.3g', '1SCI' →
'%.1e'. String / datetime / bool types return None.
parse_value
¶
Parse an AGS4-shaped raw string into its canonical Python type.
Permissive by design — this is the single source of truth for AGS4
ingest (the Rust base path), so it never raises on bad data: an
unparseable value, or an empty / whitespace-only string, comes back
as None rather than an error. Anything that isn't already a
string is stringified first, mirroring the original pure-Python
implementation's isinstance(s, str) guards.
Args:
raw: The raw AGS4 field value. None (explicit null) returns
None untouched; non-string input is coerced with
str() before parsing.
ags_type: The AGS spec type code (e.g. '2DP', 'DT',
'YN', 'ID') that selects the canonical category.
Returns:
The value in its canonical Python type, keyed off the type code:
int (INTEGER), float (DECIMAL), bool (BOOL),
datetime.datetime (DATETIME), datetime.date (DATE),
datetime.time (TIME), and str (STRING / ENUM). An
unknown AGS code passes the trimmed string through unchanged.
None for a null, empty, whitespace-only, or unparseable
value.
options: show_root_heading: false members_order: source
laterite.transport¶
Pack / unpack and lock / unlock for content-agnostic transport (zstd + optional age encryption).
transport
¶
laterite.transport — zstd compression + age passphrase encryption for any file.
The four operations are content-agnostic: they read a file's bytes,
(de)compress and optionally (de)encrypt them, and write the result. They work on
any file — an AGS4 .ags transfer file, anything — the Rust core just runs
zstd/age over raw bytes.
Two pairs of operations:
pack/unpack— zstd only. The default level 9 is the empirical sweet spot on AGS data (~10% ratio in a few seconds; higher levels buy minutes not bytes).-
lock/unlock— zstd + age passphrase encryption. The age envelope is interoperable withpyrageand thelat lockbinary subcommand — both link the same Rustagecrate.from laterite import transport transport.pack("delivery.ags") PosixPath('delivery.ags.zst') transport.lock("delivery.ags", password="hunter2") PosixPath('delivery.ags.zst.age')
pack
¶
pack(
src: str | PathLike[str],
*,
level: int = 9,
dest: str | PathLike[str] | None = None,
) -> Path
Compress any file to <src>.zst for transport (zstd only).
Works on any file — .ags, anything. level is the zstd
level (1=fastest, 22=highest ratio); default 9 is the sweet spot on AGS data.
dest overrides the default <src>.zst output path.
Args:
src: Path to the file to compress (str or os.PathLike). An
Ags4File is rejected early — call .save(path) first.
level: zstd compression level, 1 (fastest) to 22 (highest ratio).
Defaults to 9.
dest: Output path. Defaults to <src>.zst (original suffix preserved,
.zst appended).
Returns:
The Path written.
Raises:
TypeError: If src is not a path-like (e.g. an Ags4File).
RuntimeError: If the underlying zstd operation fails.
unpack
¶
Decompress a .zst produced by pack back to the original file.
By default the output strips the .zst suffix; pass dest to override.
Args:
src: Path to the .zst file to decompress.
dest: Output path. Defaults to src with its .zst suffix stripped
(or <src>.unpacked if it has no .zst suffix).
Returns:
The Path written.
Raises:
TypeError: If src is not a path-like.
RuntimeError: If the input is not valid zstd or the operation fails.
lock
¶
lock(
src: str | PathLike[str],
*,
password: str,
level: int = 9,
log_n: int | None = None,
dest: str | PathLike[str] | None = None,
) -> Path
Compress + age-passphrase-encrypt any file to <src>.zst.age.
Zstd first (low-entropy data compresses well), then age (scrypt +
ChaCha20-Poly1305). The envelope is interoperable with lat lock and
pyrage — both use the same Rust age crate. password is required (no
agent-default path). level is the zstd level (default 9); dest overrides
the default <src>.zst.age output path.
Args:
src: Path to the file to compress and encrypt.
password: The age passphrase. Required — there is no agent-key path.
level: zstd compression level, 1 (fastest) to 22 (highest ratio).
Defaults to 9.
log_n: scrypt work factor (log2(N)) for the age passphrase KDF.
None uses the pinned default (18 — age's standard tier, openable
everywhere). A lower value is faster but weaker; must be 1..=20
(>20 produces a file the browser age decoder refuses).
dest: Output path. Defaults to <src>.zst.age (flagging both the
compression and encryption layers).
Returns:
The Path written.
Raises:
TypeError: If src is not a path-like.
RuntimeError: If the underlying zstd or age operation fails, or log_n
is outside 1..=20.
unlock
¶
unlock(
src: str | PathLike[str],
*,
password: str,
dest: str | PathLike[str] | None = None,
) -> Path
Decrypt + decompress a .zst.age produced by lock back to the original.
Wrong passphrase or non-passphrase envelopes raise RuntimeError. By default
the output strips .age then .zst.
Args:
src: Path to the .zst.age file to decrypt and decompress.
password: The age passphrase used by lock.
dest: Output path. Defaults to src with .age then .zst
stripped (or <src>.unlocked if neither suffix is present).
Returns:
The Path written.
Raises:
TypeError: If src is not a path-like.
RuntimeError: If the passphrase is wrong, the envelope is not a
passphrase envelope, or decompression fails.
pack_bytes
¶
Compress bytes → bytes in memory (zstd only) — no filesystem.
The in-memory form of :func:pack. Output is a standard zstd frame, so it
opens with :func:unpack_bytes, :func:unpack (write it to a file first),
or stock zstd.
Args: data: The bytes to compress. level: zstd level, 1 (fastest) to 22 (highest ratio). Defaults to 9.
Returns: The compressed bytes.
Raises: RuntimeError: If the underlying zstd operation fails.
unpack_bytes
¶
Decompress zstd bytes → bytes in memory — the in-memory form of :func:unpack.
Args:
data: The zstd-compressed bytes (e.g. from :func:pack_bytes).
Returns: The decompressed bytes.
Raises: RuntimeError: If the input is not valid zstd or the operation fails.
lock_bytes
¶
Compress + age-passphrase-encrypt bytes → bytes in memory — no plaintext on disk.
The in-memory form of :func:lock (zstd, then age scrypt +
ChaCha20-Poly1305). Ideal for sealing sensitive data you hold in memory —
e.g. a fixed Ags4File's .bytes — without ever writing the plaintext
out. The .zst.age envelope matches :func:lock's, so the result opens
with :func:unlock_bytes, :func:unlock (write it out first), pyrage,
or the browser, given the passphrase.
Args:
data: The bytes to compress and encrypt.
password: The age passphrase. Required — there is no agent-key path.
level: zstd level, 1 (fastest) to 22 (highest ratio). Defaults to 9.
log_n: scrypt work factor (log2(N)). None uses the pinned default
(18); a lower value is faster but weaker; must be 1..=20.
Returns: The sealed bytes.
Raises:
RuntimeError: If the underlying zstd or age operation fails, or log_n
is outside 1..=20.
unlock_bytes
¶
Decrypt + decompress .zst.age bytes → bytes — the in-memory form of :func:unlock.
Args:
data: The sealed bytes (e.g. from :func:lock_bytes).
password: The age passphrase used to seal them.
Returns: The original bytes.
Raises: RuntimeError: If the passphrase is wrong, the envelope is not a passphrase envelope, or decompression fails.
options: show_root_heading: false members_order: source