Trilogy Parser (trilogy-parser)
A Rust-based CLI tool and Python library for parsing PreQL (Trilogy) files and resolving import dependencies with ETL-aware dependency ordering.
Features
- Parse PreQL files to extract imports, datasource declarations, and persist statements
- Resolve import dependencies transitively
- Build dependency graphs with ETL-aware ordering:
- Files that persist (write) to a datasource run before files that declare it, even if they import it.
- Standard import dependencies (imported files run before importing files)
Exit codes:
0: Success1: Error (parse error, file not found, circular dependency, etc.)
Installation
Crate versions track pytrilogy releases: a
published trilogy-parser version always carries the grammar of the pytrilogy
release with the same number.
Feature flags
| Feature | Default | Effect |
|---|---|---|
| (none) | ✅ | Pure Rust. No Python toolchain required. |
python |
Builds the PyO3 bindings (python_bindings, parse_trilogy_syntax*). Used only by the wheel build. |
Rust consumers should stay on default features; enabling python pulls in pyo3
and requires a Python interpreter at build time.
Rust Library Usage
use Parser;
use Path;
use ;
DatasourceDeclaration / DatasourceInfo carry the backing of each datasource:
address plus an address_kind of literal, templated (an f-string address
resolved at run time), query (a view, no physical table), or file. Flags
is_root, is_partial, and is_partitioned mirror the corresponding modifiers.
CLI Usage
Parse a single file
Parse a directory
Resolve dependencies
Analyze datasources
Python Integration
The Rust resolver is integrated into the Python package via PyO3 and maturin.
Building the Python Extension
# or
Using in Python
# Create script nodes from files
=
=
# Use the ETL dependency strategy (backed by Rust)
=
=
# Get execution order (graphs use the Rust-backed graph facade;
# nodes are script-path strings)
=
The ETLDependencyStrategy uses the Rust-based resolver under the hood for fast, accurate dependency analysis based on:
- Import statements
- Datasource declarations
- Persist statements (append/overwrite/persist)
Development
Running Rust Tests
Test layout:
- Unit tests for the parser — imports, datasource backings/modifiers, persist statements
- Unit tests for the resolver — dependency resolution and ordering
tests/cli_integration.rs— every CLI command, including the JSON output contracttests/public_api.rs— the crate's exported surface, reached only throughtrilogy_parser::*, so a regression in what is actually published fails here
Building the CLI
The binary will be at target/release/trilogy-parser-cli (or .exe on Windows).
Building for Python
Run maturin from the base of the pytrilogy repo. [not this directory.]
# Development mode (installs in current Python environment)
# Production build
# The wheel will be in target/wheels/
Dependency Ordering Rules
The resolver implements three key dependency rules:
- Import Dependencies : Imported files should run before importing files
- Persist-Before-Declare: Files that persist to a datasource must run before files that declare it, even if they import that file.
Edge Cases
- Case 1: file A imports from file B → B must run before A for all datasources in B
- Case 2: file A imports from file B, then updates datasource from file B → update takes precedence, so A runs before B.
Scope
src/trilogy.pest is the full Trilogy grammar — the same one that backs
pytrilogy's pest parser — so TrilogyParser::parse(Rule::start, ..) accepts any
valid Trilogy source.
The higher-level helpers (parse_file, parse_imports, ImportResolver) walk
only the dependency-relevant constructs of that tree: imports, datasource
declarations, and persist statements. Everything else in a file is parsed and
skipped. For anything beyond dependency analysis, drive the grammar directly.