Metadata-Version: 2.5
Name: pysepal-api
Version: 0.4.0
Summary: UI-free HTTP client for SEPAL platform services (user files, tasks, processing recipes).
Project-URL: Homepage, https://dfguerrerom.github.io/pysepal-api/
Project-URL: Documentation, https://dfguerrerom.github.io/pysepal-api/
Project-URL: Repository, https://github.com/dfguerrerom/pysepal-api
Project-URL: Changelog, https://github.com/dfguerrerom/pysepal-api/blob/main/CHANGELOG.md
Author-email: Daniel Guerrero <dfgm2006@gmail.com>
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Provides-Extra: dev
Requires-Dist: black>=24; extra == 'dev'
Requires-Dist: commitizen>=3.29; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: nox>=2024.4.15; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: docs
Requires-Dist: black>=24; extra == 'docs'
Requires-Dist: griffe>=1.0; extra == 'docs'
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.6; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
Provides-Extra: ee
Requires-Dist: earthengine-api>=1.5; extra == 'ee'
Description-Content-Type: text/markdown

# pysepal-api

📖 **[Documentation](https://dfguerrerom.github.io/pysepal-api/)**

UI-free HTTP client for SEPAL platform services. Powers Jupyter notebooks, Voila apps, CLI scripts, and background jobs running inside a SEPAL sandbox.

```python
from pysepal_api import SepalClient

# sync — notebooks, CLI, jobs
with SepalClient(session_id="...") as sepal:
    listing = sepal.files.list("/")
    task = sepal.tasks.submit("image.download", params={...})
    sepal.tasks.wait(task.id)

    # escape hatch: any route the typed endpoints don't model
    image = sepal.get("/api/gee/image/json", params={"recipeId": "foo"}).json()
```

```python
from pysepal_api import AsyncSepalClient

# async — Voila / Solara
async with AsyncSepalClient(session_id="...") as sepal:
    listing = await sepal.files.list("/")
    image = (await sepal.get("/api/gee/image/json", params={"recipeId": "foo"})).json()
```

The two clients are twins: identical surface, the only difference is `await`.

- **Scoped use** — `with SepalClient(...)` / `async with AsyncSepalClient(...)`. Construction never performs network I/O; entering the context creates the module results dir (when `module_name` is given).
- **Long-lived clients** — `sepal = SepalClient.create(...)` / `sepal = await AsyncSepalClient.create(...)`: same eager setup without a context manager; call `close()` / `aclose()` when done. `async with AsyncSepalClient.create(...)` also works.
- **Typed endpoints** for the common cases: `files`, `tasks`, `recipes`, `ee_exports`. File verbs follow `pathlib`: `read_bytes` / `read_text` / `read_json` / `write` / `mkdir`.
- **Generic primitive** — `request()` / `get()` / `post()` / `put()` / `delete()` — for any other SEPAL route, with the same typed-error mapping (`NotFound`, `Unauthorized`, …). Returns the raw `httpx.Response`.
- **One exception root** — catch `PysepalError` for anything this library raises.

TLS is verified by default. `host.docker.internal` is auto-skipped for local dev; for any other self-signed host pass `verify=False` or list it in `PYSEPAL_INSECURE_TLS_HOSTS` (comma-separated, exact-host match).

## Earth Engine exports

`sepal.ee_exports` (alias `sepal.exports`) routes notebook-created `ee.Image` and `ee.FeatureCollection` exports through SEPAL's TaskManager. It serializes the EE object into a Cloud API expression and submits an `ee.export` task — no new HTTP route, so `tasks.wait(task.id)` tracks it like any other task.

Requires the SEPAL backend adapter in [openforis/sepal#454](https://github.com/openforis/sepal/pull/454). See [the export notebook](examples/ee_exports.ipynb) for a runnable sandbox example.

Serializing a live EE object needs the optional Earth Engine extra:

```bash
pip install "pysepal-api[ee]"
```

`submit_image(...)` and `submit_table(...)` each take a `destination` of `"SEPAL"`, `"DRIVE"`, or `"GEE"`:

```python
import ee
from pysepal_api import SepalClient

ee.Initialize()
image = ee.Image("COPERNICUS/S2_SR/...").normalizedDifference(["B8", "B4"])
region = ee.Geometry.Rectangle([0, 0, 1, 1])

with SepalClient(session_id="...", module_name="my_module") as sepal:
    # → SEPAL sandbox. workspace_path is RELATIVE to your SEPAL home dir.
    task = sepal.ee_exports.submit_image(
        image=image, region=region, destination="SEPAL",
        name="ndvi_2026", scale=30,
        workspace_path="module_results/my_module/exports",
    )

    # → Google Drive
    sepal.ee_exports.submit_image(
        image=image, region=region, destination="DRIVE",
        name="ndvi_2026", scale=30, drive_folder="SEPAL/exports",
    )

    # → Earth Engine asset (asset_id is required for GEE)
    sepal.ee_exports.submit_image(
        image=image, region=region, destination="GEE",
        name="ndvi_2026", scale=30,
        asset_id="projects/my-project/assets/exports/ndvi_2026",
    )

    # FeatureCollection → asset
    sepal.ee_exports.submit_table(
        collection=ee.FeatureCollection("..."), destination="GEE",
        name="plots", asset_id="projects/my-project/assets/exports/plots",
    )

    sepal.tasks.wait(task.id)
```

Notes:

- `workspace_path` is **relative to your SEPAL home directory** — never pass an absolute path (the worker rejects `..` traversal).
- `destination="GEE"` requires `asset_id`; omitting it raises `ValueError` before any network call.
- `image` / `collection` / `region` accept either a live EE object (encoded for you) or an already-encoded Cloud API expression dict (no `[ee]` extra needed).
- Table `max_vertices` is supported by the current backend only for Drive exports. ImageCollection asset exports require SEPAL's tiled recipe workflow.
- The async twin `AsyncSepalClient.ee_exports` has the identical surface, differing only by `await`.
