Skip to main content

Run OrionBelt Semantic Layer query batches on a schedule and emit reports.

Project description

OrionBelt Runner

Run OrionBelt Semantic Layer query batches and emit reports.

A run is a YAML document combining:

  • An OBSL endpoint (base URL, optional auth, optional locale/timezone, optional model to load)
  • A list of named queries — any valid OBML query body
  • A report config — markdown, HTML, or PDF output with sections bound to queries

Numeric and timestamp cells are pre-rendered server-side using each column's format pattern from the OBML model (the runner sends format_values=true on every query), so reports show e.g. 1.853.429,67 for locale: de without any client-side formatting. See examples/monthly-revenue-2026-04-29.md (markdown), examples/monthly-revenue-2026-04-29.html (HTML), and examples/monthly-revenue-2026-04-29.pdf (PDF) for sample outputs.

Status

Early scaffold (v0.3.0). Markdown, HTML, and PDF reports, with optional per-query TSV exports and an always-on YAML run log sidecar. No scheduler yet — drive it from cron / systemd / GitHub Actions / Cloud Scheduler / etc.

Install

uv sync                  # core: markdown + HTML reports
uv sync --extra pdf      # also enable PDF output (requires Pango / Cairo)

PDF output needs WeasyPrint, which depends on system libraries (Pango, Cairo, GDK-Pixbuf). On macOS: brew install pango. On Debian / Ubuntu: apt install libpango-1.0-0 libpangoft2-1.0-0. See the WeasyPrint install guide for other platforms. Skip the extra if you only need markdown / HTML.

Apple Silicon note: Homebrew installs libraries to /opt/homebrew/lib, which Python's loader doesn't search by default. If WeasyPrint can't find libgobject-2.0-0, prefix the runner with: DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib uv run orionbelt-runner ... (or export it in your shell profile).

Run

uv run orionbelt-runner run examples/monthly-revenue.yaml

Override the OBSL endpoint without editing the spec:

uv run orionbelt-runner run examples/monthly-revenue.yaml \
  --base-url http://my-obsl:8080

Or via env (.env or shell):

OBSL_BASE_URL=http://my-obsl:8080 \
OBSL_API_TOKEN=... \
uv run orionbelt-runner run examples/monthly-revenue.yaml

Server expectations

The runner calls /v1/query/execute, so OBSL needs to be configured to execute queries (not just compile them):

  • QUERY_EXECUTE=true (or FLIGHT_ENABLED=true)
  • DB driver credentials configured for the dialect(s) you query

Three deployment shapes are supported, in order of preference:

  1. Single-model mode (MODEL_FILE=... on the server). Spec leaves obsl.model and obsl.model_id unset; the runner uses top-level shortcut endpoints.
  2. Multi-model server with a model already loaded. Set obsl.model_id in the spec; the runner still uses shortcut endpoints and keys into the named model.
  3. Runner-loaded model. Set obsl.model.yaml_path in the spec — the runner creates a session, posts the model to /v1/sessions/{id}/models, runs queries against /v1/sessions/{id}/query/execute, and deletes the session in a finally block. Useful for ad-hoc reports against a model you keep next to the spec file.

Spec format

See examples/monthly-revenue.yaml for a full spec.

name: Monthly Revenue
obsl:
  base_url: http://localhost:8080
  locale: de                       # optional — BCP-47, drives display formatting
  # timezone: Europe/Berlin        # optional — IANA TZ
  # model_id: sales                # multi-model server with a pre-loaded model
  # model:                         # OR: load your own model into a fresh session
  #   yaml_path: ./sales.obml.yaml # path is resolved relative to this spec file
  #   extends: [./fragments/dim-time.yaml]
queries:
  - name: total_revenue
    dialect: postgres
    query:
      select:
        measures: [Total Revenue]
report:
  format: markdown
  output: reports/{name}-{date}.md
  title: "Monthly Revenue  {date}"
  sections:
    - heading: Headline number
      query: total_revenue
      render: value

Section render modes:

render Output
table Table of all rows
value Single bold value (first numeric column of first row by default)
list Bullet list of one column

Path placeholders are accepted in report.output, report.title, report.intro, and report.footer. The instant comes from OBSL's GET /v1/settings (server clock + effective TZ); falls back to the runner's UTC clock when settings is unreachable.

Placeholder Example Notes
{name} Monthly Revenue the spec name
{date} 2026-04-29 YYYY-MM-DD in the resolved TZ
{datetime} 2026-04-29T18-02-06Z filesystem-safe; trailing Z only when TZ is UTC
{time} 18:02:06 colons are unsafe on Windows paths — use {time_filename}
{time_filename} 18_02_06 filesystem-safe
{tz} Europe/Berlin IANA name
{tz_filename} / {timezone} Europe, Berlin / replaced with , for path safety
{runner_version} 0.3.0 the OrionBelt Runner version that produced this report

report.footer additionally accepts result-derived counters: {number_of_queries}, {number_of_sections}, {number_of_rows} (camelCase aliases — {numberOfQueries} etc. — also work).

Queries from a folder

For larger query libraries, point at a directory instead of (or in addition to) inline queries::

queries_dir: ./queries        # recursive *.yaml + *.yml, alpha-sorted
queries:                      # optional, runs after dir queries in spec order
  - name: ad_hoc
    query: { ... }

Each file is a full QuerySpec (name, dialect, query, optional description). When name: is omitted the filename stem is used verbatim — total-revenue.yamltotal-revenue. Duplicate names across the dir + inline list raise an error at load time. Paths resolve relative to the spec file.

Outputs

A run produces up to three artefacts in the report directory:

reports/monthly-revenue-2026-04-29.md           ← report
reports/monthly-revenue-2026-04-29.run.yaml     ← run log (always)
reports/monthly-revenue-2026-04-29_exports/     ← TSV exports (opt-in)
  ├── total_revenue.tsv
  ├── revenue_by_nation.tsv
  └── top_orders_raw.tsv

Report — markdown, HTML, or PDF

format: markdown (default) writes a .md. format: html writes a self-contained HTML5 document with inline default CSS and a light/dark theme — no external assets, so the file works when emailed, opened from disk, or served from a static host. The output extension follows the template you set in output:.

report:
  format: html
  output: reports/{name}-{date}.html
  title: "Monthly Revenue  {date}"

See examples/monthly-revenue-2026-04-29.html for a rendered HTML sample.

format: pdf reuses the same HTML pipeline and runs the result through WeasyPrint — so PDF layout stays automatically in lockstep with the HTML output. A print-only stylesheet adds A4 margins and a page n / total footer; section headings (h2) get a page-break-before: auto / page-break-after: avoid hint so tables don't get orphaned.

report:
  format: pdf
  output: reports/{name}-{date}.pdf
  title: "Monthly Revenue  {date}"

PDF requires the optional pdf extra (uv sync --extra pdf) and WeasyPrint's system libraries — see Install. See examples/monthly-revenue-2026-04-29.pdf for a rendered PDF sample.

Run log (YAML sidecar) — always written

Always emitted next to the report at <report-stem>.run.yaml, even when queries fail (when it's most useful). Captures, for each query: the compiled SQL as a literal block scalar, the OBSL explain plan (planner + reasons + joins + CFL legs), the resolved info (fact tables / dimensions / measures), wall-clock and server-side timing, warnings, and any errors. The file header records the spec name, OBSL version / api_version, session/model IDs, and the report path.

YAML so it's both human-skimmable and trivially machine-parseable in one step. Useful for debugging, audit trails, and downstream tooling that wants the SQL or the plan.

See examples/monthly-revenue-2026-04-29.run.yaml.

Per-query TSV exports — opt-in

Set report.export_results: true to write each query's rows as TSV into a sibling <report-stem>_exports/ directory:

report:
  output: reports/{name}-{date}.md
  export_results: true

One file per query, named after the query and sanitised to safe path chars. TSV uses \t separator and \n line endings; cells with embedded tabs / newlines / quotes are quoted (compatible with pandas.read_csv(..., sep='\t')). Cells reflect the same format_values=true data the report shows. Exports are only written on a fully successful run.

See examples/monthly-revenue-2026-04-29_exports/.

Architecture

The runner talks to OBSL through a small ObslClient Protocol. One implementation today (HTTP). Tests can drop in a fake; an in-process implementation can be added later without touching the runner, report, or CLI code.

spec.yaml ──▶ load_spec ──▶ Runner ──▶ ObslClient ──▶ OBSL (HTTP)
                              │
                              ├─▶ render_markdown / render_html / render_pdf ──▶ report.md|html|pdf
                              ├─▶ render_runlog                 ──▶ report.run.yaml
                              └─▶ render_tsv (× N)              ──▶ report_exports/*.tsv

License

BSL-1.1 (mirrors OrionBelt Semantic Layer).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

orionbelt_runner-0.3.0.tar.gz (60.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

orionbelt_runner-0.3.0-py3-none-any.whl (29.0 kB view details)

Uploaded Python 3

File details

Details for the file orionbelt_runner-0.3.0.tar.gz.

File metadata

  • Download URL: orionbelt_runner-0.3.0.tar.gz
  • Upload date:
  • Size: 60.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.15

File hashes

Hashes for orionbelt_runner-0.3.0.tar.gz
Algorithm Hash digest
SHA256 dd512dd71bb03d86f067a633be1620ee5a45e862a27a719b6606a68fc4e1e860
MD5 90808015c86a945cef8e70e5268bfc74
BLAKE2b-256 b56955020e6066cbeb28183d1fc90b3a7b75a7370bfe318c4f5a4ce9f30433fe

See more details on using hashes here.

File details

Details for the file orionbelt_runner-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for orionbelt_runner-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ffcab5921db12fd1259122976c48a915054a44d84ec0947b2801b8e15bff6b45
MD5 06d55024ce1fabdcceee5a23b4e94e76
BLAKE2b-256 f1e323591169af565ccb0fc021bd39b8df3e828947eabb8b160ead9192ba4539

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page