Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions src/openai/resources/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ..types import image_edit_params, image_generate_params, image_create_variation_params
from .._files import deepcopy_with_paths
from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, SequenceNotStr, omit, not_given
from .._utils import extract_files, required_args, maybe_transform, async_maybe_transform
from .._utils import is_list, is_mapping, extract_files, required_args, maybe_transform, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper
Expand All @@ -21,6 +21,7 @@
from ..types.images_response import ImagesResponse
from ..types.image_gen_stream_event import ImageGenStreamEvent
from ..types.image_edit_stream_event import ImageEditStreamEvent
from ..types.image_input_reference_param import ImageInputReferenceParam

__all__ = ["Images", "AsyncImages"]

Expand Down Expand Up @@ -129,7 +130,7 @@ def create_variation(
def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
Expand Down Expand Up @@ -259,7 +260,7 @@ def edit(
def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
stream: Literal[True],
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
Expand Down Expand Up @@ -389,7 +390,7 @@ def edit(
def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
stream: bool,
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
Expand Down Expand Up @@ -519,7 +520,7 @@ def edit(
def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
Expand All @@ -542,6 +543,7 @@ def edit(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ImagesResponse | Stream[ImageEditStreamEvent]:
is_url_input = is_mapping(image) or (is_list(image) and len(image) > 0 and is_mapping(image[0]))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Send image references under the JSON images field

When image is a reference dict/list this branch switches the request to application/json, but the body constructed below still uses the multipart-only image key. The image edit JSON API expects images as an array of {image_url|file_id} references, so client.images.edit(image={"image_url": ...}, ...) posts the wrong payload and the new reference-input path is rejected instead of working. Please translate reference inputs to the images field, wrapping a single reference in an array, when taking this JSON path.

Useful? React with 👍 / 👎.

body = deepcopy_with_paths(
{
"image": image,
Expand All @@ -560,13 +562,16 @@ def edit(
"stream": stream,
"user": user,
},
[["image"], ["image", "<array>"], ["mask"]],
[] if is_url_input else [["image"], ["image", "<array>"], ["mask"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["mask"]])
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
if is_url_input:
files = None
else:
files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["mask"]])
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
return self._post(
"/images/edits",
body=maybe_transform(
Expand Down Expand Up @@ -1134,7 +1139,7 @@ async def create_variation(
async def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
Expand Down Expand Up @@ -1264,7 +1269,7 @@ async def edit(
async def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
stream: Literal[True],
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
Expand Down Expand Up @@ -1394,7 +1399,7 @@ async def edit(
async def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
stream: bool,
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
Expand Down Expand Up @@ -1524,7 +1529,7 @@ async def edit(
async def edit(
self,
*,
image: Union[FileTypes, SequenceNotStr[FileTypes]],
image: Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]],
prompt: str,
background: Optional[Literal["transparent", "opaque", "auto"]] | Omit = omit,
input_fidelity: Optional[Literal["high", "low"]] | Omit = omit,
Expand All @@ -1547,6 +1552,7 @@ async def edit(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ImagesResponse | AsyncStream[ImageEditStreamEvent]:
is_url_input = is_mapping(image) or (is_list(image) and len(image) > 0 and is_mapping(image[0]))
body = deepcopy_with_paths(
{
"image": image,
Expand All @@ -1565,13 +1571,16 @@ async def edit(
"stream": stream,
"user": user,
},
[["image"], ["image", "<array>"], ["mask"]],
[] if is_url_input else [["image"], ["image", "<array>"], ["mask"]],
)
files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["mask"]])
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
if is_url_input:
files = None
else:
files = extract_files(cast(Mapping[str, object], body), paths=[["image"], ["image", "<array>"], ["mask"]])
# It should be noted that the actual Content-Type header that will be
# sent to the server will contain a `boundary` parameter, e.g.
# multipart/form-data; boundary=---abc--
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
return await self._post(
"/images/edits",
body=await async_maybe_transform(
Expand Down
3 changes: 2 additions & 1 deletion src/openai/types/image_edit_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

from .._types import FileTypes, SequenceNotStr
from .image_model import ImageModel
from .image_input_reference_param import ImageInputReferenceParam

__all__ = ["ImageEditParamsBase", "ImageEditParamsNonStreaming", "ImageEditParamsStreaming"]


class ImageEditParamsBase(TypedDict, total=False):
image: Required[Union[FileTypes, SequenceNotStr[FileTypes]]]
image: Required[Union[FileTypes, ImageInputReferenceParam, SequenceNotStr[Union[FileTypes, ImageInputReferenceParam]]]]
"""The image(s) to edit. Must be a supported image file or an array of images.

For the GPT image models (`gpt-image-1`, `gpt-image-1-mini`, `gpt-image-1.5`,
Expand Down