Verified SQL-to-render data shapes.
A Shape is a frozen, slotted dataclass that declares — co-located with the
row model — theSELECTthat produces it. Decorate the row dataclass with
@shape("SELECT ...")and the declared SQL becomes the single source of truth
for what columns the row carries::
from dataclasses import dataclass
from chirp.data import Database, Shape, shape
@shape("SELECT id, title FROM boards WHERE id = :id")
@dataclass(frozen=True, slots=True)
class BoardView:
id: int
title: str
boards = await Shape.fetch(BoardView, db, id=42)
The compiled SQL and all execution live behind theDatabasefacade
(theShape.fetch / Shape.fetch_one / Shape.streamclassmethods,
which delegate toDatabase) — never in template-adjacent code and never as
a SQL string threaded through a handler kwarg into a template. The author writes
:name placeholders; the driver dialect (? for SQLite, $Nfor
PostgreSQL) is resolved in one place (_bind_params) so parameters are never
concatenated into the SQL text.
Free-threading lifecycle:
- The decorated class is the row type;
@shapeattaches a single frozen``_ShapeMeta`` sidecar (``cls.__chirp_shape__``) once at decoration andnever mutates it thereafter. - The module-level shape registry is shared mutable state. Both writes and
reads are guarded by a ``threading.Lock``; ``shape_registry()`` returns aread-only ``MappingProxyType`` copy. Registration happens at decoration /import time; the registry is treated read-only after app setup.
data.shapes
| Name | Type | Default | Description |
|---|---|---|---|
type
|
|
— | |
qualified_name
|
|
— | |
element_type
|
|
— | |
description
|
|
— | |
source_file
|
|
— | |
line_number
|
|
— | |
is_autodoc
|
|
— | |
autodoc_element
|
|
— | |
_autodoc_template
|
|
— | |
_autodoc_url_path
|
|
— | |
_autodoc_page_type
|
|
— | |
title
|
|
— | |
doc_content_hash
|
|
— |
Symbols on this page
If a string literal or SQL comment begins atsql[i], skip past it.
The single, shared low-level "inert span" skipper consulted by EVERY…
Return the parenthesis depth at indextarget, or Noneif inert.
Walkssql[:target]skipping string literals and SQL comments via
_skip_inert(), counting…
Yield(name, start, end) spans for each :name placeholder in sql.
The single, shared placeholder scanner consumed by both_bind_params() (which rewrites…
Fail loud whensql declares a :__chirp_...placeholder (finding F1).
The__chirp_prefix is reserved for compiler-generated placeholders
(__chirp_k0 batch keys, __chirp_rnwindow…
Explicit child-Shape declaration for the bounded compiler (#167).
Created vianested() and recorded in the field's metadata. The child
clsMUST itself be …
Immutable sidecar describing a@shape-decorated dataclass.
Attached to the class ascls.__chirp_shape__once at decoration and
never mutated (frozen-clean for free-threaded 3.14t).
Declare a nested child Shape on a parent@shapefield (#167).
Returns afield() with an empty-tuple default and the
NestedShapemetadata, so…
Collect declared nested children from a dataclass's field metadata.
Walksfields() for the chirp_nestedmetadata planted
bynested() and fails loud (…
Register a named Shape in the module-level registry.
Called automatically by@shapeso every shape is discoverable for drift detection (#166/#172); may also be…
Return a read-only snapshot of registered named Shapes.
Consumed by theshapecheckcontract (#166) for registry-drift detection. The returned mapping is an immutable copy…
RaiseShapeError unless clsis a frozen, slotted dataclass.
Declare a verified SQL row Shape on a frozen, slotted dataclass.
The decorated dataclass is the row type (identity decorator). The declared
sqlis…
Return the_ShapeMeta sidecar for a Shape, or raise ShapeError.
Translate:nameplaceholders to the driver placeholder.
SQLite uses? (positional), PostgreSQL uses $N(1-based). Returns the rewritten SQL plus the params tuple ordered…
Yield(token, depth, start) for keyword/identifier tokens in sql.
A minimal left-to-right tokenizer that tracks parenthesis depth and skips quoted-string literals AND SQL…
Return the first non-whitespace character at/afteridx, or None.
Classify the OUTER query's WHERE analyzability for scope injection (#6).
Return whether the scope predicate can be safely structurally injected.
Un-injectable (opaque) when the SQL is a CTE (WITH), a compound query…
Return the RHS of a depth-0 predicate on the scope column, orNone.
Detects ANY depth-0 constraint on the scope column --<col>…
Return whethersql already carries the canonical <scope> = :scope.
Used for idempotency (the compiler does not double-inject its own predicate) and as…
Structurally inject<scope> = :scope into sql's WHERE clause.
Idempotent: returnssqlunchanged when the compiler's own canonical
predicate is already present. Adds …
Return the start index of the first depth-0 SQL comment at/afterafter.
Walks the inert-span-aware character stream and reports where a depth-0
--…
Advanceidxpast leading whitespace and inert spans (finding F3).
Returns the index of the first REAL (non-whitespace, non-inert) character
at/afteridx, skipping…
Return the index just past the first depth-0WHEREkeyword, or 0.
Used to anchor the scope-injection tail search so a comment that appears…
Return the index just past the depth-0FROMkeyword, or 0.
Anchors the no-WHERE scope-injection tail search so a comment in the projection list…
Return the index of the first depth-0 trailing clause keyword, orlen.
The scope predicate must be inserted BEFORE a depth-0 GROUP BY…
Return the parent SELECT after scope injection (the compiler's output).
Execution surface for@shape-decorated row models.
Not instantiated directly — the decorated dataclass isthe shape. The classmethod accessors expose the declared metadata,…
The structural pieces of a child SQL needed to batch it (findings #4/A3).
Split a WHERE body into its top-levelANDconjuncts.
Returns(conjuncts, ok) where conjunctsis the list of top-level
predicate texts (split on depth-0 …
Return whetherconjunct is a simple {on} = :placeholderequality.
Decompose a child SQL into head + residual WHERE + ORDER BY / LIMIT.
The bounded compiler replaces the child's per-parent join EQUALITY with…
Build the ONE batchedIN-list query for a child level (findings #4/#5/A3).
key_namesis the ordered tuple of generated key placeholder names for…
Insert, \{window} into the projection of innerbefore its FROM.
Locates the first depth-0FROMand inserts the window column at the end…
Run the batchedIN-list query(ies) for childand attach to parents.
Collects the distinct parentkeyvalues, runs ONE query per chunk of
…
Bounded nested loader: 1 parent query + batched query(ies) per child level.
Runs the parent SELECT (scope-injected when declared), then for EACH declared child…
One field of a@compositeresolved to its member Shape.
Immutable sidecar describing a@composite-decorated dataclass.
Attached to the class ascls.__chirp_composite__once at decoration and
never mutated (frozen-clean for free-threaded 3.14t).
Resolve a composite field annotation to(shape_cls, is_sequence).
A composite field is either a single@shapeclass (single-object load)
or atuple[Shape, ...]…
Return the_ShapeMeta sidecar for cls, or Noneif not a Shape.
Resolve every composite field to a member Shape (fail loud otherwise).
Usesget_type_hints() so string annotations (from __future__ import annotations) resolve…
Aggregate several Shapes for one page into a single frozen dataclass (#170).
The decorated dataclass declares the page's data ONCE: each field is a…
Return the_CompositeMeta sidecar for a Composite, or raise ShapeError.
Load surface for@composite-decorated page models (#170, #171).
Not instantiated directly -- the decorated dataclass is the page model.
load() runs the…
Coalesce the shared scope + params for one member Shape's load.
Only the placeholders the member's compiled SQL actually references are passed (so an…
Return the set of:name placeholder names referenced by sql.
A thin consumer of the shared_scan_placeholders() scanner (which is
::cast-…
_skip_inert
function
def _skip_inert(sql: str, i: int) -> int | None
If a string literal or SQL comment begins atsql[i], skip past it.
The single, shared low-level "inert span" skipper consulted by EVERY scanner
in this module (_scan_placeholders(), _iter_sql_tokens(), and the
depth bookkeeping that consumes_iter_sql_tokens()). Routing every
scanner through one skipper removes the parallel-maintenance hazard between
them and -- critically -- makes them all comment-aware in lockstep (finding
A2): a:nametoken, a paren, or a clause keyword that lives inside a
string literal or a comment is NOT real SQL and must never drive placeholder
binding, paren depth, or clause detection.
Recognized inert spans:
- String literals (
'...'/"..."), honoring doubled-quote escapes (''/"") so a colon inside a time literal like':30:00'stays inside the string. - Line comments (
-- ... EOL) -- consumed through the next newline (the newline itself is left for the caller). - Block comments (
/* ... */) -- consumed through the closing*/. Per the SQL standard these do NOT nest, so the first*/closes the comment. An unterminated block comment runs to end-of-string.
Returns the index ONE PAST the inert span when one started ati, or
None when sql[i]does not begin an inert span (the caller advances
normally). Always returns a value> i when non-Noneso no caller can
livelock.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
i
|
int
|
— |
_paren_depth_at
function
def _paren_depth_at(sql: str, target: int) -> int | None
Return the parenthesis depth at indextarget, or Noneif inert.
Walkssql[:target]skipping string literals and SQL comments via
_skip_inert(), counting only REAL ( / ). Used to confirm a
regex match sits at depth 0 without a second, drift-prone hand-rolled depth
loop (finding A2: the two former depth counters could desync).
ReturnsNone when targetlands INSIDE an inert span -- a string
literal or a SQL comment (finding A2 leak: acommunity_id = :scope
written inside a-- ...comment is NOT a real predicate and must never be
treated as already-scoped, which would suppress injection and ship an
unscoped query). ANoneresult means "this regex match is not real SQL;
skip it." Otherwise returns the real paren depth attarget.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
target
|
int
|
— |
_scan_placeholders
function
def _scan_placeholders(sql: str) -> Iterator[tuple[str, int, int]]
Yield(name, start, end) spans for each :name placeholder in sql.
The single, shared placeholder scanner consumed by both_bind_params()
(which rewrites spans to the driver placeholder) and_placeholder_names()
(which collects the distinct names). Factoring one scanner removes the
parallel-maintenance hazard between the two callers (finding #8).
It is:
- cast-aware -- a PostgreSQL
::castoperator is not a placeholder and yields nothing (the scanner advances past both colons); - quoted-string aware -- a
:name-shaped token inside a string literal ('...'or"...") is NOT a placeholder. The scanner skips string bodies, honoring doubled-quote escapes (''/""), so a colon inside a time literal like':30:00'or an interval string is never misparsed as a bind placeholder. - comment-aware (finding A2) -- a
:name-shaped token inside a SQL line comment (-- ...) or block comment (/* ... */) is NOT a placeholder, so a commented note never becomes a phantom bind param.
A name is[A-Za-z_][A-Za-z0-9_]* immediately following a single :.
Spans are yielded left-to-right;start is the index of the :and
end is one past the last name character (so sql[start:end]is the
full:nametoken).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— |
_reject_reserved_placeholders
function
def _reject_reserved_placeholders(sql: str, *, shape_name: str) -> None
Fail loud whensql declares a :__chirp_...placeholder (finding F1).
The__chirp_prefix is reserved for compiler-generated placeholders
(__chirp_k0 batch keys, __chirp_rnwindow rank). An author placeholder
under this prefix would silently collide with a compiler-generated value at
fetch time --Shape.validate passes, but Shape.fetchbinds the
author's:__chirp_k0to the parent-key value seeded into the IN-list,
returning wrong/empty rows. The author's declared SQL never legitimately
contains a__chirp_placeholder, so this guard is precise: it scans the
DECLARED SQL's placeholders via the shared_scan_placeholders() (which
is comment- and string-literal-aware, so a:__chirp_token inside a
comment or string literal is correctly ignored) and raises on the first
reserved-prefixed author placeholder.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
shape_name
|
str
|
— |
NestedShape
class
Explicit child-Shape declaration for the bounded compiler (#167).
Created vianested() and recorded in the field's metadata. The child
cls MUST itself be @shape-decorated (it carries its own SQL). The
compiler runs ONE batchedIN-list query per child level (never per
parent row), groups children by theoncolumn, and attaches them to each
parent viareplace().
_ShapeMeta
class
Immutable sidecar describing a@shape-decorated dataclass.
Attached to the class ascls.__chirp_shape__once at decoration and
never mutated (frozen-clean for free-threaded 3.14t).
nested
function
def nested(child: type, *, on: str, key: str, optional: bool = False) -> Any
Declare a nested child Shape on a parent@shapefield (#167).
Returns afield() with an empty-tuple default and the
NestedShape metadata, so the parent's row mapping (map_row's
cls(**filtered), which keeps only keys present in the SQL row) does not
raise for the absent nested column.@shapecollects these from
fields() and the bounded compiler fills them in.
Usage::
@shape("SELECT id, title FROM boards WHERE id = :id")
@dataclass(frozen=True, slots=True)
class Board:
id: int
title: str
cards: tuple[Card, ...] = nested(Card, on="board_id", key="id")
The empty-tuple default forces a field-ordering constraint: every
nested()field must come AFTER all scalar (no-default) fields.
@shape fails loud (ShapeError) if a no-default scalar field follows
anested()field, rather than letting Python raise the opaque
"non-default argument follows default argument".
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
child
|
type
|
— | The child row Shape (a ``@shape``-decorated frozen dataclass). |
on
|
str
|
— | The child SQL column joining back to the parent ``key``. |
key
|
str
|
— | The parent column seeding the child ``IN`` list. |
optional
|
bool
|
False
|
Skip the child level for a parent whose ``key`` is ``None``. |
_collect_nested
function
def _collect_nested(cls: type) -> tuple[NestedShape, ...]
Collect declared nested children from a dataclass's field metadata.
Walksfields() for the chirp_nestedmetadata planted
bynested() and fails loud (ShapeError) when a no-default scalar
field follows a nested field (§8.2 #2) -- surfacing the field-ordering
constraint clearly instead of relying on Python's opaque class-creation
error (which is also pre-empted by the empty-tuple defaultnestedsets).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
cls
|
type
|
— |
register_shape
function
def register_shape(name: str, cls: type) -> None
Register a named Shape in the module-level registry.
Called automatically by@shapeso every shape is discoverable for
drift detection (#166/#172); may also be called explicitly to alias a name.
Same-name collision policy (fail-loud, not last-wins-silently):
- registering the same class under a name is idempotent (no-op);
- registering a different class under an already-registered name raises
ShapeError.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
name
|
str
|
— | |
cls
|
type
|
— |
shape_registry
function
def shape_registry() -> Mapping[str, type]
Return a read-only snapshot of registered named Shapes.
Consumed by theshapecheckcontract (#166) for registry-drift
detection. The returned mapping is an immutable copy taken under the
registry lock — callers cannot mutate the live registry through it.
No parameters.
_validate_target
function
def _validate_target(cls: type) -> None
RaiseShapeError unless clsis a frozen, slotted dataclass.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
cls
|
type
|
— |
shape
function
def shape(sql: str, *, computed: Sequence[str] = (), scope: str | None = None, name: str | None = None) -> Any
Declare a verified SQL row Shape on a frozen, slotted dataclass.
The decorated dataclass is the row type (identity decorator). The declared
sqlis parsed for its output column list and stored — with the
computed members and tenant scope key — in a frozen _ShapeMeta
sidecar oncls.__chirp_shape__. The shape is auto-registered under
name (defaults to cls.__name__).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | The declared ``SELECT``. Author writes ``:name`` placeholders; the driver dialect is resolved at fetch time (never concatenated). |
computed
|
Sequence[str]
|
()
|
Declared computed/derived members not present as SELECT columns (widens the verified field set for ``shapecheck``). |
scope
|
str | None
|
None
|
Tenant scope key (honored by the L3 compiler; stored only in L1). |
name
|
str | None
|
None
|
Registry name; defaults to ``cls.__name__``. |
_meta
function
def _meta(cls: type) -> _ShapeMeta
Return the_ShapeMeta sidecar for a Shape, or raise ShapeError.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
cls
|
type
|
— |
_bind_params
function
def _bind_params(sql: str, driver: str, params: Mapping[str, Any]) -> tuple[str, tuple[Any, ...]]
Translate:nameplaceholders to the driver placeholder.
SQLite uses? (positional), PostgreSQL uses $N(1-based). Returns the
rewritten SQL plus the params tuple ordered to match the rewritten
placeholders. Parameters are NEVER concatenated into the SQL text — only the
placeholder token is rewritten, so this stays injection-safe (S608).
A:nameappearing more than once reuses the same value: SQLite repeats
the value in the params tuple (one?per occurrence); PostgreSQL reuses
the same$Nfor every occurrence (one value per distinct name).
Placeholder detection (including::castand quoted-string awareness) is
delegated to the shared_scan_placeholders() scanner so this stays in
lockstep with_placeholder_names() (finding #8).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
driver
|
str
|
— | |
params
|
Mapping[str, Any]
|
— |
_iter_sql_tokens
function
def _iter_sql_tokens(sql: str) -> Iterator[tuple[str, int, int]]
Yield(token, depth, start) for keyword/identifier tokens in sql.
A minimal left-to-right tokenizer that tracks parenthesis depth and skips
quoted-string literals AND SQL comments (so a keyword, paren, or clause
keyword inside a string or comment is not a token and does not move the depth
counter -- finding A2). It yields one entry per word token ([A-Za-z_]\w*)
with the paren depth at the token's opening position. Punctuation and
parentheses are not yielded; they only adjustdepth. The single
comment-aware tokenizer that ALL depth / clause / placeholder analysis routes
through, so there is exactly one depth counter (no parallel hand-rolled loop
that can desync). This is NOT a general SQL parser.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— |
_next_nonspace
function
def _next_nonspace(sql: str, idx: int) -> str | None
Return the first non-whitespace character at/afteridx, or None.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
idx
|
int
|
— |
_outer_where_target
function
def _outer_where_target(sql: str) -> str | None
Classify the OUTER query's WHERE analyzability for scope injection (#6).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— |
_scope_injectable
function
def _scope_injectable(sql: str) -> bool
Return whether the scope predicate can be safely structurally injected.
Un-injectable (opaque) when the SQL is a CTE (WITH), a compound query
(UNION/INTERSECT/EXCEPT), a SELECT */ expression projection
the injector cannot reason about (_parse_select_columnsreturns
None), lacks an analyzable FROM, or -- per finding #6 -- has an outer
query whose WHERE/FROM is not a single analyzable target (derived table /
FROM-subquery / correlated subquery / more than one depth-0 WHERE). A scoped
shape that is un-injectable fails loud at startup (never a silently-unscoped
query). This is the §8.1 boundary:CTE / UNION / SELECT * / dynamic / derived-tablecannot be injected, so the compiler must refuse rather than
ship an unscoped query.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— |
_depth0_scope_predicate
function
def _depth0_scope_predicate(sql: str, scope: str) -> str | None
Return the RHS of a depth-0 predicate on the scope column, orNone.
Detects ANY depth-0 constraint on the scope column --<col> = <rhs>,
<col> IN (...), <col> = :other-- not just the canonical
<col> = :scope(finding #7). Returns the matched right-hand side text
(the canonical form is exactly":scope"); Nonewhen no such depth-0
predicate exists. A predicate inside a subquery (depth > 0) is ignored so a
subquery predicate cannot fool the idempotency / conflict decision.
The scope-column matcher is anchored on BOTH edges (finding A1, the
tenant-isolation BLOCKER). Without a left boundary,scope='community_id'
substring-matched the suffix ofactor_community_idand judged a query
ALREADY scoped -- so the compiler injected nothing and shipped an UNSCOPED
cross-tenant query, while this same substring match made the
validate() backstop pass clean. The left lookbehind
(?<![\w.]) rejects a preceding word character OR .(so the column is
not the tail offoo_community_idand not a qualified column whose bare
suffix happens to equalscope), and the right boundary (?![\w])
rejects a trailing word character (socommunity_iddoes not match
community_id_archived).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
scope
|
str
|
— |
_has_scope_predicate
function
def _has_scope_predicate(sql: str, scope: str) -> bool
Return whethersql already carries the canonical <scope> = :scope.
Used for idempotency (the compiler does not double-inject its own predicate)
and as thevalidate() output backstop. Matches ONLY the compiler's
canonical depth-0 form<scope> = :scope(whitespace-tolerant, optional
table qualifier) -- depth-aware so a subquery-level predicate cannot fool the
backstop (finding #7). A non-canonical author predicate on the scope column
is rejected loudly by_inject_scope(), not silently treated as present.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
scope
|
str
|
— |
_inject_scope
function
def _inject_scope(sql: str, scope: str) -> str
Structurally inject<scope> = :scope into sql's WHERE clause.
Idempotent: returnssqlunchanged when the compiler's own canonical
predicate is already present. AddsAND <scope> = :scopeto an existing
depth-0 WHERE (before any GROUP BY / ORDER BY / LIMIT tail), or a fresh
WHERE <scope> = :scopeafter the FROM target when no depth-0 WHERE
exists. RaisesShapeError when sqlis un-injectable (caller
should have validated first) OR when the author wrote their OWN
(non-canonical) predicate on the scope column -- the scope guarantee is the
compiler's, not the author's, so an ambiguous author predicate fails loud
rather than being silently double-injected (finding #7).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
scope
|
str
|
— |
_first_depth0_comment
function
def _first_depth0_comment(sql: str, after: int = 0) -> int | None
Return the start index of the first depth-0 SQL comment at/afterafter.
Walks the inert-span-aware character stream and reports where a depth-0
-- line comment or /* ... */block comment begins. The scope
predicate must be inserted BEFORE any trailing comment so the injected
AND <scope> = :scope lands in EXECUTABLE SQL, not after a --(which
would silently comment out the tenant predicate -- finding A2). A comment
inside a subquery (depth > 0) -- or one positioned BEFOREafter(e.g. a
comment in the projection list, ahead of the WHERE) -- is not a boundary for
the outer injection.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
after
|
int
|
0
|
_skip_leading_inert
function
def _skip_leading_inert(sql: str, idx: int) -> int
Advanceidxpast leading whitespace and inert spans (finding F3).
Returns the index of the first REAL (non-whitespace, non-inert) character
at/afteridx, skipping whitespace and any string-literal / SQL-comment
spans via the shared_skip_inert() skipper (no third hand-rolled
lexer). Used to move the scope-injectionafteranchor PAST a comment that
immediately follows theWHERE / FROM keyword (e.g. WHERE -- c\n a = :a or WHERE /* c */ a = :a). Without this, the tail search treats that
leading comment as the WHERE clause's tail boundary and produces malformed
WHERE AND <pred>SQL (the comment, not the predicate, becomes the tail).
Capped atlen(sql)so the result is always a valid slice index.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
idx
|
int
|
— |
_depth0_where_position
function
def _depth0_where_position(sql: str) -> int
Return the index just past the first depth-0WHEREkeyword, or 0.
Used to anchor the scope-injection tail search so a comment that appears BEFORE the depth-0 WHERE is not mistaken for the trailing tail (finding A2).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— |
_depth0_from_target_end
function
def _depth0_from_target_end(sql: str) -> int
Return the index just past the depth-0FROMkeyword, or 0.
Anchors the no-WHERE scope-injection tail search so a comment in the projection list (before the FROM) is not mistaken for the tail (finding A2).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— |
_depth0_tail_position
function
def _depth0_tail_position(sql: str, after: int = 0) -> int
Return the index of the first depth-0 trailing clause keyword, orlen.
The scope predicate must be inserted BEFORE a depth-0 GROUP BY / ORDER BY /
LIMIT / etc. tail so it lands inside the WHERE clause -- but a tail keyword
INSIDE a subquery (depth > 0) must be ignored (it is not the outer query's
tail). Depth-aware so an ORDER BY inside an IN-subquery does not split the
injection point (finding #6 robustness). A trailing depth-0 comment AT/AFTER
afteris ALSO a boundary so the injected predicate never lands after a
-- (finding A2); afterexcludes a comment that precedes the WHERE /
FROM anchor from being treated as the tail.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
after
|
int
|
0
|
_compiled_statement
function
def _compiled_statement(meta: _ShapeMeta) -> str
Return the parent SELECT after scope injection (the compiler's output).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
meta
|
_ShapeMeta
|
— |
Shape
class
Execution surface for@shape-decorated row models.
Not instantiated directly — the decorated dataclass is the shape. The
classmethod accessors expose the declared metadata, and the async
fetch / fetch_one / streammethods run the declared SQL behind
theDatabasefacade (the repository seam).
_ChildDecomposition
class
The structural pieces of a child SQL needed to batch it (findings #4/A3).
_split_top_level_and
function
def _split_top_level_and(where_body: str) -> tuple[list[str], bool] | None
Split a WHERE body into its top-levelANDconjuncts.
Returns(conjuncts, ok) where conjunctsis the list of top-level
predicate texts (split on depth-0AND within the WHERE body) and ok
isFalse when a top-level ORis present (removing one conjunct from a
disjunction is unsound, so the join predicate cannot be safely isolated --
finding A3). ReturnsNonewhen the body is empty. Inert-span aware so an
AND/ORinside a string, comment, or sub-paren is not a split point.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
where_body
|
str
|
— |
_is_join_equality
function
def _is_join_equality(conjunct: str, on: str) -> bool
Return whetherconjunct is a simple {on} = :placeholderequality.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
conjunct
|
str
|
— | |
on
|
str
|
— |
_decompose_child
function
def _decompose_child(sql: str, on: str) -> _ChildDecomposition
Decompose a child SQL into head + residual WHERE + ORDER BY / LIMIT.
The bounded compiler replaces the child's per-parent join EQUALITY with a
single batchedWHERE {on} IN (...), so ONLY that equality is dropped --
the residual WHERE filters (e.g.deleted = 0) are PRESERVED and
recombined asWHERE {on} IN (...) AND (<residual>)(finding A3). The
trailingORDER BY and LIMITare also preserved (finding #4: the old
_child_headsilently discarded them, turning "top 5 recent comments per
card" into "all comments, arbitrary order"). All clause boundaries are found
at paren depth 0 so an ORDER BY / LIMIT / AND inside a subquery is left
attached. Theonargument is the child column joining back to the parent
key; it is used to locate (and drop) the per-parent join equality.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— | |
on
|
str
|
— |
_batched_child_sql
function
def _batched_child_sql(child_meta: _ShapeMeta, on: str, key_names: Sequence[str]) -> str
Build the ONE batchedIN-list query for a child level (findings #4/#5/A3).
key_namesis the ordered tuple of generated key placeholder names for
THIS chunk (__chirp_k0, __chirp_k1, ...). The generated batch-key
names use the reserved__chirp_ prefix (matching __chirp_rn) so they
can never collide with an author placeholder that happens to be namedk0
-- a collision would silently bind the author's residual filter to a
parent-key value (finding R3-2). The child's per-parent join EQUALITY is
replaced by a singleWHERE {on} IN (...), and the child's RESIDUAL WHERE
filters (e.g.deleted = 0) are preserved as AND (<residual>)so the
author's row exclusions survive the IN-list rewrite (finding A3). The child's
declared trailingORDER BY is re-attached and a per-parent LIMITis
rewritten into aROW_NUMBER() OVER (PARTITION BY {on} ORDER BY ...)window
top-N (so each parent's children are limited independently rather than
globally); the OUTER select orders on the PROJECTED{on}, __chirp_rnso
within-parent order is deterministic and not driver-dependent without
referencing a column the inner derived table does not expose (findings
A4/R3-1). When the child declaresscope=, the scope predicate is
structurally injected into the inner query too (every child statement
scoped).
Inexpressible cases (LIMIT without ORDER BY, OFFSET, per-parent LIMIT with
further nested grandchildren, an un-isolable join predicate) are rejected at
decoration byvalidate(), not here.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
child_meta
|
_ShapeMeta
|
— | |
on
|
str
|
— | |
key_names
|
Sequence[str]
|
— |
_splice_window_column
function
def _splice_window_column(inner: str, window: str) -> str
Insert, \{window} into the projection of innerbefore its FROM.
Locates the first depth-0FROMand inserts the window column at the end
of the projection list. Used by the per-parent-LIMIT top-N rewrite (#4).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
inner
|
str
|
— | |
window
|
str
|
— |
_resolve_children
function
async
async def _resolve_children(parents: list[Any], child: NestedShape, db: Database, params: Mapping[str, Any]) -> list[Any]
Run the batchedIN-list query(ies) for childand attach to parents.
Collects the distinct parentkeyvalues, runs ONE query per chunk of
_MAX_IN_LIST_KEYSkeys (finding #5: a single IN-list with one placeholder
per key crashes at ~32k keys with "too many SQL variables"), merges the
chunk results, groups child rows by theironvalue, recurses into the
child's own nested children, and rebuilds each parent via
replace() (frozen-safe).
Returns the list of parents with the nested field populated. Query count for
this level isceil(distinct_keys / _MAX_IN_LIST_KEYS)-- O(chunks), still
independent of the child ROW count (the #167 bounded-query guarantee).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
parents
|
list[Any]
|
— | |
child
|
NestedShape
|
— | |
db
|
Database
|
— | |
params
|
Mapping[str, Any]
|
— |
_fetch_nested
function
async
async def _fetch_nested(cls: type[T], db: Database, params: Mapping[str, Any]) -> list[T]
Bounded nested loader: 1 parent query + batched query(ies) per child level.
Runs the parent SELECT (scope-injected when declared), then for EACH declared
child level runs ONE batchedIN-list query per chunk of
_MAX_IN_LIST_KEYSdistinct parent keys (never per parent ROW). Query
count =1 + sum(ceil(distinct_keys_at_level / _MAX_IN_LIST_KEYS))--
bounded,O(depth * chunks), independent of the parent ROW count N
(the #167 no-N+1 guarantee; chunking added for finding #5 so the IN-list
never exceeds the driver's bind-variable ceiling).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
cls
|
type[T]
|
— | |
db
|
Database
|
— | |
params
|
Mapping[str, Any]
|
— |
_CompositeMember
class
One field of a@compositeresolved to its member Shape.
_CompositeMeta
class
Immutable sidecar describing a@composite-decorated dataclass.
Attached to the class ascls.__chirp_composite__once at decoration and
never mutated (frozen-clean for free-threaded 3.14t).
_resolve_member_shape
function
def _resolve_member_shape(annotation: Any) -> tuple[type, bool] | None
Resolve a composite field annotation to(shape_cls, is_sequence).
A composite field is either a single@shapeclass (single-object load)
or atuple[Shape, ...] (sequence load). Returns Nonewhen the
annotation is neither (the field is not a Shape member -- fail loud at
decoration incomposite()). Optionalsingle Shapes
(Shape | None) are accepted and load one row (or None).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
annotation
|
Any
|
— |
_shape_or_none
function
def _shape_or_none(cls: Any) -> _ShapeMeta | None
Return the_ShapeMeta sidecar for cls, or Noneif not a Shape.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
cls
|
Any
|
— |
_collect_members
function
def _collect_members(cls: type) -> tuple[_CompositeMember, ...]
Resolve every composite field to a member Shape (fail loud otherwise).
Usesget_type_hints() so string annotations (from __future__ import annotations) resolve to real classes. A field whose type is neither
a@shape class nor a tuple[Shape, ...] raises ShapeErrorat
decoration -- a composite member must be a Shape (the page's data is declared
once, in terms of Shapes).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
cls
|
type
|
— |
composite
function
def composite(*, scope: str | None = None) -> Any
Aggregate several Shapes for one page into a single frozen dataclass (#170).
The decorated dataclass declares the page's data ONCE: each field is a member
@shape class (single object) or tuple[Shape, ...](a list). Run the
whole page behind the repository seam viaload(), which fans
out to the member Shapes (reusing the L3 bounded compiler for nested members),
coalesces the shared tenantscope+ params, and returns one frozen
instance::
@composite(scope="community_id")
@dataclass(frozen=True, slots=True)
class BoardPage:
board: Board # single-object member
members: tuple[Member, ...] # sequence member
activity: tuple[Event, ...]
page = await Composite.load(BoardPage, db, board_id=7, scope=1)
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
scope
|
str | None
|
None
|
Composite-level tenant scope key. When set, the ``:scope`` value (threaded from ``Composite.load(..., scope=...)``) is passed to every member Shape that declares a matching ``scope=`` -- the page scopes once; the members inherit it. |
_composite_meta
function
def _composite_meta(cls: type) -> _CompositeMeta
Return the_CompositeMeta sidecar for a Composite, or raise ShapeError.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
cls
|
type
|
— |
Composite
class
Load surface for@composite-decorated page models (#170, #171).
Not instantiated directly -- the decorated dataclass is the page model.
load() runs the batched query set across the member Shapes behind the
Databasefacade (the repository seam): SQL never
leaves the@shape/@compositedeclarations, and the frozen result -- not
a SQL string -- is what reaches the template.
_member_params
function
def _member_params(member_meta: _ShapeMeta, composite_scope: str | None, params: Mapping[str, Any]) -> dict[str, Any]
Coalesce the shared scope + params for one member Shape's load.
Only the placeholders the member's compiled SQL actually references are
passed (so an unrelated page param does not error a member whose SQL never
names it). The tenant:scopevalue is threaded when the member declares
scope=-- the composite-level scope is the page's single declaration; the
member inherits it.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
member_meta
|
_ShapeMeta
|
— | |
composite_scope
|
str | None
|
— | |
params
|
Mapping[str, Any]
|
— |
_placeholder_names
function
def _placeholder_names(sql: str) -> set[str]
Return the set of:name placeholder names referenced by sql.
A thin consumer of the shared_scan_placeholders() scanner (which is
::cast- and quoted-string-aware), so the coalescer asks for exactly the
params each member statement binds and never drifts from_bind_params()
(finding #8).
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
sql
|
str
|
— |
View source · /home/runner/work/chirp/chirp/site/../src/chirp/data/shapes.py:1