Metadata-Version: 2.4
Name: imagen-sdk
Version: 0.1.2
Summary: A client library for generating images through the flix-imagen API
Project-URL: Homepage, https://bitbucket.org/flixstock-dev/imagen-sdk
Project-URL: Repository, https://bitbucket.org/flixstock-dev/imagen-sdk
Project-URL: Issues, https://bitbucket.org/flixstock-dev/imagen-sdk/issues
Author-email: Flixstock <dev@flixstock.com>
License-Expression: LicenseRef-Flixstock-Proprietary
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary 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 :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: >=3.9
Requires-Dist: cryptography>=42.0.0
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: tox>=4.0; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

# Imagen SDK

A proprietary client library for generating images through the [flix-imagen](https://bitbucket.org/flixstock-dev/flix-imagen) API.

**Authorized Flixstock users only.** A signed runtime license file is required before the SDK can be used.

## Python compatibility

- **Supported:** Python 3.9 through 3.14 (CPython and PyPy)
- **Wheel:** one universal `py3-none-any` wheel works across all supported versions
- **Floor:** Python 3.9 (required by the `cryptography` dependency)

Test locally across versions (requires [tox](https://tox.wiki/) and multiple Python installs):

```bash
pip install -e ".[dev]"
tox
```

CI runs the test suite on Python 3.9–3.13 via `bitbucket-pipelines.yml`.

## Installation

```bash
pip install imagen-sdk
```

## Runtime license file

Before creating a client, obtain an RSA-signed license file from Flixstock. The SDK verifies signatures using a **public key bundled in the package**; the private key never ships with the SDK.

The SDK checks for a license in this order:

1. `license_path` argument to `ImagenClient`
2. `IMAGEN_SDK_LICENSE` environment variable
3. `./imagen-sdk.license` or `./license.json` in the working directory
4. `~/.config/imagen-sdk/license.json`

Example license format: see `license.example.json`.

Allowed email domains: `@flixstock.com`, `@imageedit.ai`.

### Key setup (Flixstock admins, one-time)

Generate an RSA key pair. The public key is committed to the repo; the private key stays local.

```bash
pip install cryptography
python scripts/generate_keys.py
```

- Public key: `src/imagen_sdk/keys/license_public.pem` (bundled in the wheel)
- Private key: `keys/license_private.pem` (gitignored — store securely)

### Issue a license (Flixstock admins)

```bash
python scripts/issue_license.py \
  --licensee "Jane Doe" \
  --email "jane.doe@flixstock.com" \
  --output imagen-sdk.license
```

Optional: `--private-key /path/to/license_private.pem` or set `IMAGEN_SDK_PRIVATE_KEY`.

## Usage

**Full user guide:** see [`USAGE.md`](USAGE.md) for the complete instruction set —
all generation modes, accepted parameters, and per-model allowed values (aspect
ratios, resolutions, output formats, image counts).

The SDK is a production workflow layer over the flix-imagen HTTP API. It handles
validation, model-agnostic parameter normalization, job creation, polling,
output downloads, batch orchestration with manifests/resume, typed errors, and
idempotency. See [`requirements.md`](requirements.md) for the full design and
what the backend currently supports.

### Client setup

```python
from imagen_sdk import ImagenClient

client = ImagenClient(
    api_key="pk-your-project-key",                 # or "uk-..." user key
    base_url="https://api-fliximagen.flixstock.com",
    # license_path="/path/to/imagen-sdk.license",  # or via IMAGEN_SDK_LICENSE
    max_concurrency=10,
    timeout=60,
    poll_interval=2,
    max_poll_interval=15,
    log_level="INFO",
)
```

A signed runtime license is still required (see below). Authentication to the
API uses the `X-API-Key` header. With a `uk-` user key, pass `project_name=...`
to `generate(...)` or set it on the client.

### Single generation

```python
result = client.generate(
    model="nano_banana_2",                         # stable SDK alias from provider_registry.json
    prompt="Female model wearing a black blazer and white trousers",
    input_image="https://cdn.example.com/inputs/blazer.jpg",  # hosted HTTPS URL
    output_path="./outputs/blazer.png",
    parameters={
        "aspect_ratio": "3:4",
        "image_count": 1,
        "resolution": "2K",
        "output_format": "png",
    },
)

print(result.job_id, result.status)
print(result.outputs[0].saved_to)
```

Models are referenced by stable aliases (`ImagenClient.available_models()`):
`nano_banana_2`, `nano_banana_pro`, `nano_banana_2_edit`,
`nano_banana_pro_edit`, `gpt_image_2_edit`, `gpt_4o`. You can also pass a raw model:
`generate(model="fal:fal-ai/flux/dev", ...)` or
`generate(model="gemini-3.1-flash-image-preview", provider="vertex", ...)`.

### Batch generation with manifest + resume

```python
batch = client.generate_many(
    requests=[
        {
            "request_id": "sku_001",
            "model": "nano_banana_2",
            "prompt": "Model wearing SKU 001",
            "input_image": "https://cdn.example.com/inputs/sku_001.jpg",
            "output_path": "./outputs/sku_001.png",
            "parameters": {"aspect_ratio": "3:4", "output_format": "png"},
        },
    ],
    concurrency=10,
    save_manifest="./outputs/manifest.json",
    continue_on_error=True,
)

print(batch.total, batch.succeeded, batch.failed)

# Resume an interrupted batch (skips already-succeeded items):
client.resume_batch("./outputs/manifest.json", requests)
```

### Async client

```python
import asyncio
from imagen_sdk import AsyncImagenClient

async def main():
    async with AsyncImagenClient(api_key="pk-...") as client:
        results = await client.generate_many(
            [
                {"model": "nano_banana_2", "prompt": "red dress",
                 "input_image": "https://cdn/1.jpg", "output_path": "./out/1.png"},
                {"model": "nano_banana_2", "prompt": "blue dress",
                 "input_image": "https://cdn/2.jpg", "output_path": "./out/2.png"},
            ],
            concurrency=10,
        )
        print(results.succeeded)

asyncio.run(main())
```

### Dry run

```python
preview = client.generate(
    model="nano_banana_2",
    prompt="denim jacket",
    input_image="https://cdn/in.jpg",
    parameters={"aspect_ratio": "3:4", "image_count": 4},
    dry_run=True,
)
print(preview.normalized_payload)   # provider input_fields, no job created
print(preview.uploads_required)
```

### Low-level API

```python
dto = client.create_job(model="nano_banana_2", prompt="...",
                        input_image="https://cdn/in.jpg")
status = client.wait_for_job(dto["id"])
client.download_outputs(status["output_urls"], job_id=dto["id"],
                        output_dir="./outputs/")
```

### Errors

```python
from imagen_sdk import JobFailedError, ValidationError, RateLimitError

try:
    client.generate(model="nano_banana_2", prompt="...")
except ValidationError as exc:      # caught locally before any API call
    print(exc.details)
except JobFailedError as exc:
    print(exc.job_id, exc.error_code, exc.retryable)
```

All errors derive from `SDKError` and carry `code`, `status_code`, `job_id`,
`request_id`, and `retryable`.

### Output overwrite policy

`generate(..., on_existing=...)` accepts `error` (default), `overwrite`,
`rename` (writes `out_1.webp`, `out_2.webp`, ...), or `skip`.

### Local file uploads

Local files (`input_image="./in.jpg"`, `Path(...)`, etc.) are uploaded
automatically to S3/CDN via the Flixstudio **Pegasus** presign service — the
same flow the web UI uses:

1. `POST https://api-pegasus.flixstudio.io/api/v1/util/s3PutPreSignedUrl`
   with `{"key", "bucketName", "mimeType"}` → `{"url": "<signed PUT URL>"}`
2. `PUT` the bytes to the signed URL
3. The public URL `https://fxgati.flixstock.com/<key>` is passed into the job

```python
result = client.generate(
    model="nano_banana_2",
    prompt="red dress",
    input_image="./inputs/front.jpg",   # local file -> uploaded automatically
    output_path="./outputs/front.png",
)
# Or upload manually:
up = client.upload_file("./inputs/front.jpg")
print(up.cdn_url)
```

Configurable on the client: `enable_uploads` (default `True`),
`pegasus_presign_url`, `upload_bucket` (`fxgati`), `cdn_host`
(`fxgati.flixstock.com`), `upload_key_prefix` (`Flix_ImageGen`), and
`pegasus_token` (sent as `Authorization: Bearer` if the service requires it).
Set `enable_uploads=False` to force hosted-URL-only inputs.

### Backend limitations (current flix-imagen state)

- **Cancellation, webhooks, and server-side idempotency are not yet available.**
  `cancel_job()` raises `NotSupportedError`; the SDK still sends an
  `Idempotency-Key` header so it works the moment the backend adds support.

### Legacy construction

```python
from imagen_sdk import ImagenClient, LicenseError

try:
    client = ImagenClient(api_key="pk-...", license_path="/path/to/imagen-sdk.license")
except LicenseError as exc:
    print(exc)
```

Or set the environment variable:

```bash
export IMAGEN_SDK_LICENSE=/path/to/imagen-sdk.license
```

## Development

```bash
python -m venv .venv
.venv\Scripts\activate          # Windows
# source .venv/bin/activate     # macOS/Linux

pip install -e ".[dev]"
python scripts/generate_keys.py   # if keys do not exist yet
python scripts/issue_license.py --licensee "Dev User" --email "dev@flixstock.com"
pytest
```

## Publishing to PyPI

### CI/CD (Bitbucket Pipelines)

Releases are published automatically when you push a version tag.

1. Bump `version` in `pyproject.toml` and `__version__` in `src/imagen_sdk/__init__.py`.
2. Commit and push.
3. Create and push a matching tag:

```bash
git tag v0.0.2
git push origin v0.0.2
```

The `v*` tag pipeline runs tests, verifies the tag matches the package version, builds, and uploads to PyPI.

**Manual TestPyPI publish:** Pipelines → Run pipeline → `publish-testpypi`.

#### Bitbucket repository variables

Add these under **Repository settings → Pipelines → Repository variables**:

| Variable | Secured | Value |
|---|---|---|
| `TWINE_USERNAME` | No | `__token__` |
| `TWINE_PASSWORD` | **Yes** | Production PyPI API token (full value, including `pypi-` prefix) |
| `TESTPYPI_TWINE_PASSWORD` | **Yes** | TestPyPI API token (only needed for the `publish-testpypi` custom pipeline) |

Create tokens at:

- Production: [pypi.org/manage/account/#api-tokens](https://pypi.org/manage/account/#api-tokens)
- Test: [test.pypi.org/manage/account/#api-tokens](https://test.pypi.org/manage/account/#api-tokens)

Scope tokens to the `imagen-sdk` project when possible.

### Manual publish

Follow the [Python Packaging User Guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/).

### 1. Install build tools

```bash
pip install --upgrade pip build twine
```

### 2. Bump the version

Update `version` in `pyproject.toml` and `__version__` in `src/imagen_sdk/__init__.py`. PyPI does not allow re-uploading the same version.

### 3. Build distribution archives

```bash
# Windows PowerShell
Remove-Item -Recurse -Force dist, build -ErrorAction SilentlyContinue
python -m build
```

This produces in `dist/`:

- `imagen_sdk-<version>.tar.gz` — source distribution
- `imagen_sdk-<version>-py3-none-any.whl` — wheel

### 4. Test on TestPyPI (recommended)

1. Register at [test.pypi.org](https://test.pypi.org/account/register/) and verify your email.
2. Create an API token at [test.pypi.org/manage/account/#api-tokens](https://test.pypi.org/manage/account/#api-tokens).
3. Upload:

```bash
python -m twine upload --repository testpypi dist/*
```

When prompted:

- **Username**: `__token__`
- **Password**: your API token (including the `pypi-` prefix)

4. Verify install:

```bash
pip install --index-url https://test.pypi.org/simple/ --no-deps imagen-sdk
```

5. Check: `https://test.pypi.org/project/imagen-sdk/`

### 5. Publish to PyPI

1. Register at [pypi.org](https://pypi.org/account/register/) (separate from TestPyPI).
2. Confirm `imagen-sdk` is available on [pypi.org](https://pypi.org/).
3. Create a production API token at [pypi.org/manage/account/#api-tokens](https://pypi.org/manage/account/#api-tokens).
4. Upload:

```bash
python -m twine upload dist/*
```

5. Install from PyPI:

```bash
pip install imagen-sdk
```

### Optional: store credentials in `.pypirc`

```ini
[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = __token__
password = pypi-...

[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-...
```

Never commit `.pypirc` or API tokens to version control.

## License

Proprietary — see [LICENSE](LICENSE). Unauthorized use, copying, or distribution is prohibited.
