Metadata-Version: 2.4
Name: dosi-engine
Version: 0.1.0
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3.12
Summary: Python bindings for Dosi (Datus OSI engine): compile and execute metric queries over OSI semantic models
License: Apache-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/datus-ai/osi-engine

# dosi-engine

Python bindings for [osi-engine](https://github.com/datus-ai/osi-engine): load
an OSI semantic model once, then list metrics/dimensions, compile metric
queries to dialect SQL, and execute them against a warehouse — all in-process.

```python
from dosi_engine import Engine, QueryError

engine = Engine(model_path="model.yaml", connections_path="agent.yml")

engine.metrics()                     # [{"name", "kind", "datasets", ...}]
engine.compile({"metrics": ["order_count"],
                "group_by": [{"field": "orders.status"}]},
               dialect="snowflake")  # {"dialect": "snowflake", "sql": "..."}
engine.execute({"metrics": ["order_count"]},
               connection="warehouse", timeout_secs=60)
#   {"dialect", "sql", "columns", "rows": [{col: val}], "row_count"}
```

The list/compile/execute payloads are the same machine contract as
`osi --format json` and the REST API; errors raise
`ModelError` / `QueryError` / `ExecuteError` (see `dosi_engine.errors`)
carrying the same structured fields (`code`, `candidates`, `hint`) as the
CLI/REST JSON error contract. `QueryError.candidates` lists valid
alternatives for unknown/ambiguous names, so agentic callers can retry
without parsing prose.

## Semantics worth knowing

- **One Engine = one compiled model.** Construction runs load → validate →
  compile and fails fast with `ModelError`. Engines are thread-safe; compile
  and execute release the GIL.
- **Connections** use the Datus `agent.yml` `datasources:` vocabulary. An
  explicit `connections_path` is loaded eagerly (fail fast); otherwise the
  CLI's discovery order applies lazily (`DOSI_CONNECTIONS`,
  `./osi-connections.yaml`, `~/.config/osi/connections.yaml`,
  `./conf/agent.yml`, `~/.datus/conf/agent.yml`).
- **`execute` without `connection`** runs on the connections file's
  `default: true` profile when one exists, else on local DuckDB
  (`db_path=` file, or in-memory). In-memory DuckDB spawns one process per
  statement, so seeded data requires a file database.
- **Timeouts abandon, they do not cancel**: on `timeout_secs` expiry the call
  raises `ExecuteError(code="timeout")` but the warehouse request keeps
  running on its thread; its pool slot frees when it finishes (same
  limitation as osi-server).
- **DuckDB execution shells out to the system `duckdb` CLI** — install it
  (https://duckdb.org) for local execution; warehouse drivers are compiled
  into the wheel (`exec-all`).

## Building

Requires a Python ≥ 3.12 interpreter discoverable by pyo3; on hosts whose
default `python3` is older, set `PYO3_PYTHON`:

```bash
cd crates/osi-py
PYO3_PYTHON=$(command -v python3.12) uvx maturin build --release -o ../../target/wheels
```

Development loop against a venv: `uvx maturin develop` (with the venv
active). Rust-side tests: `cargo test -p osi-py`; Python smoke tests (after
`maturin develop`): `pytest tests/python/`.

