runapi-gpt-image-2.5 0.1.1__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.
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.5
2
+ Name: runapi-gpt-image-2.5
3
+ Version: 0.1.1
4
+ Summary: RunAPI GPT Image 2.5 SDK for text-to-image and image editing workflows in JavaScript, Python, Ruby, Go, Java, and PHP
5
+ Project-URL: Homepage, https://runapi.ai/models/gpt-image-2.5
6
+ Project-URL: Documentation, https://runapi.ai/docs/resources/sdks
7
+ Project-URL: Source, https://github.com/runapi-ai/gpt-image-2.5-sdk
8
+ Project-URL: Issues, https://github.com/runapi-ai/gpt-image-2.5-sdk/issues
9
+ Project-URL: Changelog, https://github.com/runapi-ai/gpt-image-2.5-sdk/blob/main/CHANGELOG.md
10
+ Project-URL: Release Notes, https://github.com/runapi-ai/gpt-image-2.5-sdk/releases/tag/python%2Fv0.1.1
11
+ Author-email: RunAPI <contact@runapi.ai>
12
+ License-Expression: Apache-2.0
13
+ Keywords: api,golang,gpt-image-2-5,gpt-image-2-5-api,gradle,image-api,image-generation,java,maven,python,ruby,runapi,runapi-ai,sdk,text-to-image,typescript
14
+ Requires-Python: >=3.9
15
+ Requires-Dist: runapi-core>=0.8.2
16
+ Description-Content-Type: text/markdown
17
+
18
+ # GPT Image 2.5 Python SDK for RunAPI
19
+
20
+ The GPT Image 2.5 Python SDK provides text-to-image and image editing clients. Requests select `gpt-image-2.5-flare` or `gpt-image-2.5-sunburst`.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install runapi-gpt-image-2.5
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ```python
31
+ from runapi.gpt_image_2_5 import GptImage25Client
32
+
33
+ client = GptImage25Client()
34
+ task = client.text_to_image.create(
35
+ model="gpt-image-2.5-flare",
36
+ prompt="A precise product render on white marble",
37
+ aspect_ratio="1:1",
38
+ output_resolution="1k",
39
+ )
40
+ status = client.text_to_image.get(task.id)
41
+
42
+ edit = client.edit_image.create(
43
+ model="gpt-image-2.5-sunburst",
44
+ prompt="Change the background to a botanical studio",
45
+ source_image_urls=["https://cdn.runapi.ai/public/samples/image.jpg"],
46
+ )
47
+ ```
48
+
49
+ Use `create` to submit, `get` to fetch task status, and `run` to create and poll until completion. Keep `RUNAPI_API_KEY` in the environment or a secret manager.
50
+
51
+ RunAPI-generated file URLs are temporary. Store generated files in durable storage within 7 days.
52
+
53
+ ## Links
54
+
55
+ - Model overview: https://runapi.ai/models/gpt-image-2.5
56
+ - Flare: https://runapi.ai/models/gpt-image-2.5/flare
57
+ - Sunburst: https://runapi.ai/models/gpt-image-2.5/sunburst
58
+ - API reference: https://runapi.ai/docs/api/gpt-image-2-5/text-to-image
59
+ - SDK docs: https://runapi.ai/docs/resources/sdks
60
+ - Repository: https://github.com/runapi-ai/gpt-image-2.5-sdk
61
+
62
+ ## License
63
+
64
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,47 @@
1
+ # GPT Image 2.5 Python SDK for RunAPI
2
+
3
+ The GPT Image 2.5 Python SDK provides text-to-image and image editing clients. Requests select `gpt-image-2.5-flare` or `gpt-image-2.5-sunburst`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install runapi-gpt-image-2.5
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```python
14
+ from runapi.gpt_image_2_5 import GptImage25Client
15
+
16
+ client = GptImage25Client()
17
+ task = client.text_to_image.create(
18
+ model="gpt-image-2.5-flare",
19
+ prompt="A precise product render on white marble",
20
+ aspect_ratio="1:1",
21
+ output_resolution="1k",
22
+ )
23
+ status = client.text_to_image.get(task.id)
24
+
25
+ edit = client.edit_image.create(
26
+ model="gpt-image-2.5-sunburst",
27
+ prompt="Change the background to a botanical studio",
28
+ source_image_urls=["https://cdn.runapi.ai/public/samples/image.jpg"],
29
+ )
30
+ ```
31
+
32
+ Use `create` to submit, `get` to fetch task status, and `run` to create and poll until completion. Keep `RUNAPI_API_KEY` in the environment or a secret manager.
33
+
34
+ RunAPI-generated file URLs are temporary. Store generated files in durable storage within 7 days.
35
+
36
+ ## Links
37
+
38
+ - Model overview: https://runapi.ai/models/gpt-image-2.5
39
+ - Flare: https://runapi.ai/models/gpt-image-2.5/flare
40
+ - Sunburst: https://runapi.ai/models/gpt-image-2.5/sunburst
41
+ - API reference: https://runapi.ai/docs/api/gpt-image-2-5/text-to-image
42
+ - SDK docs: https://runapi.ai/docs/resources/sdks
43
+ - Repository: https://github.com/runapi-ai/gpt-image-2.5-sdk
44
+
45
+ ## License
46
+
47
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "runapi-gpt-image-2.5"
7
+ version = "0.1.1"
8
+ description = "RunAPI GPT Image 2.5 SDK for text-to-image and image editing workflows in JavaScript, Python, Ruby, Go, Java, and PHP"
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", "runapi-ai", "gpt-image-2-5", "api", "sdk", "typescript", "python", "ruby", "golang", "java", "maven", "gradle", "image-generation", "text-to-image", "image-api", "gpt-image-2-5-api"]
14
+ dependencies = ["runapi-core>=0.8.2"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://runapi.ai/models/gpt-image-2.5"
18
+ Documentation = "https://runapi.ai/docs/resources/sdks"
19
+ Source = "https://github.com/runapi-ai/gpt-image-2.5-sdk"
20
+ Issues = "https://github.com/runapi-ai/gpt-image-2.5-sdk/issues"
21
+ Changelog = "https://github.com/runapi-ai/gpt-image-2.5-sdk/blob/main/CHANGELOG.md"
22
+ "Release Notes" = "https://github.com/runapi-ai/gpt-image-2.5-sdk/releases/tag/python%2Fv0.1.1"
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["src/runapi"]
26
+
27
+ [tool.uv]
28
+ package = true
29
+
30
+ [dependency-groups]
31
+ dev = ["pytest>=8"]
32
+
33
+ [tool.runapi]
34
+ slug = "gpt-image-2.5"
@@ -0,0 +1,24 @@
1
+ """GPT Image 2.5 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 GptImage25Client
14
+
15
+ __all__ = [
16
+ "GptImage25Client",
17
+ "AuthenticationError",
18
+ "RateLimitError",
19
+ "InsufficientCreditsError",
20
+ "NotFoundError",
21
+ "ValidationError",
22
+ "TaskFailedError",
23
+ "TaskTimeoutError",
24
+ ]
@@ -0,0 +1,28 @@
1
+ """GPT Image 2.5 client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from runapi.core import ProviderClient
8
+
9
+ from .resources.edit_image import EditImage
10
+ from .resources.text_to_image import TextToImage
11
+
12
+
13
+ class GptImage25Client(ProviderClient):
14
+ """GPT Image 2.5 text-to-image and edit-image client.
15
+
16
+ Example::
17
+
18
+ client = GptImage25Client(api_key="sk-...")
19
+ result = client.text_to_image.run(
20
+ model="gpt-image-2.5-flare", prompt="A futuristic cityscape at night"
21
+ )
22
+ """
23
+
24
+ def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
25
+ super().__init__(api_key, **options)
26
+ http = self._http
27
+ self.text_to_image = TextToImage(http)
28
+ self.edit_image = EditImage(http)
@@ -0,0 +1,134 @@
1
+ CONTRACT = {
2
+ "edit-image": {
3
+ "models": ["gpt-image-2.5-flare", "gpt-image-2.5-sunburst"],
4
+ "fields_by_model": {
5
+ "gpt-image-2.5-flare": {
6
+ "aspect_ratio": {
7
+ "enum": ["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16", "2:1", "1:2", "3:1", "1:3", "21:9", "9:21"]
8
+ },
9
+ "output_resolution": {
10
+ "enum": ["1k", "2k", "4k"]
11
+ },
12
+ "prompt": {
13
+ "required": True
14
+ },
15
+ "source_image_urls": {
16
+ "required": True,
17
+ "max_items": 16
18
+ }
19
+ },
20
+ "gpt-image-2.5-sunburst": {
21
+ "aspect_ratio": {
22
+ "enum": ["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16", "2:1", "1:2", "3:1", "1:3", "21:9", "9:21"]
23
+ },
24
+ "output_resolution": {
25
+ "enum": ["1k", "2k", "4k"]
26
+ },
27
+ "prompt": {
28
+ "required": True
29
+ },
30
+ "source_image_urls": {
31
+ "required": True,
32
+ "max_items": 16
33
+ }
34
+ }
35
+ },
36
+ "rules": [{
37
+ "when": {
38
+ "aspect_ratio": "1:1",
39
+ "output_resolution": "4k"
40
+ },
41
+ "forbidden": ["aspect_ratio"]
42
+ }, {
43
+ "when": {
44
+ "aspect_ratio": "auto",
45
+ "output_resolution": "4k"
46
+ },
47
+ "forbidden": ["aspect_ratio"]
48
+ }, {
49
+ "when": {
50
+ "aspect_ratio": {
51
+ "present": False
52
+ },
53
+ "output_resolution": "4k"
54
+ },
55
+ "forbidden": ["output_resolution"]
56
+ }, {
57
+ "when": {
58
+ "aspect_ratio": "auto",
59
+ "output_resolution": "2k"
60
+ },
61
+ "forbidden": ["aspect_ratio"]
62
+ }, {
63
+ "when": {
64
+ "aspect_ratio": {
65
+ "present": False
66
+ },
67
+ "output_resolution": "2k"
68
+ },
69
+ "forbidden": ["output_resolution"]
70
+ }]
71
+ },
72
+ "text-to-image": {
73
+ "models": ["gpt-image-2.5-flare", "gpt-image-2.5-sunburst"],
74
+ "fields_by_model": {
75
+ "gpt-image-2.5-flare": {
76
+ "aspect_ratio": {
77
+ "enum": ["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16", "2:1", "1:2", "3:1", "1:3", "21:9", "9:21"]
78
+ },
79
+ "output_resolution": {
80
+ "enum": ["1k", "2k", "4k"]
81
+ },
82
+ "prompt": {
83
+ "required": True
84
+ }
85
+ },
86
+ "gpt-image-2.5-sunburst": {
87
+ "aspect_ratio": {
88
+ "enum": ["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16", "2:1", "1:2", "3:1", "1:3", "21:9", "9:21"]
89
+ },
90
+ "output_resolution": {
91
+ "enum": ["1k", "2k", "4k"]
92
+ },
93
+ "prompt": {
94
+ "required": True
95
+ }
96
+ }
97
+ },
98
+ "rules": [{
99
+ "when": {
100
+ "aspect_ratio": "1:1",
101
+ "output_resolution": "4k"
102
+ },
103
+ "forbidden": ["aspect_ratio"]
104
+ }, {
105
+ "when": {
106
+ "aspect_ratio": "auto",
107
+ "output_resolution": "4k"
108
+ },
109
+ "forbidden": ["aspect_ratio"]
110
+ }, {
111
+ "when": {
112
+ "aspect_ratio": {
113
+ "present": False
114
+ },
115
+ "output_resolution": "4k"
116
+ },
117
+ "forbidden": ["output_resolution"]
118
+ }, {
119
+ "when": {
120
+ "aspect_ratio": "auto",
121
+ "output_resolution": "2k"
122
+ },
123
+ "forbidden": ["aspect_ratio"]
124
+ }, {
125
+ "when": {
126
+ "aspect_ratio": {
127
+ "present": False
128
+ },
129
+ "output_resolution": "2k"
130
+ },
131
+ "forbidden": ["output_resolution"]
132
+ }]
133
+ }
134
+ }
@@ -0,0 +1,4 @@
1
+ from .edit_image import EditImage
2
+ from .text_to_image import TextToImage
3
+
4
+ __all__ = ["TextToImage", "EditImage"]
@@ -0,0 +1,58 @@
1
+ """GPT Image 2.5 edit-image resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from runapi.core import Resource, RequestOptions
8
+
9
+ from ..contract_gen import CONTRACT
10
+ from ..types import (
11
+ CompletedEditImageResponse,
12
+ EditImageResponse,
13
+ )
14
+
15
+
16
+ class EditImage(Resource):
17
+ """Edit images from a prompt and source images with GPT Image 2.5."""
18
+
19
+ ENDPOINT = "/api/v1/gpt_image_2_5/edit_image"
20
+
21
+ RESPONSE_CLASS = EditImageResponse
22
+ COMPLETED_RESPONSE_CLASS = CompletedEditImageResponse
23
+
24
+ def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
25
+ """Create an edit-image task and poll until it completes.
26
+
27
+ Args:
28
+ **params: Edit-image parameters (model, prompt, ...).
29
+
30
+ Returns:
31
+ The completed edit-image response.
32
+ """
33
+ task = self.create(options=options, **params)
34
+ return self._poll_until_complete(lambda: self.get(task.id, options=options))
35
+
36
+ def create(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
37
+ """Create an edit-image task and return immediately with an id.
38
+
39
+ Args:
40
+ **params: Edit-image parameters (model, prompt, ...).
41
+
42
+ Returns:
43
+ The task creation result with an id.
44
+ """
45
+ compacted = self._compact_params(params)
46
+ self._validate_contract(CONTRACT["edit-image"], compacted)
47
+ return self._request("post", self.ENDPOINT, body=compacted, options=options)
48
+
49
+ def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
50
+ """Fetch the current status of an edit-image task.
51
+
52
+ Args:
53
+ id: Task id.
54
+
55
+ Returns:
56
+ The current edit-image status.
57
+ """
58
+ return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
@@ -0,0 +1,58 @@
1
+ """GPT Image 2.5 text-to-image resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from runapi.core import Resource, RequestOptions
8
+
9
+ from ..contract_gen import CONTRACT
10
+ from ..types import (
11
+ CompletedTextToImageResponse,
12
+ TextToImageResponse,
13
+ )
14
+
15
+
16
+ class TextToImage(Resource):
17
+ """Generate images from text prompts with GPT Image 2.5."""
18
+
19
+ ENDPOINT = "/api/v1/gpt_image_2_5/text_to_image"
20
+
21
+ RESPONSE_CLASS = TextToImageResponse
22
+ COMPLETED_RESPONSE_CLASS = CompletedTextToImageResponse
23
+
24
+ def run(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
25
+ """Create a text-to-image task and poll until it completes.
26
+
27
+ Args:
28
+ **params: Text-to-image parameters (model, prompt, ...).
29
+
30
+ Returns:
31
+ The completed text-to-image response.
32
+ """
33
+ task = self.create(options=options, **params)
34
+ return self._poll_until_complete(lambda: self.get(task.id, options=options))
35
+
36
+ def create(self, options: Optional[RequestOptions] = None, **params: Any) -> Any:
37
+ """Create a text-to-image task and return immediately with an id.
38
+
39
+ Args:
40
+ **params: Text-to-image parameters (model, prompt, ...).
41
+
42
+ Returns:
43
+ The task creation result with an id.
44
+ """
45
+ compacted = self._compact_params(params)
46
+ self._validate_contract(CONTRACT["text-to-image"], compacted)
47
+ return self._request("post", self.ENDPOINT, body=compacted, options=options)
48
+
49
+ def get(self, id: str, options: Optional[RequestOptions] = None) -> Any:
50
+ """Fetch the current status of a text-to-image task.
51
+
52
+ Args:
53
+ id: Task id.
54
+
55
+ Returns:
56
+ The current text-to-image status.
57
+ """
58
+ return self._request("get", f"{self.ENDPOINT}/{id}", options=options)
@@ -0,0 +1,30 @@
1
+ """GPT Image 2.5 model lists, enums, and response models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from runapi.core import BaseModel, TaskResponse, optional, required
6
+
7
+
8
+ class Image(BaseModel):
9
+ url = optional(str)
10
+
11
+
12
+ class TextToImageResponse(TaskResponse):
13
+ """Task status/result for GPT Image 2.5 text-to-image."""
14
+ id = required(str)
15
+ status = optional(str, enum=lambda: TaskResponse.Status.ALL)
16
+ task_replayed = optional(bool)
17
+ images = optional([lambda: Image])
18
+ error = optional(str)
19
+
20
+
21
+ EditImageResponse = TextToImageResponse
22
+
23
+
24
+ class CompletedTextToImageResponse(TextToImageResponse):
25
+ """Narrowed response from ``run()`` once polling observes completion."""
26
+
27
+ images = required([lambda: Image])
28
+
29
+
30
+ CompletedEditImageResponse = CompletedTextToImageResponse
@@ -0,0 +1,155 @@
1
+ import pytest
2
+
3
+ from runapi.core import config
4
+ from runapi.core.errors import AuthenticationError, ValidationError
5
+ from runapi.gpt_image_2_5 import GptImage25Client
6
+ from runapi.gpt_image_2_5.resources.edit_image import EditImage
7
+ from runapi.gpt_image_2_5.resources.text_to_image import TextToImage
8
+ from runapi.gpt_image_2_5.types import CompletedTextToImageResponse, TextToImageResponse
9
+
10
+
11
+ class FakeHttp:
12
+ def __init__(self, *responses):
13
+ self._responses = list(responses)
14
+ self.calls = []
15
+
16
+ def request(self, method, path, body=None, options=None):
17
+ self.calls.append((method, path, body))
18
+ if self._responses:
19
+ return self._responses.pop(0)
20
+ return {"id": "task_1", "status": "pending"}
21
+
22
+
23
+ @pytest.fixture(autouse=True)
24
+ def reset_config(monkeypatch):
25
+ monkeypatch.delenv("RUNAPI_API_KEY", raising=False)
26
+ monkeypatch.setattr(config, "api_key", None)
27
+ yield
28
+
29
+
30
+ # --- auth -----------------------------------------------------------------
31
+
32
+
33
+ def test_accepts_api_key_parameter():
34
+ assert isinstance(GptImage25Client(api_key="k", http_client=FakeHttp()), GptImage25Client)
35
+
36
+
37
+ def test_falls_back_to_global(monkeypatch):
38
+ monkeypatch.setattr(config, "api_key", "global-key")
39
+ assert isinstance(GptImage25Client(http_client=FakeHttp()), GptImage25Client)
40
+
41
+
42
+ def test_falls_back_to_env(monkeypatch):
43
+ monkeypatch.setenv("RUNAPI_API_KEY", "env-key")
44
+ assert isinstance(GptImage25Client(http_client=FakeHttp()), GptImage25Client)
45
+
46
+
47
+ def test_raises_without_api_key():
48
+ with pytest.raises(AuthenticationError, match="API key is required"):
49
+ GptImage25Client()
50
+
51
+
52
+ # --- injection / accessors ------------------------------------------------
53
+
54
+
55
+ def test_uses_injected_http_client():
56
+ fake = FakeHttp()
57
+ client = GptImage25Client(api_key="k", http_client=fake)
58
+ assert client.text_to_image._http is fake
59
+ assert client.edit_image._http is fake
60
+
61
+
62
+ def test_exposes_resource_accessors():
63
+ client = GptImage25Client(api_key="k", http_client=FakeHttp())
64
+ assert isinstance(client.text_to_image, TextToImage)
65
+ assert isinstance(client.edit_image, EditImage)
66
+
67
+
68
+ # --- request shapes -------------------------------------------------------
69
+
70
+
71
+ def test_create_posts_compacted_body():
72
+ fake = FakeHttp({"id": "t1", "status": "pending", "task_replayed": True})
73
+ client = GptImage25Client(api_key="k", http_client=fake)
74
+ result = client.text_to_image.create(
75
+ model="gpt-image-2.5-flare", prompt="hello world", aspect_ratio="1:1", output_resolution=None
76
+ )
77
+ assert fake.calls == [
78
+ ("post", "/api/v1/gpt_image_2_5/text_to_image", {"model": "gpt-image-2.5-flare", "prompt": "hello world", "aspect_ratio": "1:1"}),
79
+ ]
80
+ assert isinstance(result, TextToImageResponse)
81
+ assert result.task_replayed is True
82
+
83
+
84
+ def test_get_fetches_by_id():
85
+ fake = FakeHttp({"id": "t1", "status": "processing"})
86
+ client = GptImage25Client(api_key="k", http_client=fake)
87
+ client.text_to_image.get("t1")
88
+ assert fake.calls == [("get", "/api/v1/gpt_image_2_5/text_to_image/t1", None)]
89
+
90
+
91
+ def test_edit_create_posts_compacted_body():
92
+ fake = FakeHttp({"id": "t1", "status": "pending"})
93
+ client = GptImage25Client(api_key="k", http_client=fake)
94
+ client.edit_image.create(
95
+ model="gpt-image-2.5-flare",
96
+ prompt="oil painting",
97
+ source_image_urls=["https://x/a.png"],
98
+ )
99
+ assert fake.calls == [
100
+ (
101
+ "post",
102
+ "/api/v1/gpt_image_2_5/edit_image",
103
+ {"model": "gpt-image-2.5-flare", "prompt": "oil painting", "source_image_urls": ["https://x/a.png"]},
104
+ ),
105
+ ]
106
+
107
+
108
+ def test_run_narrows_completed_type():
109
+ fake = FakeHttp(
110
+ {"id": "t1", "status": "pending"},
111
+ {"id": "t1", "status": "completed", "images": [{"url": "https://x/y.png"}]},
112
+ )
113
+ client = GptImage25Client(api_key="k", http_client=fake)
114
+ result = client.text_to_image.run(model="gpt-image-2.5-flare", prompt="a serene lake")
115
+ assert isinstance(result, CompletedTextToImageResponse)
116
+ assert result.images[0].url == "https://x/y.png"
117
+
118
+
119
+ # --- validation -----------------------------------------------------------
120
+
121
+
122
+ def test_requires_model():
123
+ client = GptImage25Client(api_key="k", http_client=FakeHttp())
124
+ with pytest.raises(ValidationError, match="model must be one of: gpt-image-2.5-flare"):
125
+ client.text_to_image.create(prompt="hi there")
126
+
127
+
128
+ def test_requires_prompt():
129
+ client = GptImage25Client(api_key="k", http_client=FakeHttp())
130
+ with pytest.raises(ValidationError, match="prompt is required"):
131
+ client.text_to_image.create(model="gpt-image-2.5-flare")
132
+
133
+
134
+ def test_rejects_unknown_model():
135
+ client = GptImage25Client(api_key="k", http_client=FakeHttp())
136
+ with pytest.raises(ValidationError, match="model must be one of: gpt-image-2.5-flare"):
137
+ client.text_to_image.create(model="nope", prompt="hi there")
138
+
139
+
140
+ def test_rejects_invalid_aspect_ratio():
141
+ client = GptImage25Client(api_key="k", http_client=FakeHttp())
142
+ with pytest.raises(ValidationError, match="aspect_ratio must be one of"):
143
+ client.text_to_image.create(model="gpt-image-2.5-flare", prompt="hi there", aspect_ratio="5:5")
144
+
145
+
146
+ def test_rejects_invalid_output_resolution():
147
+ client = GptImage25Client(api_key="k", http_client=FakeHttp())
148
+ with pytest.raises(ValidationError, match="output_resolution must be one of"):
149
+ client.text_to_image.create(model="gpt-image-2.5-flare", prompt="hi there", output_resolution="8k")
150
+
151
+
152
+ def test_edit_requires_source_image_urls():
153
+ client = GptImage25Client(api_key="k", http_client=FakeHttp())
154
+ with pytest.raises(ValidationError, match="source_image_urls is required"):
155
+ client.edit_image.create(model="gpt-image-2.5-flare", prompt="make it pop")