Metadata-Version: 2.4
Name: dosi-engine
Version: 0.1.9
Classifier: License :: Other/Proprietary License
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: Elastic-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://dosi.datus.ai/
Project-URL: Homepage, https://dosi.datus.ai/

# 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={"warehouse": {"type": "snowflake", "account": "...", "password": "..."}},
)

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** are an in-memory `name -> datasource settings` mapping in
  the Datus `agent.yml` `datasources:` vocabulary. File discovery and loading
  belong to the Rust CLI/server; the Python binding never reads or writes a
  connections file.
- **`execute` without `connection`** runs on the runtime connections mapping's
  `default: true` profile when one exists, else on local DuckDB
  (`db_path=` file, or in-memory).
- **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 dosi-server).
- **DuckDB runs in process and ships inside the wheel** — nothing to install
  for local execution. Warehouse drivers are compiled in too (`exec-all`), so
  no client libraries either.

## Building

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

```bash
cd crates/dosi-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 dosi-py`; Python smoke tests (after
`maturin develop`): `pytest tests/python/`.

