Command-Line Interface (obsl)¶
obsl is the OrionBelt Semantic Layer command-line tool. It is local-first:
validate, compile, describe, diagram, graph, sparql, rules and
convert run in-process by calling the same compiler, parser and converter the REST API
uses — so you can lint a model and preview the generated SQL with zero
infrastructure. This makes it a natural fit for CI pipelines and pre-commit
hooks.
Commands that benefit from a running engine (notably execute and
rules evaluate, which need a warehouse connection) can target a deployed
server with --server.
Installation¶
The CLI ships with the package and is registered as the obsl console script
alongside orionbelt-api and orionbelt-ui:
pip install orionbelt-semantic-layer
# or
uv pip install orionbelt-semantic-layer
obsl --help
obsl --version
Commands¶
| Command | What it does | Runs |
|---|---|---|
obsl validate MODEL |
Validate a model; exits 1 on error |
local or --server |
obsl compile [MODEL] -q QUERY |
Compile a query to SQL | local or --server |
obsl execute [MODEL] -q QUERY |
Compile and run a query | local or --server |
obsl describe MODEL |
Structured overview of artefacts | local |
obsl diagram [MODEL] |
Mermaid ER diagram, raw or as Markdown | local or --server |
obsl graph [MODEL] |
OBSL-Core RDF graph, the model's ontology export (Turtle) | local or --server |
obsl sparql [MODEL] --sparql TEXT |
Read-only SPARQL (SELECT or ASK) over that graph | local or --server |
obsl rules list [MODEL] |
Business rules and whether each compiles | local or --server |
obsl rules compile [MODEL] |
The SQL behind each rule; exits 1 if one fails |
local or --server |
obsl rules evaluate [MODEL] |
Run rules and report findings; exits 1 if one fails |
local or --server |
obsl lineage [MODEL] --measure NAME |
What an artefact or query is built from (Mermaid, Markdown, JSON, Turtle) | local or --server |
obsl convert DIRECTION INPUT |
OSI ↔ OBML conversion | local or --server |
obsl dialects |
List supported SQL dialects | local or --server |
MODEL and INPUT accept a file path, or - to read from standard input.
For compile and execute you supply the query one of two ways (exactly one):
-q / --query— a query document (JSON or YAML; snake_case or camelCase fields)--sql— an OrionBelt Semantic QL (OBSQL) string, BI-style SQL against the model's virtual table:SELECT <dim/measure labels> FROM <model> [WHERE ...] [ORDER BY ...] [LIMIT n]
Options reference¶
obsl <command> --help is always authoritative. Every option:
| Command | Options |
|---|---|
| common (where remote-capable) | -f, --format {table,json,csv,tsv} · -s, --server URL (env OBSL_SERVER) · --api-key KEY (env OBSL_API_KEY) · --ca-cert PATH (env OBSL_CA_CERT) · --client-cert PATH (env OBSL_CLIENT_CERT) · --client-key PATH (env OBSL_CLIENT_KEY) - see TLS in remote mode |
validate |
--online · -d/--dialect NAME (with --online; defaults to DB_VENDOR) · -f/--format · -s/--server · --api-key · TLS options above |
compile |
-q/--query PATH · --sql TEXT · -d/--dialect NAME · --explain · --pretty/--no-pretty (default pretty) · -f/--format · -s/--server · --api-key · TLS options above |
execute |
-q/--query PATH · --sql TEXT · -d/--dialect NAME · --limit N (default 1000; see note) · -f/--format · -s/--server · --api-key · TLS options above |
describe |
-f/--format |
diagram |
--columns/--no-columns (default columns) · --theme NAME (Mermaid theme, default default) · --markdown/--md · -o/--output PATH (Markdown when it ends in .md) · -s/--server · --api-key · TLS options above |
graph |
-o/--output PATH · -s/--server · --api-key · TLS options above |
sparql |
-q/--query PATH (SPARQL file) · --sparql TEXT · -f/--format · -s/--server · --api-key · TLS options above |
rules list |
-d/--dialect NAME · -f/--format · -s/--server · --api-key · TLS options above |
rules compile |
-r/--rule NAME (repeatable; default every rule) · -d/--dialect NAME · -f/--format · -s/--server · --api-key · TLS options above |
lineage |
--dimension/--measure/--metric NAME · -r/--rule NAME · -q/--query PATH · --sql TEXT · -d/--dialect NAME · -f/--format {mermaid,markdown,json,turtle} · -o/--output PATH · -s/--server · --api-key · TLS options above |
rules evaluate |
-r/--rule NAME (repeatable) · --type TYPE (repeatable) · --severity LEVEL (repeatable) · --limit N (findings per rule, default 20) · -d/--dialect NAME · -f/--format · -s/--server · --api-key · TLS options above |
convert |
DIRECTION (osi-to-obml|obml-to-osi) · INPUT · --name NAME (OSI model name, obml-to-osi) · -s/--server · --api-key · TLS options above |
dialects |
-f/--format · -s/--server · --api-key · TLS options above |
Global: -V/--version, --install-completion, --show-completion.
--limit applies to -q queries and local --sql (when the query has no
limit). It cannot apply to remote --sql (the server's OBSQL endpoint takes
no limit) — put LIMIT n in the SQL there; the CLI warns if you pass both.
Validate¶
Validation returns a non-zero exit code when the model is invalid, so it drops straight into CI:
obsl validate model.yaml
# model is valid
obsl validate model.yaml -f json # machine-readable result
# .github/workflows/ci.yml — validate every model, fail on the first invalid one
- run: |
for m in models/*.yaml; do obsl validate "$m" || exit 1; done
Checking against the datasource¶
Validation is offline by default: the model is checked against itself and the
OBML schema, and no connection is opened. The physical binding is what that
leaves out — a data object's code and each column's code are opaque strings
to the validator, so a model stays valid after the table behind it is dropped or
a column is renamed, and the warehouse is the first thing to say otherwise.
--online adds a probe of every data object: one SELECT <declared columns>
FROM <table> LIMIT 0, quoted the way a compiled query would quote it, which
plans without scanning and proves the columns are readable under the spelling
the model uses.
obsl validate model.yaml --online
# error: model is invalid:
# error: [DATASOURCE_COLUMN_MISSING] Column 'order_id' does not exist on main.orders
# (data object 'Orders'). (dataObjects.Orders.columns.Order ID.code)
# error: [DATASOURCE_TYPE_MISMATCH] Column 'amount' on data object 'Orders' is declared
# 'float' but the datasource returns a string column.
# error: [DATASOURCE_TABLE_MISSING] Data object 'Shipments' maps to main.shipments,
# which the datasource could not read: Catalog Error: Table with name
# shipments does not exist!
Findings are errors like any other, so the command exits 1 — which is what
makes it useful as a post-deploy check, run against the warehouse the model will
actually query:
- run: obsl validate models/sales.yaml --online
env:
DB_VENDOR: postgres
POSTGRES_HOST: ${{ secrets.PG_HOST }}
-d/--dialect picks which configured datasource to probe. Note that it defaults
to DB_VENDOR rather than to the model's defaultDialect, unlike the --dialect
on compile and execute: here it selects a connection to open, and the model's
declared dialect says what SQL to generate, not what server is reachable.
Because the flag is opt-in it fails closed — if the connection is missing you get
DATASOURCE_UNAVAILABLE and a non-zero exit, not a pass. See
the endpoint reference
for the full list of codes and what the probe deliberately does not check.
Compile¶
obsl compile model.yaml -q query.json --dialect snowflake
# or with an OBSQL string instead of a query document:
obsl compile model.yaml --sql 'SELECT "Customer Country", "Revenue" FROM model LIMIT 5'
Add --explain to print the planner's decisions (chosen planner, base object,
join path, CFL legs) to stderr while the SQL stays on stdout, so piping is
unaffected:
A minimal query document:
Execute¶
execute compiles and runs the query against the configured warehouse. Running
locally requires database drivers and credentials to be configured (see
Configuration); otherwise point it at a
deployed engine:
obsl execute model.yaml -q query.json -f csv > results.csv # local
obsl execute model.yaml -q query.json --limit 50 # cap rows when the query has none
obsl execute -q query.json --server https://your-host --api-key "$OBSL_API_KEY" # remote
--limit (default 1000) applies when the query carries no limit of its own. It
covers -q queries and local --sql; for remote --sql, put LIMIT n in the
SQL (the CLI warns if you pass --limit there).
Describe, diagram, graph¶
obsl describe model.yaml # tables of data objects, dimensions, measures, metrics
obsl diagram model.yaml > er.mmd # Mermaid ER diagram
obsl diagram model.yaml -o er.md # the same, in a ```mermaid Markdown fence
obsl diagram model.yaml --no-columns --theme dark # compact entities, dark theme
obsl graph model.yaml -o model.ttl # OBSL-Core RDF (Turtle), the ontology export
obsl diagram -s https://obsl.example.com -o er.md # download from the server's curated model
obsl graph -s https://obsl.example.com -o model.ttl
-o writes the file and notes it on stderr. The Markdown form is what the UI's
.md download saves, and the Turtle is what its Export Onto button saves.
SPARQL¶
obsl sparql runs a read-only SPARQL query (SELECT or ASK) against the
model's OBSL-Core graph, the one obsl graph prints. Give the query inline with
--sparql or as a file with -q (exactly one). SELECT prints a table of the
bindings, ASK prints true or false. Updates are refused.
obsl sparql model.yaml --sparql 'ASK { ?s ?p ?o }'
obsl sparql model.yaml -q measures.rq -f csv
obsl sparql -s https://obsl.example.com -q measures.rq # the server's curated model
Business rules¶
A model's rules: compile to ordinary queries whose rows are the rule's
findings: members for classification and eligibility rules, violations for
validation and constraint rules. The rules commands use the same planner
as the REST rules endpoints.
obsl rules list model.yaml # every rule, and whether it compiles
obsl rules compile model.yaml -r "High Value Client" # the SQL behind one rule
obsl rules evaluate model.yaml # one summary row per rule
obsl rules evaluate model.yaml --type validation --severity error
obsl rules evaluate model.yaml -r "Low Stock Product" # that rule's findings
evaluate fetches at most --limit findings per rule (default 20). A count
shown as 20+ reached the limit and may be higher. Locally it needs a
configured warehouse, like execute. compile and evaluate exit 1 when a
rule fails to compile or run, and report the others anyway, so a CI job catches
a rule that no longer works. Findings do not change the exit code: a validation
rule that runs and reports violations exits 0. To fail a data-quality job on
violations, read the findings, for example the finding_count of each rule in
-f json.
Lineage¶
obsl lineage shows what a dimension, measure, metric, rule or query is built
from, down to the tables. Name the artefact with exactly one of --dimension,
--measure, --metric, --rule/-r, or give a query with -q or --sql; a
query is compiled, so its lineage includes the planner's joins.
obsl lineage model.yaml --metric "Gross Margin" # Mermaid flowchart
obsl lineage model.yaml -r "Healthy Category" -o rule.md # as a ```mermaid Markdown file
obsl lineage model.yaml --sql 'SELECT "Country Name", "Total Sales" FROM m' -o query.ttl
obsl lineage -s https://obsl.example.com --measure "Total Sales" -f json
-f picks mermaid (default), markdown, json (the API's shape) or
turtle (the OBSL graph's IRIs linked by prov:wasDerivedFrom). Without -f,
an -o path ending in .md, .json or .ttl picks the format. With
--server, pass a query with -q; --sql needs the local model to translate.
Convert (OSI ↔ OBML)¶
OrionBelt interoperates with the Open Semantic Interchange (OSI) format:
obsl convert obml-to-osi model.yaml > model.osi.yaml
obsl convert osi-to-obml model.osi.yaml > model.yaml
Conversion requires the optional converter: pip install 'orionbelt-semantic-layer[osi]'.
Output formats and streams¶
The --format / -f flag controls tabular output (table, json, csv,
tsv). Data is written to stdout; informational notes, warnings and errors
go to stderr — so obsl ... -f json | jq and redirects work cleanly.
Remote mode¶
| Flag | Env var | Purpose |
|---|---|---|
--server URL |
OBSL_SERVER |
Target a deployed OrionBelt REST API |
--api-key KEY |
OBSL_API_KEY |
API key for that server |
--ca-cert PATH |
OBSL_CA_CERT |
PEM CA bundle for a private or self-signed authority |
--client-cert PATH |
OBSL_CLIENT_CERT |
PEM client certificate, when an ingress requires one |
--client-key PATH |
OBSL_CLIENT_KEY |
Its private key, when they are separate files |
compile and execute in remote mode run the query against the server's
curated model (via the /v1/query/sql and /v1/query/execute shortcuts that
auto-resolve the deployed model) — no model is uploaded, so MODEL is omitted
and governed single-model deployments (where ad-hoc model upload is disabled)
are respected. diagram, graph, sparql and the rules commands work the
same way, through the /v1/diagram/er, /v1/graph, /v1/sparql and
/v1/rules shortcuts. validate and convert operate on the
model you pass.
TLS in remote mode¶
--server speaks HTTP to the REST API, so an https:// URL is encrypted and
verified: the certificate chain and the hostname are both checked, and an
expired or untrusted certificate is refused rather than warned about.
--ca-cert replaces the default trust store rather than adding to it, and
there is deliberately no flag to disable verification: a CLI that can be told to
trust anything gets told to trust anything.
Against a gateway that requires mutual TLS:
obsl execute --sql 'SELECT "Region", "Sales" FROM model' \
--server https://obsl.internal \
--ca-cert /etc/ssl/certs/internal-ca.crt \
--client-cert /etc/ssl/certs/obsl-client.crt \
--client-key /etc/ssl/private/obsl-client.key
Omit --client-key when the certificate and key live in one PEM.
Every failure names the setting it is about: a path that is missing, unreadable, or present but not what it claims fails when the flags are resolved rather than as an SSL error from inside the HTTP client mentioning neither the flag nor the file.
Which end these are
These are the client half. The server half is
API_TLS_CLIENT_CA, which makes
the REST API demand a certificate; --client-cert is how obsl presents
one. They also work against a gateway in front of OBSL that asks for a
certificate, in which case nothing on the server needs configuring at all.
The wire-surface settings are a different surface and do not apply here.
obsl --server is a REST client; it never connects over pgwire or Flight
SQL. For those see Postgres wire and
ADBC / Arrow Flight SQL.