Metadata-Version: 2.4
Name: oleanderhq-sdk
Version: 0.4.1
Summary: Python SDK for oleander: query lake, list and launch Spark jobs
License-Expression: MIT
Keywords: data,lake,oleander,openLineage,query,sdk,serverless,spark
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.7
Provides-Extra: dev
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'dev'
Requires-Dist: pytest<9,>=8; extra == 'dev'
Requires-Dist: respx<1,>=0.22; extra == 'dev'
Description-Content-Type: text/markdown

# oleander Python SDK

Use the [oleander](https://oleander.dev) API from Python: run lake queries, list Spark jobs, and launch Spark jobs.

## Install

```bash
pip install oleanderhq-sdk
```

## Get your API key

Create an API key in [oleander settings](https://oleander.dev/app/settings), or run `oleander configure` if you use the CLI. You can pass the key when creating the client or set the `OLEANDER_API_KEY` environment variable.

## Quick start

```python
import asyncio
from oleander_sdk import Oleander

async def main():
    oleander = Oleander()
    result = await oleander.list_spark_jobs()
    print(result.artifacts)

asyncio.run(main())
```

## API

All methods are async. Use `await` when calling them.

### Query (lake)

Run a SQL query against the oleander lake. The second parameter is optional; `save` defaults to `False`. Use `save=True` to persist results as a table.

```python
from oleander_sdk import Oleander, QueryOptions

oleander = Oleander()
result = await oleander.query(
    "SELECT * FROM oleander.default.flowers LIMIT 10",
)
print(result.results.columns, result.results.rows)
print(result.row_count, result.execution_time)
if result.saved_table_name:
    print("Saved to:", result.saved_table_name)
```

### List Spark jobs

List your Spark artifacts. Options: `limit` (default 20), `offset` (default 0).

```python
from oleander_sdk import Oleander, ListSparkJobsOptions

oleander = Oleander()
result = await oleander.list_spark_jobs()
print(result.artifacts, result.has_more)

next_page = await oleander.list_spark_jobs(ListSparkJobsOptions(offset=20))
```

### Launch Spark job

Submit a Spark job. Required: `namespace`, `name`, `entrypoint`. `cluster` defaults to `"oleander"`. The legacy `script_name` argument is still accepted as an alias for `entrypoint`.

```python
from oleander_sdk import Oleander, SparkJobSubmitOptions

oleander = Oleander()
result = await oleander.submit_spark_job(SparkJobSubmitOptions(
    namespace="my-namespace",
    name="my-job-name",
    entrypoint="my_script.py",
))
print("Run ID:", result.run_id)
```

To target an external cluster, set `cluster` and provide the cluster-specific properties that match the current API:

```python
result = await oleander.submit_spark_job(SparkJobSubmitOptions(
    cluster="emr-prod",
    namespace="my-namespace",
    name="my-job-name",
    entrypoint="s3://bucket/jobs/main.py",
    args=["--date", "2026-03-11"],
    py_files="s3://bucket/deps.zip",
    packages=["org.example:my-lib:1.0.0"],
))
```

### Wait for a run to finish

Submit and poll until the run completes. Optional: `poll_interval_ms` (default 10000), `timeout_ms` (default 600000).

```python
from oleander_sdk import Oleander, SubmitSparkJobAndWaitOptions

oleander = Oleander()
result = await oleander.submit_spark_job_and_wait(SubmitSparkJobAndWaitOptions(
    namespace="my-namespace",
    name="my-job-name",
    entrypoint="my_script.py",
))
print(result.run_id, result.state)  # COMPLETE | FAIL | ABORT
```

### Get run status

```python
run = await oleander.get_run(run_id)
print(run.state, run.duration)
```

### Get Spark cluster information

```python
cluster = await oleander.get_spark_cluster("emr-prod")
print(cluster.type, cluster.properties)
```

### Typed error handling

The SDK raises structured errors for HTTP failures:

- `OleanderHttpError` for any non-2xx response (`status`, `method`, `path`, `url`, `body`, `api_error`, `api_details`)
- `RunNotFoundError` (subclass of `OleanderHttpError`) when `get_run(run_id)` returns 404

```python
from oleander_sdk import Oleander, OleanderHttpError, RunNotFoundError

try:
    await oleander.get_run(run_id)
except RunNotFoundError as err:
    print("Run is not visible yet:", err.run_id)
except OleanderHttpError as err:
    print(err.status, err.path, err.api_error or err.api_details)
```

## Options

- **Constructor**: `Oleander(api_key=..., base_url=...)`. Omit `api_key` to use `OLEANDER_API_KEY`. Set `base_url` to use a different endpoint (e.g. `http://localhost:3000`).
- **Models**: The package exports Pydantic models (e.g. `OleanderOptions`, `SparkJobSubmitOptions`) if you want to validate config or options yourself.
- **Errors**: The package exports `OleanderHttpError` and `RunNotFoundError` for structured error handling.
