Metadata-Version: 2.4
Name: cleanlib-sdk
Version: 0.4.1
Summary: CleanLibrary Python SDK — mirrors cleanlib-client (Rust SDK) HTTP surface + cycle-5 cosign verdict-attestation
Project-URL: Homepage, https://bitbucket.org/triamsec/cleanlib-sdk-py
Project-URL: Source, https://bitbucket.org/triamsec/cleanlib-sdk-py
Project-URL: Documentation, https://bitbucket.org/triamsec/cleanlib/src/main/workstream-specs/05-app/sdk-substrate-rev1.md
Author: CleanStart
License: Proprietary
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# cleanlib-sdk-py

CleanLibrary Python SDK — `asyncio` + `httpx`; mirrors the Rust `cleanlib-client` HTTP surface.

**Status**: `v0.1.0-substrate` (cycle-4 §D.1). Substrate-only — `fetch_verdict` ships; remaining verbs (`scan` / `audit_recent` / `policy_preview` / `fetch_bytes`) iterate cycle-5+.

## Install (cycle-5+; not yet on PyPI)

```bash
pip install cleanlib-sdk
```

For cycle-4 substrate, install from this repo:

```bash
pip install git+https://bitbucket.org/triamsec/cleanlib-sdk-py.git
```

## Usage

```python
import asyncio
from cleanlib_sdk import Client, PolicyDenyError, RiskAcceptanceRequiredError

async def main() -> None:
    async with Client(
        endpoint="https://cleanapp.clnstrt.dev",
        api_key="clk_std_...",   # opaque CleanLibrary access key
    ) as c:
        try:
            v = await c.fetch_verdict("npm", "lodash", "4.17.21")
            print(f"{v.decision} composite_score={v.composite_score}")
            print(f"reasoning: {v.reasoning}")
        except PolicyDenyError as e:
            print(f"DENIED [{e.reason_code}]: {e.message}")
        except RiskAcceptanceRequiredError as e:
            print(f"RISK ACCEPT REQUIRED: {e.message}")
            if e.docs_url:
                print(f"see: {e.docs_url}")

asyncio.run(main())
```

## Error hierarchy

All errors descend from `CleanLibraryError`. Subclasses:

| Exception | HTTP | Triggered by |
|---|---|---|
| `PolicyDenyError` | 403 / 451 | `POLICY_DENY_VERDICT` / `POLICY_DENY_RULE_EXPLICIT` |
| `IntegrityFailureError` | 403 | `INTEGRITY_FAILURE` |
| `RateLimitExceededError` | 429 | tier-throttled; carries `retry_after_seconds` |
| `RiskAcceptanceRequiredError` | 403 | `RISK_ACCEPTANCE_REQUIRED` |
| `AuthenticationError` | 401 / 403 | `KEY_INVALID` / `KEY_EXPIRED` / `KEY_SCOPE_INSUFFICIENT` |
| `InsufficientDataError` | 403 | `INSUFFICIENT_DATA_FAIL_CLOSED` |
| `PackageNotFoundError` | 404 | not in catalog + ingest declined |
| `ServerError` | 5xx | retryable on 502/503/504 |
| `TransportError` | — | network / TLS / timeout / DNS |
| `ParseError` | — | response body shape mismatch |

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check .
```

## Cross-references

- [App workstream substrate spec](https://bitbucket.org/triamsec/cleanlib/src/main/workstream-specs/05-app/sdk-substrate-rev1.md) — canonical surface design
- [Rust cleanlib-client](https://bitbucket.org/triamsec/cleanlib/src/main/cleanlib-client/) — reference implementation
- [App customer endpoint contract](https://bitbucket.org/triamsec/cleanlib/src/main/workstream-specs/05-app/app-scope-outline-rev4.md) §9 — HTTP surface contract

## License

Proprietary — CleanStart.
