Metadata-Version: 2.4
Name: consentguard
Version: 0.1.0
Summary: ConsentGuard Python SDK
Requires-Python: >=3.9
Requires-Dist: httpx<1.0,>=0.27
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# ConsentGuard Python SDK

Govern AI tool execution with the stable ConsentGuard SDK entry points:

- `ConsentGuardClient`
- `wrap()`

## Install

```powershell
python -m pip install consentguard
```

For local repo testing on Windows PowerShell:

```powershell
python -m pip install -e .\sdk\python
```

Editable installs link to the source package. Fix package issues in `sdk/python`, not in a copied site-packages directory.

## Minimal Usage

```python
import os

from consentguard import ConsentGuardClient, wrap

client = ConsentGuardClient(
    api_key=os.environ["CONSENTGUARD_API_KEY"],
    base_url="https://your-project.supabase.co/functions/v1",
)


@wrap(client=client, tool_name="transfer_money", timeout=90)
async def send_payment(amount: int) -> dict:
    return {"id": "payment_123", "amount": amount}


result = await send_payment(250)

print(result.data)
print(result.action_id)
print(result.risk_score)
print(result.risk_level)
print(result.decision)
print(result.reasons)
```

`wrap()` returns ConsentGuard metadata around the tool result, not the raw tool result directly. Read the original tool value from `result.data`.

## Client Options

Minimal production config:

```python
client = ConsentGuardClient(
    api_key=os.environ["CONSENTGUARD_API_KEY"],
    base_url="https://your-project.supabase.co/functions/v1",
)
```

Advanced config:

```python
client = ConsentGuardClient(
    api_key=os.environ["CONSENTGUARD_API_KEY"],
    base_url=os.environ["CONSENTGUARD_BASE_URL"],
    poll_interval_seconds=2.0,
    retry={
        "max_retries": 2,
        "initial_delay_seconds": 0.2,
        "max_delay_seconds": 2.0,
        "backoff_multiplier": 2.0,
        "retryable_status_codes": {429, 500, 502, 503, 504},
    },
)
```

- `api_key`: required production API key, usually `cg_live_*`.
- `base_url`: Supabase Edge Functions base URL ending in `/functions/v1`. The default is a placeholder and should be set before production use.
- `poll_interval_seconds`: consent status polling interval in seconds. Defaults to `2.0`.
- `retry`: backend/network retry policy. Retries apply to retryable infrastructure failures, not governance `block` or denied consent decisions.

## Timeout Units

- `timeout` in `wrap()` config is the consent approval timeout in seconds. Example: `timeout=90`.
- `poll_interval_seconds` in `ConsentGuardClient` is the consent status polling interval in seconds.
- Backend and network failures are surfaced through `ConsentGuardNetworkError` subclasses, not as consent approval timeouts.

## Wrapper Result Shape

```python
result = await guarded_tool(250)

print(result.data)
print(result.action_id)
print(result.risk_score)
print(result.risk_level)
print(result.decision)
print(result.reasons)
```

## Tool Name Resolution

ConsentGuard sends a `tool_name` to the governance backend. Name-based rules only match if this name is stable and meaningful.

Precedence:

1. `tool_name`
2. the wrapped function's inferred `__name__`
3. `"anonymous_tool"` with a warning

Named function:

```python
@wrap(client=client)
async def transfer_money(amount: int) -> dict:
    return {"status": "sent", "amount": amount}
```

Direct wrap form:

```python
async def transfer_money(amount: int) -> dict:
    return {"status": "sent", "amount": amount}

guarded = wrap(transfer_money, client=client)
```

Anonymous function with explicit override:

```python
guarded = wrap(lambda amount: {"status": "sent", "amount": amount}, client=client, tool_name="transfer_money")
```

If an anonymous function has no `tool_name`, ConsentGuard uses `"anonymous_tool"` and warns that name-based governance rules will not match.

## Error Handling

```python
from consentguard import (
    ConsentGuardAuthError,
    ConsentGuardBlockedError,
    ConsentGuardConsentTimeoutError,
    ConsentGuardDeniedError,
    ConsentGuardNetworkError,
    ConsentGuardRateLimitedError,
    ConsentGuardServiceUnavailableError,
)

try:
    result = await send_payment(12500)
    print(result.data, result.decision, result.reasons)
except ConsentGuardBlockedError as error:
    print("Blocked by policy", error.action_id)
except ConsentGuardDeniedError as error:
    print("Consent denied", error.action_id)
except ConsentGuardConsentTimeoutError as error:
    print("Consent approval timed out", error.action_id)
except ConsentGuardAuthError as error:
    print("Invalid API key or base URL", error.code)
except ConsentGuardRateLimitedError as error:
    print("Rate limited", error.retry_after_seconds)
except ConsentGuardServiceUnavailableError:
    print("ConsentGuard service unavailable")
except ConsentGuardNetworkError as error:
    print("Network/backend failure", error.code, error.retryable)
```

Hierarchy:

```text
ConsentGuardError
  ConsentGuardBlockedError
  ConsentGuardDeniedError
  ConsentGuardTimeoutError
    ConsentGuardConsentTimeoutError
  ConsentGuardNetworkError
    ConsentGuardAuthError
    ConsentGuardRateLimitedError
    ConsentGuardServiceUnavailableError
    ConsentGuardInvalidResponseError
```

Retryable errors expose `retryable = True`. Auth and invalid response errors are not retryable.

## Synchronous Frameworks

The Python SDK is async-first. In plain scripts, call governed tools from an async main:

```python
import asyncio

asyncio.run(send_payment(250))
```

Framework guidance:

- Streamlit: use a small bridge that calls `asyncio.run()` when no loop is active, or runs the coroutine in a worker thread when a loop is already active.
- Flask and Django sync views: prefer moving governed tool execution into an async view/task where possible. If you must bridge, do it at the view boundary and avoid nested `asyncio.run()` inside an already running loop.
- CLI scripts: `asyncio.run()` is usually enough.

See the Streamlit example for a concrete bridge:

```powershell
cd .\sdk\python\examples\streamlit-basic
python -m pip install -r requirements.txt
python -m pip install -e ..\..
streamlit run app.py
```

## Stability

For v0.1.0, `wrap()`, `ConsentGuardClient`, the decision vocabulary, and the exported error classes are the stable public entry points. Additive config fields and result metadata may expand during the Phase 4 readiness pass, but internal governance contracts are not yet a public extension API. Do not depend on private module internals.
