runapi-gpt-4o-image 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runapi_gpt_4o_image-0.1.0/.gitignore +29 -0
- runapi_gpt_4o_image-0.1.0/PKG-INFO +82 -0
- runapi_gpt_4o_image-0.1.0/README.md +69 -0
- runapi_gpt_4o_image-0.1.0/pyproject.toml +30 -0
- runapi_gpt_4o_image-0.1.0/src/runapi/gpt_4o_image/__init__.py +24 -0
- runapi_gpt_4o_image-0.1.0/src/runapi/gpt_4o_image/client.py +27 -0
- runapi_gpt_4o_image-0.1.0/src/runapi/gpt_4o_image/py.typed +0 -0
- runapi_gpt_4o_image-0.1.0/src/runapi/gpt_4o_image/resources/__init__.py +3 -0
- runapi_gpt_4o_image-0.1.0/src/runapi/gpt_4o_image/resources/text_to_image.py +74 -0
- runapi_gpt_4o_image-0.1.0/src/runapi/gpt_4o_image/types.py +25 -0
- runapi_gpt_4o_image-0.1.0/tests/test_client.py +146 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Build artifacts
|
|
2
|
+
dist/
|
|
3
|
+
build/
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
|
|
7
|
+
# Bytecode
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
|
|
15
|
+
# uv
|
|
16
|
+
uv.lock
|
|
17
|
+
|
|
18
|
+
# Test / type caches
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
|
|
25
|
+
# IDE / OS
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
*.swp
|
|
29
|
+
.DS_Store
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runapi-gpt-4o-image
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GPT-4o Image text-to-image client for RunAPI
|
|
5
|
+
Project-URL: Homepage, https://runapi.ai/models/gpt-4o-image
|
|
6
|
+
Project-URL: Documentation, https://runapi.ai/docs#sdk-gpt-4o-image
|
|
7
|
+
Author-email: RunAPI <contact@runapi.ai>
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
Keywords: ai,gpt-4o-image,runapi,sdk,text-to-image
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Requires-Dist: runapi-core
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# GPT-4o Image Python SDK for RunAPI
|
|
15
|
+
|
|
16
|
+
The GPT-4o Image Python SDK is the language-specific package for GPT-4o Image on
|
|
17
|
+
RunAPI. Use it for text-to-image, image editing, and creative production flows
|
|
18
|
+
when your application needs JSON request bodies, task status lookup, and
|
|
19
|
+
consistent RunAPI errors in Python.
|
|
20
|
+
|
|
21
|
+
For model details, use https://runapi.ai/models/gpt-4o-image; for API reference,
|
|
22
|
+
use https://runapi.ai/docs#gpt-4o-image; for SDK docs, use
|
|
23
|
+
https://runapi.ai/docs#sdk-gpt-4o-image.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install runapi-gpt-4o-image
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from runapi.gpt_4o_image import Gpt4oImageClient
|
|
35
|
+
|
|
36
|
+
client = Gpt4oImageClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
|
|
37
|
+
|
|
38
|
+
task = client.text_to_image.create(
|
|
39
|
+
model="gpt-4o-image",
|
|
40
|
+
prompt="A cinematic product photo on warm paper",
|
|
41
|
+
aspect_ratio="1:1",
|
|
42
|
+
)
|
|
43
|
+
status = client.text_to_image.get(task.id)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use `create` to submit a task and return quickly, `get` to fetch the latest task
|
|
47
|
+
state, and `run` to create and poll until completion:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
result = client.text_to_image.run(
|
|
51
|
+
model="gpt-4o-image",
|
|
52
|
+
prompt="A futuristic cityscape at dusk",
|
|
53
|
+
aspect_ratio="3:2",
|
|
54
|
+
)
|
|
55
|
+
print(result.images[0].url)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
In web request handlers, prefer `create` plus webhook or later `get` polling so a
|
|
59
|
+
worker is not held open.
|
|
60
|
+
|
|
61
|
+
RunAPI-generated file URLs are temporary. Download and store generated images in
|
|
62
|
+
your own durable storage within 7 days; do not treat returned URLs as long-term
|
|
63
|
+
assets.
|
|
64
|
+
|
|
65
|
+
## Language notes
|
|
66
|
+
|
|
67
|
+
Pass parameters as keyword arguments and catch the `runapi.gpt_4o_image` error
|
|
68
|
+
classes when building image jobs or scripts. The available resource is
|
|
69
|
+
`text_to_image`. Keep `RUNAPI_API_KEY` in the environment or your secret
|
|
70
|
+
manager; never commit API keys or callback secrets.
|
|
71
|
+
|
|
72
|
+
## Links
|
|
73
|
+
|
|
74
|
+
- Model page: https://runapi.ai/models/gpt-4o-image
|
|
75
|
+
- SDK docs: https://runapi.ai/docs#sdk-gpt-4o-image
|
|
76
|
+
- Product docs: https://runapi.ai/docs#gpt-4o-image
|
|
77
|
+
- Pricing and rate limits: https://runapi.ai/models/gpt-4o-image
|
|
78
|
+
- Full catalog: https://runapi.ai/models
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Licensed under the Apache License, Version 2.0.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# GPT-4o Image Python SDK for RunAPI
|
|
2
|
+
|
|
3
|
+
The GPT-4o Image Python SDK is the language-specific package for GPT-4o Image on
|
|
4
|
+
RunAPI. Use it for text-to-image, image editing, and creative production flows
|
|
5
|
+
when your application needs JSON request bodies, task status lookup, and
|
|
6
|
+
consistent RunAPI errors in Python.
|
|
7
|
+
|
|
8
|
+
For model details, use https://runapi.ai/models/gpt-4o-image; for API reference,
|
|
9
|
+
use https://runapi.ai/docs#gpt-4o-image; for SDK docs, use
|
|
10
|
+
https://runapi.ai/docs#sdk-gpt-4o-image.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install runapi-gpt-4o-image
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from runapi.gpt_4o_image import Gpt4oImageClient
|
|
22
|
+
|
|
23
|
+
client = Gpt4oImageClient() # reads RUNAPI_API_KEY, or pass api_key="sk-..."
|
|
24
|
+
|
|
25
|
+
task = client.text_to_image.create(
|
|
26
|
+
model="gpt-4o-image",
|
|
27
|
+
prompt="A cinematic product photo on warm paper",
|
|
28
|
+
aspect_ratio="1:1",
|
|
29
|
+
)
|
|
30
|
+
status = client.text_to_image.get(task.id)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use `create` to submit a task and return quickly, `get` to fetch the latest task
|
|
34
|
+
state, and `run` to create and poll until completion:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
result = client.text_to_image.run(
|
|
38
|
+
model="gpt-4o-image",
|
|
39
|
+
prompt="A futuristic cityscape at dusk",
|
|
40
|
+
aspect_ratio="3:2",
|
|
41
|
+
)
|
|
42
|
+
print(result.images[0].url)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
In web request handlers, prefer `create` plus webhook or later `get` polling so a
|
|
46
|
+
worker is not held open.
|
|
47
|
+
|
|
48
|
+
RunAPI-generated file URLs are temporary. Download and store generated images in
|
|
49
|
+
your own durable storage within 7 days; do not treat returned URLs as long-term
|
|
50
|
+
assets.
|
|
51
|
+
|
|
52
|
+
## Language notes
|
|
53
|
+
|
|
54
|
+
Pass parameters as keyword arguments and catch the `runapi.gpt_4o_image` error
|
|
55
|
+
classes when building image jobs or scripts. The available resource is
|
|
56
|
+
`text_to_image`. Keep `RUNAPI_API_KEY` in the environment or your secret
|
|
57
|
+
manager; never commit API keys or callback secrets.
|
|
58
|
+
|
|
59
|
+
## Links
|
|
60
|
+
|
|
61
|
+
- Model page: https://runapi.ai/models/gpt-4o-image
|
|
62
|
+
- SDK docs: https://runapi.ai/docs#sdk-gpt-4o-image
|
|
63
|
+
- Product docs: https://runapi.ai/docs#gpt-4o-image
|
|
64
|
+
- Pricing and rate limits: https://runapi.ai/models/gpt-4o-image
|
|
65
|
+
- Full catalog: https://runapi.ai/models
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
Licensed under the Apache License, Version 2.0.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runapi-gpt-4o-image"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "GPT-4o Image text-to-image client for RunAPI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "RunAPI", email = "contact@runapi.ai" }]
|
|
13
|
+
keywords = ["runapi", "gpt-4o-image", "text-to-image", "ai", "sdk"]
|
|
14
|
+
dependencies = ["runapi-core"]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://runapi.ai/models/gpt-4o-image"
|
|
18
|
+
Documentation = "https://runapi.ai/docs#sdk-gpt-4o-image"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/runapi"]
|
|
22
|
+
|
|
23
|
+
[tool.uv]
|
|
24
|
+
package = true
|
|
25
|
+
|
|
26
|
+
[tool.uv.sources]
|
|
27
|
+
runapi-core = { path = "../runapi-core", editable = true }
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = ["pytest>=8"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""GPT-4o Image client for RunAPI."""
|
|
2
|
+
|
|
3
|
+
from runapi.core import (
|
|
4
|
+
AuthenticationError,
|
|
5
|
+
InsufficientCreditsError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
RateLimitError,
|
|
8
|
+
TaskFailedError,
|
|
9
|
+
TaskTimeoutError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .client import Gpt4oImageClient
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Gpt4oImageClient",
|
|
17
|
+
"AuthenticationError",
|
|
18
|
+
"RateLimitError",
|
|
19
|
+
"InsufficientCreditsError",
|
|
20
|
+
"NotFoundError",
|
|
21
|
+
"ValidationError",
|
|
22
|
+
"TaskFailedError",
|
|
23
|
+
"TaskTimeoutError",
|
|
24
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""GPT-4o Image client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from runapi.core import ClientOptions, HttpClient, resolve_api_key
|
|
8
|
+
|
|
9
|
+
from .resources.text_to_image import TextToImage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Gpt4oImageClient:
|
|
13
|
+
"""GPT-4o Image text-to-image client.
|
|
14
|
+
|
|
15
|
+
Example::
|
|
16
|
+
|
|
17
|
+
client = Gpt4oImageClient(api_key="sk-...")
|
|
18
|
+
result = client.text_to_image.run(
|
|
19
|
+
model="gpt-4o-image", prompt="A futuristic cityscape", aspect_ratio="1:1"
|
|
20
|
+
)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
|
|
24
|
+
resolved_api_key = resolve_api_key(api_key)
|
|
25
|
+
client_options = ClientOptions(api_key=resolved_api_key, **options)
|
|
26
|
+
http = client_options.http_client or HttpClient(client_options)
|
|
27
|
+
self.text_to_image = TextToImage(http)
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""GPT-4o Image text-to-image resource."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from runapi.core import Resource, ValidationError
|
|
8
|
+
|
|
9
|
+
from ..types import (
|
|
10
|
+
ASPECT_RATIOS,
|
|
11
|
+
MODELS,
|
|
12
|
+
OUTPUT_COUNTS,
|
|
13
|
+
CompletedTextToImageResponse,
|
|
14
|
+
TextToImageResponse,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TextToImage(Resource):
|
|
19
|
+
"""Generate images from text prompts with GPT-4o Image."""
|
|
20
|
+
|
|
21
|
+
ENDPOINT = "/api/v1/gpt_4o_image/text_to_image"
|
|
22
|
+
|
|
23
|
+
RESPONSE_CLASS = TextToImageResponse
|
|
24
|
+
COMPLETED_RESPONSE_CLASS = CompletedTextToImageResponse
|
|
25
|
+
|
|
26
|
+
def run(self, **params: Any) -> Any:
|
|
27
|
+
"""Create a text-to-image task and poll until it completes.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
**params: Text-to-image parameters (model, prompt, ...).
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The completed text-to-image response.
|
|
34
|
+
"""
|
|
35
|
+
task = self.create(**params)
|
|
36
|
+
return self._poll_until_complete(lambda: self.get(task.id))
|
|
37
|
+
|
|
38
|
+
def create(self, **params: Any) -> Any:
|
|
39
|
+
"""Create a text-to-image task and return immediately with an id.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
**params: Text-to-image parameters (model, prompt, ...).
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The task creation result with an id.
|
|
46
|
+
"""
|
|
47
|
+
compacted = self._compact_params(params)
|
|
48
|
+
self._validate_params(compacted)
|
|
49
|
+
return self._request("post", self.ENDPOINT, body=compacted)
|
|
50
|
+
|
|
51
|
+
def get(self, id: str) -> Any:
|
|
52
|
+
"""Fetch the current status of a text-to-image task.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
id: Task id.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The current text-to-image status.
|
|
59
|
+
"""
|
|
60
|
+
return self._request("get", f"{self.ENDPOINT}/{id}")
|
|
61
|
+
|
|
62
|
+
def _validate_params(self, params: Dict[str, Any]) -> None:
|
|
63
|
+
if not params.get("model"):
|
|
64
|
+
raise ValidationError("model is required")
|
|
65
|
+
if not params.get("aspect_ratio"):
|
|
66
|
+
raise ValidationError("aspect_ratio is required")
|
|
67
|
+
|
|
68
|
+
model = params.get("model")
|
|
69
|
+
if model not in MODELS:
|
|
70
|
+
joined = ", ".join(MODELS)
|
|
71
|
+
raise ValidationError(f"Invalid model: {model}. Must be: {joined}")
|
|
72
|
+
|
|
73
|
+
self._validate_optional(params, "aspect_ratio", ASPECT_RATIOS)
|
|
74
|
+
self._validate_optional(params, "output_count", OUTPUT_COUNTS)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""GPT-4o Image model lists, enums, and response models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from runapi.core import BaseModel, TaskResponse, optional, required
|
|
6
|
+
|
|
7
|
+
MODELS = ["gpt-4o-image"]
|
|
8
|
+
ASPECT_RATIOS = ["1:1", "3:2", "2:3"]
|
|
9
|
+
OUTPUT_COUNTS = [1, 2, 4]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Image(BaseModel):
|
|
13
|
+
url = optional(str)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TextToImageResponse(TaskResponse):
|
|
17
|
+
"""Task status/result for GPT-4o Image text-to-image."""
|
|
18
|
+
id = required(str)
|
|
19
|
+
status = optional(str, enum=lambda: TaskResponse.Status.ALL)
|
|
20
|
+
images = optional([lambda: Image])
|
|
21
|
+
progress = optional(str)
|
|
22
|
+
error = optional(str)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
CompletedTextToImageResponse = TextToImageResponse
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from runapi.core import config
|
|
4
|
+
from runapi.core.errors import AuthenticationError, ValidationError
|
|
5
|
+
from runapi.gpt_4o_image import Gpt4oImageClient
|
|
6
|
+
from runapi.gpt_4o_image.resources.text_to_image import TextToImage
|
|
7
|
+
from runapi.gpt_4o_image.types import CompletedTextToImageResponse, TextToImageResponse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FakeHttp:
|
|
11
|
+
"""Records (method, path, body) and replays preset responses by call order."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, *responses):
|
|
14
|
+
self._responses = list(responses)
|
|
15
|
+
self.calls = []
|
|
16
|
+
|
|
17
|
+
def request(self, method, path, body=None, options=None):
|
|
18
|
+
self.calls.append((method, path, body))
|
|
19
|
+
if self._responses:
|
|
20
|
+
return self._responses.pop(0)
|
|
21
|
+
return {"id": "task_1", "status": "pending"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture(autouse=True)
|
|
25
|
+
def reset_config(monkeypatch):
|
|
26
|
+
monkeypatch.delenv("RUNAPI_API_KEY", raising=False)
|
|
27
|
+
monkeypatch.setattr(config, "api_key", None)
|
|
28
|
+
yield
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# --- authentication -------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_accepts_api_key_parameter():
|
|
35
|
+
assert isinstance(
|
|
36
|
+
Gpt4oImageClient(api_key="param-key", http_client=FakeHttp()), Gpt4oImageClient
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_falls_back_to_global(monkeypatch):
|
|
41
|
+
monkeypatch.setattr(config, "api_key", "global-key")
|
|
42
|
+
assert isinstance(Gpt4oImageClient(http_client=FakeHttp()), Gpt4oImageClient)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_falls_back_to_env(monkeypatch):
|
|
46
|
+
monkeypatch.setenv("RUNAPI_API_KEY", "env-key")
|
|
47
|
+
assert isinstance(Gpt4oImageClient(http_client=FakeHttp()), Gpt4oImageClient)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_raises_without_api_key():
|
|
51
|
+
with pytest.raises(AuthenticationError, match="API key is required"):
|
|
52
|
+
Gpt4oImageClient()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# --- transport injection / accessors --------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_uses_injected_http_client():
|
|
59
|
+
fake = FakeHttp()
|
|
60
|
+
client = Gpt4oImageClient(api_key="k", http_client=fake)
|
|
61
|
+
assert client.text_to_image._http is fake
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_exposes_resource_accessors():
|
|
65
|
+
client = Gpt4oImageClient(api_key="k", http_client=FakeHttp())
|
|
66
|
+
assert isinstance(client.text_to_image, TextToImage)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# --- request shapes -------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_create_posts_compacted_body():
|
|
73
|
+
fake = FakeHttp({"id": "t1", "status": "pending"})
|
|
74
|
+
client = Gpt4oImageClient(api_key="k", http_client=fake)
|
|
75
|
+
result = client.text_to_image.create(
|
|
76
|
+
model="gpt-4o-image",
|
|
77
|
+
prompt="hello",
|
|
78
|
+
aspect_ratio="1:1",
|
|
79
|
+
mask_url=None,
|
|
80
|
+
)
|
|
81
|
+
assert fake.calls == [
|
|
82
|
+
(
|
|
83
|
+
"post",
|
|
84
|
+
"/api/v1/gpt_4o_image/text_to_image",
|
|
85
|
+
{"model": "gpt-4o-image", "prompt": "hello", "aspect_ratio": "1:1"},
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
assert isinstance(result, TextToImageResponse)
|
|
89
|
+
assert result.id == "t1"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_get_fetches_by_id():
|
|
93
|
+
fake = FakeHttp({"id": "t1", "status": "processing"})
|
|
94
|
+
client = Gpt4oImageClient(api_key="k", http_client=fake)
|
|
95
|
+
client.text_to_image.get("t1")
|
|
96
|
+
assert fake.calls == [("get", "/api/v1/gpt_4o_image/text_to_image/t1", None)]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_run_polls_and_narrows_completed_type():
|
|
100
|
+
fake = FakeHttp(
|
|
101
|
+
{"id": "t1", "status": "pending"},
|
|
102
|
+
{"id": "t1", "status": "completed", "images": [{"url": "https://x/y.png"}]},
|
|
103
|
+
)
|
|
104
|
+
client = Gpt4oImageClient(api_key="k", http_client=fake)
|
|
105
|
+
result = client.text_to_image.run(
|
|
106
|
+
model="gpt-4o-image", prompt="hi", aspect_ratio="1:1"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
assert isinstance(result, CompletedTextToImageResponse)
|
|
110
|
+
assert result.images[0].url == "https://x/y.png"
|
|
111
|
+
assert [call[0] for call in fake.calls] == ["post", "get"]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# --- validation -----------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_create_requires_model():
|
|
118
|
+
client = Gpt4oImageClient(api_key="k", http_client=FakeHttp())
|
|
119
|
+
with pytest.raises(ValidationError, match="model is required"):
|
|
120
|
+
client.text_to_image.create(aspect_ratio="1:1")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_create_requires_aspect_ratio():
|
|
124
|
+
client = Gpt4oImageClient(api_key="k", http_client=FakeHttp())
|
|
125
|
+
with pytest.raises(ValidationError, match="aspect_ratio is required"):
|
|
126
|
+
client.text_to_image.create(model="gpt-4o-image")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_create_rejects_unknown_model():
|
|
130
|
+
client = Gpt4oImageClient(api_key="k", http_client=FakeHttp())
|
|
131
|
+
with pytest.raises(ValidationError, match=r"Invalid model: not-a-model\. Must be: gpt-4o-image"):
|
|
132
|
+
client.text_to_image.create(model="not-a-model", aspect_ratio="1:1")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_create_rejects_invalid_aspect_ratio():
|
|
136
|
+
client = Gpt4oImageClient(api_key="k", http_client=FakeHttp())
|
|
137
|
+
with pytest.raises(ValidationError, match="Invalid aspect_ratio"):
|
|
138
|
+
client.text_to_image.create(model="gpt-4o-image", aspect_ratio="99:1")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_create_rejects_invalid_output_count():
|
|
142
|
+
client = Gpt4oImageClient(api_key="k", http_client=FakeHttp())
|
|
143
|
+
with pytest.raises(ValidationError, match="Invalid output_count"):
|
|
144
|
+
client.text_to_image.create(
|
|
145
|
+
model="gpt-4o-image", aspect_ratio="1:1", output_count=3
|
|
146
|
+
)
|