Metadata-Version: 2.5
Name: canopy-ag-sdk
Version: 0.1.0
Summary: Official Python SDK for the Canopy Customer API (api-stg.canopy.ag/v1): openapi-python-client generated transport + a thin wrapper with env contract and API-key redaction.
License: MIT
Keywords: canopy,iot,irrigation,openapi,sdk
Requires-Python: >=3.11
Requires-Dist: attrs>=22.2.0
Requires-Dist: httpx<0.29,>=0.23.1
Requires-Dist: python-dateutil>=2.8.0
Description-Content-Type: text/markdown

# canopy-ag-sdk

Official Python SDK for the [Canopy Customer API](https://api-stg.canopy.ag/v1) —
transport generated from the committed public spec (`spec/public/openapi.json`)
by [openapi-python-client](https://github.com/openapi-generators/openapi-python-client),
wrapped by a thin hand-written layer that owns the env contract and API-key
redaction.

## Install

```sh
pip install canopy-ag-sdk
```

> **Package-name note:** the distribution is `canopy-ag-sdk` but the import
> names are `canopy_sdk` (wrapper) and `canopy_sdk_client` (generated
> transport) — a Python distribution name and its import name need not match.
> The distribution is **not** `canopy-sdk`: that name was already taken on PyPI
> by an unrelated project (Pinecone's RAG framework / context engine) before
> Canopy's first publish, so `canopy-ag-sdk` was chosen to mirror the
> `@canopy-ag/` npm scope used by the TypeScript SDK. That squatted
> distribution installs top-level `canopy` / `canopy_cli` / `canopy_server`, so
> it does not collide with our import names even if both are installed. See
> [`docs/ci/public-sdk-publishing.md`](../../../docs/ci/public-sdk-publishing.md)
> (`.github/workflows/publish-public-sdk.yml`) for how publishing (PyPI OIDC
> trusted publishing — no token) and versioning (the version tracks
> `spec/public/openapi.json` `info.version`) work.

Requires Python >= 3.11 (the generated client pins `httpx >= 0.23.1, < 0.29`).

## Quickstart — list irrigation zones

Only `CANOPY_API_KEY` needs to be set (7 lines of code):

```python
from canopy_sdk import CanopyClient
from canopy_sdk_client.api.irrigation_zones import irrigation_zones_controller_find_all

client = CanopyClient()  # reads CANOPY_API_KEY
zones = irrigation_zones_controller_find_all.sync(
    client=client.raw, x_tenant_id="<your-tenant-uuid>"
)
print(zones)
```

Every operation in the public spec has a module under `canopy_sdk_client.api.*`
with `sync` / `sync_detailed` / `asyncio` / `asyncio_detailed` variants; pass
`client=client.raw` to any of them.

## Environment contract

| Variable              | Required | Behavior                                                                                |
| --------------------- | -------- | --------------------------------------------------------------------------------------- |
| `CANOPY_API_KEY`      | yes      | `CanopyClient()` **fails fast** with `MissingApiKeyError` naming this variable if unset. |
| `CANOPY_API_BASE_URL` | no       | Overrides the default base URL `https://api-stg.canopy.ag/v1` (`DEFAULT_BASE_URL`).      |

Both can also be passed programmatically — `CanopyClient(api_key=..., base_url=...)`
(arguments win over the environment).

## Key redaction guarantee

The API key never appears in:

- `repr()` / `str()` of `CanopyClient` (rendered as `[REDACTED]`),
- exceptions raised through the request path (scrubbed by a wrapping transport),
- httpx logs at default (INFO) level.

Pinned by the leak tests in `tests/test_redaction.py` (written first, per the
story brief). **Do not `repr()` the raw generated client** (`client.raw`) —
its attrs-generated repr is not redacted; treat it as a transport handle only.

## Regenerating the transport

`src/canopy_sdk_client/` is generated code — committed, drift-gated in CI
(`sdk-python-drift` in `.github/workflows/pr-checks.yml`), and never hand-edited.
After any change to `spec/public/openapi.json`:

```sh
npx nx run sdk-python:generate   # or: bash libs/sdk/python/tools/generate.sh
```

Generator + ruff post-hook versions are pinned inside `tools/generate.sh` so
regeneration is byte-identical everywhere.

## Development

```sh
npx nx run sdk-python:install    # uv sync
npx nx run sdk-python:test       # uv run pytest tests/ -v
npx nx run sdk-python:lint       # ruff check on the hand-written surface
npx nx run sdk-python:build      # uv build (wheel + sdist)
```
