Skip to content

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:

obsl compile model.yaml -q query.json -d postgres --explain > out.sql

A minimal query document:

{
  "select": { "dimensions": ["Customer Country"], "measures": ["Total Revenue"] },
  "limit": 10
}

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.

obsl validate model.yaml --server https://obsl.internal \
  --ca-cert /etc/ssl/certs/internal-ca.crt

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.

export OBSL_SERVER=https://your-host
export OBSL_API_KEY=sk-...
obsl compile -q query.json                                  # query document
obsl execute --sql 'SELECT "Region", "Sales" FROM model'    # OBSQL string
obsl validate model.yaml                                     # validates the model you pass