# Inhibitor Python SDK

**Official Python client for the Inhibitor policy compliance API.** Evaluate natural-language content against your policies and get **ALLOW**, **MODIFY**, or **BLOCK** decisions with explanations.

---

## Features

- **Simple API** — Send only `query`, `metadata`, and `additional_metadata`; no extra parameters.
- **Structured metadata** — Use the `{"key": {"value": "val", "description": "des"}}` shape for clear, documented context.
- **Sync and async** — `InhibitorClient` and `AsyncInhibitorClient` with the same interface.
- **Typed models** — Pydantic request/response models for IDE support and validation.

---

## Installation

```bash
pip install inhibitor
```

**Requirements:** Python 3.9+, `httpx`, `pydantic`.

---

## Quick start

```python
from inhibitor import InhibitorClient

client = InhibitorClient(api_key="your-api-key")

response = client.inference.query(
    "Can I share this document with external partners?",
    metadata={
        "consent": {"value": "yes", "description": "User gave consent"},
        "data_type": {"value": "PHI", "description": "Contains protected health info"},
    },
)

print(response.final_decision)   # ALLOW | MODIFY | BLOCK
print(response.explanation)      # Human-readable explanation
print(response.suggestion)       # Suggested rewording (if MODIFY)
print(response.processing_time_seconds)
```

```python
client = InhibitorClient(api_key="your-api-key", base_url="https://your-custom-api.com")
```

---

## API overview

### What you send (request)

Only three fields are accepted:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| **query** | `str` | Yes | Natural language content to evaluate against your policies. |
| **metadata** | `dict` | No | Context for compliance. Shape: `{"key": {"value": "val", "description": "des"}}`. Used by the engine for the decision. |
| **additional_metadata** | `dict` | No | Stores optional user information (e.g., name, email) as key-value pairs, saved along with the inference log for auditing purposes. Example: {"user_name": "John Doe", "email": "john.doe@example.com"} |

**Metadata shape** — Each key maps to an object with:

- **value** (str): The actual value (e.g. `"yes"`, `"PHI"`).
- **description** (str): Optional short description of the field.

Example:

```python
metadata = {
    "consent": {"value": "yes", "description": "User consent obtained"},
    "classification": {"value": "internal", "description": "Document classification"},
}
```

### What you get (response)

`InferenceResponse` includes:

| Field | Type | Description |
|-------|------|-------------|
| `query` | str | Echo of the query. |
| `final_decision` | str | `ALLOW`, `MODIFY`, or `BLOCK`. |
| `final_score` | float | Aggregate score (0–1). |
| `matched_policies` | list[str] | Policies that were evaluated. |
| `model_results` | list[RuleResult] | Per-rule decision, confidence, and probabilities. |
| `explanation` | str \| None | Human-readable explanation. |
| `make_it_complient` | str \| None \| Suggested changes for the query. |

---

## Usage

### Sync client

```python
from inhibitor import InhibitorClient, InferenceRequest

client = InhibitorClient(api_key="your-api-key")

# Simple query
response = client.inference.query("Should I send this email to a competitor?")

# With metadata (authoritative context for the engine)
response = client.inference.query(
    "Share the report with the client.",
    metadata={
        "consent": {"value": "yes", "description": "Client consent on file"},
        "channel": {"value": "email", "description": "Delivery channel"},
    },
)

# With additional_metadata (stored with log only)
response = client.inference.query(
    "Upload to cloud storage.",
    additional_metadata={"user_name": "John Doe", "email": "john.doe@example.com"},
)

# Using the request model
req = InferenceRequest(
    query="Can I export this data?",
    metadata={"data_type": {"value": "PII", "description": "Personal data"}},
)
response = client.inference.run(req)
```

### Async client

```python
from inhibitor import AsyncInhibitorClient

async with AsyncInhibitorClient(api_key="your-api-key") as client:
    response = await client.inference.query_async(
        "Can I share this document?",
        metadata={"consent": {"value": "yes", "description": "Consent given"}},
    )
    print(response.final_decision)

# Or with run_async
req = InferenceRequest(query="...", metadata={...})
response = await client.inference.run_async(req)
```

### Configuration

| Parameter | Description |
|-----------|-------------|
| `api_key` | **Required.** Your Inhibitor API key (from the dashboard). |
| `base_url` | Override API base URL. Default: `https://inhibitor-be.envistudios.com`. |
| `timeout` | Request timeout in seconds (default: `60.0`). |
| `headers` | Optional extra HTTP headers. |

The client sends the API key in the `X-API-Key` header. No other auth is required.

---

## Exceptions

| Exception | When |
|-----------|------|
| **InhibitorAuthError** | Invalid or expired API key (401). |
| **InhibitorValidationError** | Bad request or invalid parameters (400). |
| **InhibitorAPIError** | Other API errors (4xx/5xx). Has `status_code`, `detail`, `response_body`. |

All extend **InhibitorError** (with `message` and optional `status_code`).

```python
from inhibitor import InhibitorClient
from inhibitor.exceptions import InhibitorAuthError, InhibitorAPIError

client = InhibitorClient(api_key="your-api-key")
try:
    r = client.inference.query("Some query")
except InhibitorAuthError as e:
    print("Auth failed:", e.message)
except InhibitorAPIError as e:
    print("API error:", e.status_code, e.detail)
```

---

## Integration checklist

1. **Get an API key** from your Inhibitor dashboard.
2. **Install** — `pip install inhibitor`.
3. **Create client** — `InhibitorClient(api_key="...")`.
4. **Call** — `client.inference.query(query, metadata=..., additional_metadata=...)`.
5. **Handle** — Use `response.final_decision`, `response.explanation`, and catch `InhibitorAuthError` / `InhibitorAPIError` as needed.

---

## Package structure

Each module has a single responsibility:

| Module | Purpose |
|--------|---------|
| `inhibitor.constants` | Default base URL and API path. |
| `inhibitor._http` | HTTP response helpers (e.g. error detail extraction). |
| `inhibitor.models` | Request/response Pydantic models and metadata shape. |
| `inhibitor.inference` | Inference API (sync/async): `query`, `run`, `query_async`, `run_async`. |
| `inhibitor.client` | Sync and async HTTP clients. |
| `inhibitor.exceptions` | Auth, validation, and API exceptions. |

Import the public API from `inhibitor`; use submodules only if you need constants or helpers.

---

## License

MIT
