Metadata-Version: 2.5
Name: picx-ai
Version: 0.2.0
Summary: Official Python SDK for the PicX AI image and video generation API.
Project-URL: Homepage, https://picxstudio.com
Project-URL: Documentation, https://ai.picxstudio.com/docs/code-examples/python-sdk
Project-URL: Source, https://github.com/Type-Think-AI/picx-sdk-python
Project-URL: Issues, https://github.com/Type-Think-AI/picx-sdk-python/issues
Author-email: PicX Studio <support@picxstudio.com>
License: MIT License
        
        Copyright (c) 2025 PicX Studio
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: api,genai,image-generation,picx,sdk,video-generation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
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: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.24
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'dev'
Requires-Dist: pytest>=7.4; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# picx-ai

Official Python client for the [PicX](https://picxstudio.com) image and video generation API.

- Sync (`PicX`) and async (`AsyncPicX`) clients with the same surface
- Typed results and a typed exception hierarchy; ships `py.typed`, passes `mypy --strict`
- Automatic retries with exponential backoff + jitter, `Retry-After` aware
- `Idempotency-Key` support on the calls that spend credits
- One runtime dependency: [`httpx`](https://www.python-httpx.org/)

> **This is not the CLI.** This package is a library you install into your own
> application. The `admin-cli/` project in this organisation builds the internal
> `picx-admin` command line binary and is unrelated to this SDK.

## Install

```bash
pip install picx-ai
# or
uv add picx-ai
```

Requires Python 3.9+.

## Quickstart

```python
import os
from picx import PicX

picx = PicX(os.environ["PICX_API_KEY"])
job = picx.video.create(prompt="sneaker on marble, slow orbit", duration=12)
asset = job.wait()
print(asset.url)
```

`PicX()` falls back to the `PICX_API_KEY` environment variable when no key is
passed, so `PicX()` works once the variable is set. Use it as a context manager
to close the connection pool deterministically:

```python
with PicX() as picx:
    asset = picx.images.generate("a red sneaker on white marble", size="2K", aspect_ratio="1:1")
    print(asset.url, asset.credits_used)
```

### Configuration

```python
picx = PicX(
    api_key="pxsk_...",  # defaults to os.environ["PICX_API_KEY"]
    base_url="https://api.picxstudio.com/v1",  # or the PICX_BASE_URL env var
    timeout=60.0,  # seconds, per request
    max_retries=2,  # retries for 429 / 5xx / connection errors
)
```

## API

| Call | Endpoint | Notes |
| --- | --- | --- |
| `picx.images.generate(prompt, model=…, size=…, aspect_ratio=…)` | `POST /images/generate` | scope `images:generate`; `size` is `1K`/`2K`/`4K`, `aspect_ratio` like `16:9` |
| `picx.images.edit(instruction, image_urls, model=…, size=…)` | `POST /images/edit` | scope `images:edit`; 1-5 image URLs |
| `picx.video.create(prompt=…, duration=…, resolution=…, sound=…, …)` | `POST /videos/generate` | scope `videos:generate`; returns **202** and a job to poll |
| `picx.generations.get(id)` | `GET /generations/{id}` | raises `NotFoundError` on 404 |
| `picx.models.list(type="image")` | `GET /models` | **public**, no API key needed |
| `picx.account.usage(period=30)` | `GET /account/usage` | |
| `picx.account.me()` | `GET /account/me` | |

`picx.images` is also available as `picx.image`, and `picx.video` as `picx.videos`.

### Images

```python
asset = picx.images.generate("a red sneaker on white marble", size="2K", aspect_ratio="1:1")
asset.id, asset.url, asset.model, asset.size, asset.aspect_ratio, asset.credits_used

edited = picx.images.edit("put it on a wet street at night", [asset.url], size="4K")
```

### Videos (202 Accepted, then poll)

`POST /videos/generate` is asynchronous server-side, so `create()` returns a job:

```python
job = picx.video.create(
    prompt="sneaker on marble, slow orbit",
    duration=12,  # server default 5
    resolution="720p",  # server default "720p"; must be priced for the model
    sound=True,  # server default True
    aspect_ratio="16:9",
    mode="image",  # "text" | "image" | "reference"
    image_url="https://…/ref.png",
    callback_url="https://example.com/webhook",
)

job.id, job.status  # "gen_…", "queued"
generation = job.wait(timeout=900, poll_interval=5)
print(generation.url)  # alias for .output_url
```

`wait()` polls `GET /generations/{id}` until a terminal status
(`succeeded`, `completed`, `failed`, `error`, `cancelled`, `canceled`).
It raises `JobFailedError` on a failed generation — pass
`raise_on_failure=False` to get the `Generation` back instead — and
`JobTimeoutError` if the timeout elapses first. `job.refresh()` polls once.

Anything left unset is omitted from the request body so the server applies its
own defaults (model `fal-ai/bytedance/seedance/v2`, duration 5, resolution
`720p`, sound on).

### Models, usage, account

```python
for model in picx.models.list(type="video"):
    print(model.id, model.name, model.credits)

usage = picx.account.usage(period=30)
print(usage.total_requests, usage.credits_used, usage.total_cost_usd, usage.model_breakdown)

me = picx.account.me()
print(me.email, me.is_active, me.credits)
```

`GET /models` is the only public endpoint, so `PicX(api_key=None).models.list()`
works without credentials.

Every result object also keeps the untouched response on `.raw`, so new API
fields are reachable before the SDK models them.

## Async

```python
import asyncio, os
from picx import AsyncPicX


async def main() -> None:
    async with AsyncPicX(os.environ["PICX_API_KEY"]) as picx:
        job = await picx.video.create(prompt="sneaker on marble, slow orbit", duration=12)
        asset = await job.wait()
        print(asset.url)

        image = await picx.images.generate("a red sneaker on white marble")
        print(image.url)


asyncio.run(main())
```

The async surface mirrors the sync one method for method — only `await` and
`aclose()`/`async with` differ. A parity test enforces this.

## Error handling

```python
from picx import (
    PicXError,  # base class: .status_code, .request_id, .body, .message
    ValidationError,  # 400 / 422, and invalid arguments caught locally
    AuthenticationError,  # 401, or no API key configured
    PermissionDeniedError,  # 403, the key lacks the required scope
    NotFoundError,  # 404
    RateLimitError,  # 429, exposes .retry_after
    ServerError,  # 5xx
    APIConnectionError,  # DNS/TLS/socket failure
    APITimeoutError,  # subclass of APIConnectionError
    JobFailedError,  # a generation ended failed/cancelled
    JobTimeoutError,  # wait() timed out, generation still running
)

try:
    asset = picx.images.generate("a red sneaker")
except RateLimitError as exc:
    print("slow down for", exc.retry_after, "seconds")
except PermissionDeniedError:
    print("this key is missing the images:generate scope")
except PicXError as exc:
    print(exc.status_code, exc.request_id, exc)
```

Errors are parsed from both response shapes the API uses — FastAPI's
`{"detail": …}` (including the 422 list form) and `{"error": …, "detail": …}`.

### Retries and idempotency

Retries apply to `429`, `5xx` and connection/timeout failures only — never to
other 4xx. Backoff is exponential with full jitter and honours `Retry-After`
(capped at 60s). `max_retries` defaults to 2; set `max_retries=0` to disable.

A `POST` is **not** replayed unless you supply an idempotency key, because
replaying it could charge twice:

```python
import uuid

asset = picx.images.generate("a red sneaker", idempotency_key=str(uuid.uuid4()))
```

The key is sent as the `Idempotency-Key` header, which the backend honours on
calls that spend credits. `idempotency_key` is available on
`images.generate`, `images.edit` and `video.create`.

### API keys are never logged

The key is redacted from `repr()`/`str()` of the client and from every exception
message and response body (anything matching `pxsk_…` becomes
`pxsk_***REDACTED***`). Read it back deliberately with `picx.api_key` if you
really need it. This is covered by tests.

## Examples

```bash
export PICX_API_KEY=pxsk_...
python examples/generate_image.py "a red sneaker on white marble"
python examples/generate_video.py "sneaker on marble, slow orbit"
python examples/generate_video.py --async
```

## Development

```bash
uv venv --python 3.13
uv pip install -e ".[dev]"
uv run mypy
uv run pytest
```

The test suite runs entirely against `httpx.MockTransport` — no network access,
no API key required.

## License

MIT
