Python cheatsheet¶
The whole Python surface is three handles plus two result objects. Read one
file, then chain — every method below hangs off what read() gives you.
# what this shows: the lazy single-result AgsQuery builder + its four terminals.
import laterite
import pandas as pd
import polars as pl
from duckdb import DuckDBPyRelation
# Build a query lazily: nothing runs until a terminal is called.
q = (
laterite.read("examples/sample_site.ags")
.query("SELECT * FROM LOCA")
.filter("LOCA_GL > 28")
.select("LOCA_ID", "LOCA_TYPE", "LOCA_GL")
)
# Four terminals materialise the same lazy plan:
frame = q.frame() # handle's default backend (polars)
pl_df = q.to_polars() # always polars
pd_df = q.to_pandas() # always pandas
rel = q.relation() # the lazy DuckDBPyRelation (not yet materialised)
print(pl_df)
# Projection is honoured exactly as declared.
assert frame.columns == ["LOCA_ID", "LOCA_TYPE", "LOCA_GL"]
assert pl_df.columns == ["LOCA_ID", "LOCA_TYPE", "LOCA_GL"]
# Each terminal returns its expected type.
assert isinstance(frame, pl.DataFrame)
assert isinstance(pl_df, pl.DataFrame)
assert isinstance(pd_df, pd.DataFrame)
assert isinstance(rel, DuckDBPyRelation)
shape: (7, 3)
┌─────────┬───────────┬─────────┐
│ LOCA_ID ┆ LOCA_TYPE ┆ LOCA_GL │
│ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 │
╞═════════╪═══════════╪═════════╡
│ BH02 ┆ RC ┆ 32.49 │
│ BH03 ┆ RC ┆ 28.54 │
│ BH04 ┆ RC ┆ 29.04 │
│ BH05 ┆ RC ┆ 31.62 │
│ BH07 ┆ RC ┆ 31.33 │
│ BH08 ┆ CP ┆ 28.67 │
│ BH09 ┆ CP ┆ 30.98 │
└─────────┴───────────┴─────────┘
A lazy AgsQuery (above), built up and then materialised. The tables that
follow are the full lookup — what each method returns, and whether it keeps the
chain alive.
Ags4File — the read handle¶
laterite.read(...) returns this. Methods that return self (or a new
Ags4File) keep you on the handle; the rest hand you a query, a relation, or a
materialised value.
| Method / attr | Returns | Chainable? | Example |
|---|---|---|---|
read(path) / read(text=) / read(data=bytes) |
Ags4File |
start of chain | ex01 |
.validate() |
Ags4File (same handle) |
yes — runs the rule engine, parks the Report on .report |
ex02 |
.fix() |
Ags4File (new) |
yes — applies safe auto-fixes, original untouched | ex15 |
.at(code, [ids]) |
AgsQuery |
→ query (fan-out from a parent group) | ex03 |
.query(sql) |
AgsQuery (lazy) |
→ query (build with .filter / .select) |
ex05 |
.sql(sql) |
DuckDBPyRelation |
→ relation (terminal: .pl() / .df() / .arrow()) |
ex06 |
.pipe(fn, *args) |
whatever fn returns |
depends on fn |
ex07 |
["CODE"] / .table(code) |
polars.DataFrame |
no — one group, materialised | ex01 |
.groups |
list[str] |
no — group codes present | ex01 |
.report |
Report | None |
no — set by .validate() |
ex02 |
.text |
str |
no — byte-faithful AGS4 source | ex08 |
.bytes |
bytes |
no — raw encoded source | ex08 |
.certify(path) |
writes an .ags.idx sidecar |
no — mints the validity cert | ex08 |
.save(path) |
writes the file | no — terminal | ex08 |
Born typed
["CODE"] and the query terminals hand back polars frames where the column
dtype is the AGS data type — LOCA_GL arrives as f64, not a string to
re-parse. See Born typed.
AgsQuery — the lazy query handle¶
.at(...) and .query(...) return this. Builder methods are immutable: each
returns a new AgsQuery, so the plan only runs when you call a terminal.
| Method | Returns | Chainable? |
|---|---|---|
.filter(expr) |
AgsQuery (new) |
yes — builder |
.select(*cols) |
AgsQuery (new) |
yes — builder |
.frame() |
polars.DataFrame |
terminal — single result, handle's backend |
.to_polars() |
polars.DataFrame |
terminal — single result |
.to_pandas() |
pandas.DataFrame |
terminal — single result |
.relation() |
DuckDBPyRelation |
terminal — single result, still lazy |
.frames() |
dict[str, DataFrame] |
terminal — fan-out (e.g. ["SAMP"]) |
.groups |
list[str] |
fan-out — group codes in the result |
Single-result and fan-out are mutually exclusive
An AgsQuery is either a single-result query (use .frame / .to_polars
/ .to_pandas / .relation) or a fan-out (use .frames / .groups) —
.at(code, [ids]) produces the fan-out shape, .query(sql) the
single-result shape. Calling the wrong family raises. And every builder call
(.filter, .select) returns a new immutable AgsQuery; the original
is unchanged, so you can branch a plan safely. See
ex04 and ex05.
Report — the validation verdict¶
Parked on Ags4File.report after .validate().
| Method / attr | Returns | Notes |
|---|---|---|
.is_valid |
bool |
True when zero findings |
.count |
int |
number of findings |
.dict_version |
str |
AGS edition the rules resolved to |
.resolution |
str |
how that edition was chosen |
.findings |
list[Finding] |
rule · line · group · description |
.to_json() |
str |
JSON, same shape as lat validate --json |
→ worked example: Validate (ex02).
BuildResult — the producer result¶
build_ags4(frames | typed PROJ graph) returns this. Same .text / .bytes /
.save door as Ags4File, plus what the build did.
| Method / attr | Returns | Notes |
|---|---|---|
.text |
str |
the emitted AGS4 |
.bytes |
bytes |
the emitted AGS4, encoded |
.findings |
list[Finding] |
validation findings on the built file |
.fixes_applied |
list[str] |
what the emitter normalised |
.save(path) |
writes the file | terminal |
→ worked examples: Produce — from frames (ex09a) or from a typed PROJ graph (ex09b).
See also: the CLI reference for lat, and the
Cookbook for end-to-end recipes.