Metadata-Version: 2.4
Name: wizchat-management
Version: 1.0.0
Summary: Official Python SDK for the WizChat Management API (config-as-code control plane).
Author: WizChat
License: MIT
Keywords: wizchat,chatbot,management,config-as-code,sdk
Classifier: License :: OSI Approved :: MIT License
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2
Provides-Extra: dev
Requires-Dist: datamodel-code-generator>=0.25; extra == "dev"
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# wizchat-management

Official Python SDK for the **WizChat Management API** — the programmatic
control plane for WizChat chatbots, including the config-as-code workflow
(`get_config` → edit → `apply_config`).

The typed models in `wizchat_management.models` are generated from the canonical
OpenAPI 3.1 document via [`datamodel-code-generator`](https://github.com/koxudaxi/datamodel-code-generator)
(Pydantic v2). The client is a thin [`httpx`](https://www.python-httpx.org/)
wrapper — no heavy framework dependencies. It mirrors the TypeScript SDK
(`@wizchat/management`) one-to-one in surface and semantics.

## Install

```bash
pip install wizchat-management
```

Requires Python 3.10+.

## Authentication

Authenticate with a Management API key (`wpk_live_…`). It is sent on every
request as `Authorization: Bearer <key>`. Keys are **scoped** — each operation
requires specific scopes (`chatbots:read`, `chatbots:write`, `mcp:*`,
`skills:*`, `security:*`, `domains:*`, `deploy`, `analytics:read`,
`documents:*`, `videos:*`). A request with insufficient scope raises a
`WizChatApiError` (`status == 403`, `code == "insufficient_scope"`).

```python
import os
from wizchat_management import WizChatClient

wizchat = WizChatClient(
    api_key=os.environ["WIZCHAT_API_KEY"],  # wpk_live_...
    # base_url defaults to https://www.wizchat.com — override for staging/self-host.
)
```

## Quickstart

```python
import os
from wizchat_management import WizChatClient, WizChatApiError

wizchat = WizChatClient(api_key=os.environ["WIZCHAT_API_KEY"])

try:
    # 1. List your chatbots.
    chatbots = wizchat.list_chatbots()
    chatbot_id = chatbots[0].id

    # 2. Export the current configuration as a desired-state document.
    config = wizchat.get_config(chatbot_id)

    # 3. Edit the document in memory (config-as-code).
    core = dict(config.spec.core or {})
    core["name"] = "Acme Support Bot"
    config.spec.core = core

    # 4. Preview the change with a dry-run — returns the reconcile plan, no writes.
    plan = wizchat.apply_config(chatbot_id, config, dry_run=True)
    print(plan.plan.summary)  # { create, update, delete, noop }

    # 5. Apply for real once the plan looks right.
    result = wizchat.apply_config(chatbot_id, config)
    print(result.applied)
except WizChatApiError as err:
    print(f"API error {err.status} [{err.code}]: {err.message}", err.details)
finally:
    wizchat.close()
```

`WizChatClient` is also a context manager, which closes the connection pool for
you:

```python
with WizChatClient(api_key=os.environ["WIZCHAT_API_KEY"]) as wizchat:
    chatbots = wizchat.list_chatbots()
```

### Partial-failure semantics (HTTP 422)

`apply_config` returns an `ApplyResult` for **both** full success (HTTP 200) and
**partial failure (HTTP 422)** — a 422 is *not* raised. The body is an
`ApplyResult` (`applied` / `failed` / `plan`), not an error envelope. Inspect
`result.failed` (`None` when everything applied) and re-apply after fixing the
offending section — apply is idempotent:

```python
result = wizchat.apply_config(chatbot_id, config)
if result.failed is not None:
    print("section failed:", result.failed.op, result.failed.message)
    # fix the offending section, then re-apply (idempotent)
```

Auth/validation failures (400/401/403/404/429) still raise `WizChatApiError`.

### Prune semantics

`apply` defaults to `prune=true` — array sections (`mcpServers`, `skills`,
`domains`) are **authoritative**: resources present on the chatbot but absent
from the document are **deleted**. Every delete is surfaced in the dry-run plan
first. Pass `prune=False` for merge-only (create/update, never delete):

```python
wizchat.apply_config(chatbot_id, config, prune=False)
```

`dry_run` is sent **only** as the `?dryRun=true` query param; any stray `dryRun`
on the document body is stripped so the per-call option is the single source of
truth.

## Ergonomic helpers

| Method | HTTP | Scope |
|---|---|---|
| `list_chatbots()` | `GET /api/v1/chatbots` | `chatbots:read` |
| `get_chatbot(chatbot_id)` | `GET /api/v1/chatbots/{id}` | `chatbots:read` |
| `get_config(chatbot_id)` | `GET /api/v1/chatbots/{id}/config` | `chatbots:read` (+ per-section read) |
| `apply_config(chatbot_id, document, *, dry_run=False, prune=None)` | `POST /api/v1/chatbots/{id}/apply` | per-section read (dry-run) / write (apply) |

Each helper returns a typed Pydantic model (or list) and raises
`WizChatApiError` on a non-2xx response (except the documented 422 on apply).

## Errors

```python
class WizChatApiError(Exception):
    status: int                       # HTTP status (401, 403, 404, 422, 429, …)
    code: str                         # e.g. "not_found", "insufficient_scope"
    message: str                      # human-readable
    details: dict | None              # optional structured context
```

When the body is not the standard `{ "error": { code, message, details } }`
envelope, `code` falls back to `http_<status>`.

## Generated models

All request/response payloads are typed by Pydantic v2 models in
`wizchat_management.models`, generated from the OpenAPI components. The key
types are re-exported from the package root:

```python
from wizchat_management import (
    ChatbotConfigDocument,
    ApplyResult,
    Plan,
    PublicChatbot,
    Spec,
)
```

Less-common types (e.g. `PublicMcpServer`, `PublicSkill`, `PublicTelemetry`)
live in `wizchat_management.models`.

## Development

The models are generated from the **single committed OpenAPI snapshot** shared
with the TypeScript SDK (`sdks/typescript/openapi.json`) — there is no second
copy under `sdks/python`.

```bash
python -m venv .venv
.venv/Scripts/activate          # Windows  (POSIX: source .venv/bin/activate)
pip install -e ".[dev]"

python scripts/gen.py           # regenerate wizchat_management/models.py
pytest                          # run the httpx-MockTransport suite
```

`scripts/gen.py` runs:

```bash
datamodel-codegen \
  --input ../typescript/openapi.json --input-file-type openapi \
  --output wizchat_management/models.py \
  --output-model-type pydantic_v2.BaseModel \
  --openapi-scopes schemas --use-annotated --field-constraints \
  --use-standard-collections --target-python-version 3.10 \
  --disable-timestamp --collapse-root-models --formatters black isort
```

Re-running is deterministic for a fixed input snapshot. The package version
tracks the OpenAPI `info.version`.
