From 307a0664383b9d1d4151bc1a05a78c4fdcdcc9b0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 23:26:30 +0000 Subject: [PATCH 1/8] chore(internal): codegen related update --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 75118d46be..7a05625aa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", From e040d22c2df068e908f69dc6b892e7f8b3fe6e99 Mon Sep 17 00:00:00 2001 From: peter-zhong-replit Date: Mon, 1 Dec 2025 05:13:27 -0800 Subject: [PATCH 2/8] fix(client): avoid mutating user-provided response config object (#2700) --- src/openai/resources/responses/responses.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/openai/resources/responses/responses.py b/src/openai/resources/responses/responses.py index dcf87ba07c..8365b808ae 100644 --- a/src/openai/resources/responses/responses.py +++ b/src/openai/resources/responses/responses.py @@ -2,6 +2,7 @@ from __future__ import annotations +from copy import copy from typing import Any, List, Type, Union, Iterable, Optional, cast from functools import partial from typing_extensions import Literal, overload @@ -1046,6 +1047,7 @@ def stream( if "format" in text: raise TypeError("Cannot mix and match text.format with text_format") + text = copy(text) text["format"] = _type_to_text_format_param(text_format) api_request: partial[Stream[ResponseStreamEvent]] = partial( @@ -1151,7 +1153,7 @@ def parse( if "format" in text: raise TypeError("Cannot mix and match text.format with text_format") - + text = copy(text) text["format"] = _type_to_text_format_param(text_format) tools = _make_tools(tools) @@ -2507,7 +2509,7 @@ def stream( if "format" in text: raise TypeError("Cannot mix and match text.format with text_format") - + text = copy(text) text["format"] = _type_to_text_format_param(text_format) api_request = self.create( @@ -2617,7 +2619,7 @@ async def parse( if "format" in text: raise TypeError("Cannot mix and match text.format with text_format") - + text = copy(text) text["format"] = _type_to_text_format_param(text_format) tools = _make_tools(tools) From 0b1a27f08639d14dfe40bf80b48e2b8a1a51593c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:56:43 +0000 Subject: [PATCH 3/8] fix: ensure streams are always closed --- src/openai/_streaming.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/openai/_streaming.py b/src/openai/_streaming.py index 05c284a2be..74e54f74fa 100644 --- a/src/openai/_streaming.py +++ b/src/openai/_streaming.py @@ -55,9 +55,10 @@ def __stream__(self) -> Iterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - for sse in iterator: - if sse.data.startswith("[DONE]"): - break + try: + for sse in iterator: + if sse.data.startswith("[DONE]"): + break # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data if sse.event and sse.event.startswith("thread."): @@ -96,8 +97,9 @@ def __stream__(self) -> Iterator[_T]: yield process_data(data=data, cast_to=cast_to, response=response) - # As we might not fully consume the response stream, we need to close it explicitly - response.close() + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() def __enter__(self) -> Self: return self @@ -156,9 +158,10 @@ async def __stream__(self) -> AsyncIterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - async for sse in iterator: - if sse.data.startswith("[DONE]"): - break + try: + async for sse in iterator: + if sse.data.startswith("[DONE]"): + break # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data if sse.event and sse.event.startswith("thread."): @@ -197,8 +200,9 @@ async def __stream__(self) -> AsyncIterator[_T]: yield process_data(data=data, cast_to=cast_to, response=response) - # As we might not fully consume the response stream, we need to close it explicitly - await response.aclose() + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() async def __aenter__(self) -> Self: return self From 22cd586dbd5484b47f625da55db697691116b22b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:01:53 +0000 Subject: [PATCH 4/8] chore(deps): mypy 1.18.1 has a regression, pin to 1.17 --- pyproject.toml | 2 +- requirements-dev.lock | 4 +++- requirements.lock | 9 +++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7a05625aa6..4130dba20a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ managed = true # version pins are in requirements-dev.lock dev-dependencies = [ "pyright==1.1.399", - "mypy", + "mypy==1.17", "respx", "pytest", "pytest-asyncio", diff --git a/requirements-dev.lock b/requirements-dev.lock index b454537b96..07443f2dae 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -105,7 +105,7 @@ msal-extensions==1.2.0 multidict==6.5.0 # via aiohttp # via yarl -mypy==1.14.1 +mypy==1.17.0 mypy-extensions==1.0.0 # via mypy nest-asyncio==1.6.0 @@ -125,6 +125,8 @@ pandas==2.2.3 # via openai pandas-stubs==2.1.4.231227 # via openai +pathspec==0.12.1 + # via mypy platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 diff --git a/requirements.lock b/requirements.lock index b047cb3f88..386ec3c590 100644 --- a/requirements.lock +++ b/requirements.lock @@ -69,9 +69,9 @@ propcache==0.3.2 # via yarl pycparser==2.23 # via cffi -pydantic==2.11.9 +pydantic==2.12.5 # via openai -pydantic-core==2.33.2 +pydantic-core==2.41.5 # via pydantic python-dateutil==2.9.0.post0 # via pandas @@ -88,13 +88,14 @@ tqdm==4.66.5 # via openai types-pytz==2024.2.0.20241003 # via pandas-stubs -typing-extensions==4.12.2 +typing-extensions==4.15.0 + # via anyio # via multidict # via openai # via pydantic # via pydantic-core # via typing-inspection -typing-inspection==0.4.1 +typing-inspection==0.4.2 # via pydantic tzdata==2025.2 # via pandas From b4109c5fcf971ccfb25b4bdaef0bf36999f9eca5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 11:05:28 +0000 Subject: [PATCH 5/8] chore: update lockfile --- pyproject.toml | 12 +-- requirements-dev.lock | 165 ++++++++++++++++++++++-------------------- requirements.lock | 47 ++++++------ 3 files changed, 119 insertions(+), 105 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4130dba20a..3ad3b4a58a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,16 +7,18 @@ license = "Apache-2.0" authors = [ { name = "OpenAI", email = "support@openai.com" }, ] + dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", "typing-extensions>=4.11, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", "tqdm > 4", "jiter>=0.10.0, <1", ] + requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", diff --git a/requirements-dev.lock b/requirements-dev.lock index 07443f2dae..a7201a127b 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,65 +12,70 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.12.13 +aiohttp==3.13.2 # via httpx-aiohttp # via openai -aiosignal==1.3.2 +aiosignal==1.4.0 # via aiohttp -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.1.0 +anyio==4.12.0 # via httpx # via openai -argcomplete==3.1.2 +argcomplete==3.6.3 # via nox -asttokens==2.4.1 +asttokens==3.0.1 # via inline-snapshot async-timeout==5.0.1 # via aiohttp -attrs==24.2.0 +attrs==25.4.0 # via aiohttp + # via nox # via outcome # via trio -azure-core==1.31.0 +azure-core==1.36.0 # via azure-identity -azure-identity==1.19.0 -certifi==2023.7.22 +azure-identity==1.25.1 +backports-asyncio-runner==1.2.0 + # via pytest-asyncio +certifi==2025.11.12 # via httpcore # via httpx # via requests -cffi==1.16.0 +cffi==2.0.0 # via cryptography # via sounddevice -charset-normalizer==3.3.2 +charset-normalizer==3.4.4 # via requests colorama==0.4.6 # via griffe -colorlog==6.7.0 +colorlog==6.10.1 # via nox -cryptography==42.0.7 +cryptography==46.0.3 # via azure-identity # via msal # via pyjwt -dirty-equals==0.6.0 -distlib==0.3.7 +dependency-groups==1.3.1 + # via nox +dirty-equals==0.11 +distlib==0.4.0 # via virtualenv -distro==1.8.0 +distro==1.9.0 # via openai -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio # via pytest # via trio -execnet==2.1.1 +execnet==2.1.2 # via pytest-xdist -executing==2.2.0 +executing==2.2.1 # via inline-snapshot -filelock==3.12.4 +filelock==3.19.1 # via virtualenv -frozenlist==1.7.0 +frozenlist==1.8.0 # via aiohttp # via aiosignal -griffe==1.13.0 +griffe==1.14.0 h11==0.16.0 # via httpcore httpcore==1.0.9 @@ -81,139 +86,145 @@ httpx==0.28.1 # via respx httpx-aiohttp==0.1.9 # via openai -idna==3.4 +humanize==4.13.0 + # via nox +idna==3.11 # via anyio # via httpx # via requests # via trio # via yarl -importlib-metadata==7.0.0 -iniconfig==2.0.0 +importlib-metadata==8.7.0 +iniconfig==2.1.0 # via pytest -inline-snapshot==0.28.0 -jiter==0.11.0 +inline-snapshot==0.31.1 +jiter==0.12.0 # via openai markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -msal==1.31.0 +msal==1.34.0 # via azure-identity # via msal-extensions -msal-extensions==1.2.0 +msal-extensions==1.3.1 # via azure-identity -multidict==6.5.0 +multidict==6.7.0 # via aiohttp # via yarl mypy==1.17.0 -mypy-extensions==1.0.0 +mypy-extensions==1.1.0 # via mypy nest-asyncio==1.6.0 -nodeenv==1.8.0 +nodeenv==1.9.1 # via pyright -nox==2023.4.22 +nox==2025.11.12 numpy==2.0.2 # via openai # via pandas # via pandas-stubs outcome==1.3.0.post0 # via trio -packaging==23.2 +packaging==25.0 + # via dependency-groups # via nox # via pytest -pandas==2.2.3 +pandas==2.3.3 # via openai -pandas-stubs==2.1.4.231227 +pandas-stubs==2.2.2.240807 # via openai pathspec==0.12.1 # via mypy -platformdirs==3.11.0 +platformdirs==4.4.0 # via virtualenv -pluggy==1.5.0 +pluggy==1.6.0 # via pytest -portalocker==2.10.1 - # via msal-extensions -propcache==0.3.2 +propcache==0.4.1 # via aiohttp # via yarl pycparser==2.23 # via cffi -pydantic==2.11.9 +pydantic==2.12.5 # via openai -pydantic-core==2.33.2 +pydantic-core==2.41.5 # via pydantic -pygments==2.18.0 +pygments==2.19.2 # via pytest # via rich -pyjwt==2.8.0 +pyjwt==2.10.1 # via msal pyright==1.1.399 -pytest==8.4.1 +pytest==8.4.2 # via inline-snapshot # via pytest-asyncio # via pytest-xdist -pytest-asyncio==0.24.0 -pytest-xdist==3.7.0 -python-dateutil==2.8.2 +pytest-asyncio==1.2.0 +pytest-xdist==3.8.0 +python-dateutil==2.9.0.post0 # via pandas # via time-machine -pytz==2023.3.post1 - # via dirty-equals +pytz==2025.2 # via pandas -requests==2.31.0 +requests==2.32.5 # via azure-core # via msal respx==0.22.0 -rich==13.7.1 +rich==14.2.0 # via inline-snapshot -ruff==0.9.4 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 - # via asttokens - # via azure-core +ruff==0.14.7 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via openai # via trio sortedcontainers==2.4.0 # via trio -sounddevice==0.5.1 +sounddevice==0.5.3 # via openai -time-machine==2.9.0 -tomli==2.0.2 +time-machine==2.19.0 +tomli==2.3.0 + # via dependency-groups # via inline-snapshot # via mypy + # via nox # via pytest -tqdm==4.66.5 +tqdm==4.67.1 # via openai -trio==0.27.0 -types-pyaudio==0.2.16.20240516 -types-pytz==2024.2.0.20241003 +trio==0.31.0 +types-pyaudio==0.2.16.20250801 +types-pytz==2025.2.0.20251108 # via pandas-stubs -types-tqdm==4.66.0.20240417 -typing-extensions==4.12.2 +types-requests==2.32.4.20250913 + # via types-tqdm +types-tqdm==4.67.0.20250809 +typing-extensions==4.15.0 + # via aiosignal + # via anyio # via azure-core # via azure-identity + # via cryptography + # via exceptiongroup # via multidict # via mypy # via openai # via pydantic # via pydantic-core # via pyright + # via pytest-asyncio # via typing-inspection -typing-inspection==0.4.1 + # via virtualenv +typing-inspection==0.4.2 # via pydantic -tzdata==2024.1 +tzdata==2025.2 # via pandas -urllib3==2.2.1 +urllib3==2.5.0 # via requests -virtualenv==20.24.5 + # via types-requests +virtualenv==20.35.4 # via nox websockets==15.0.1 # via openai -yarl==1.20.1 +yarl==1.22.0 # via aiohttp -zipp==3.17.0 +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 386ec3c590..8e021bd69b 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,30 +12,30 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.12.13 +aiohttp==3.13.2 # via httpx-aiohttp # via openai -aiosignal==1.3.2 +aiosignal==1.4.0 # via aiohttp -annotated-types==0.6.0 +annotated-types==0.7.0 # via pydantic -anyio==4.1.0 +anyio==4.12.0 # via httpx # via openai async-timeout==5.0.1 # via aiohttp -attrs==25.3.0 +attrs==25.4.0 # via aiohttp -certifi==2023.7.22 +certifi==2025.11.12 # via httpcore # via httpx -cffi==1.17.1 +cffi==2.0.0 # via sounddevice -distro==1.8.0 +distro==1.9.0 # via openai -exceptiongroup==1.2.2 +exceptiongroup==1.3.1 # via anyio -frozenlist==1.7.0 +frozenlist==1.8.0 # via aiohttp # via aiosignal h11==0.16.0 @@ -47,24 +47,24 @@ httpx==0.28.1 # via openai httpx-aiohttp==0.1.9 # via openai -idna==3.4 +idna==3.11 # via anyio # via httpx # via yarl -jiter==0.11.0 +jiter==0.12.0 # via openai -multidict==6.5.0 +multidict==6.7.0 # via aiohttp # via yarl numpy==2.0.2 # via openai # via pandas # via pandas-stubs -pandas==2.2.3 +pandas==2.3.3 # via openai pandas-stubs==2.2.2.240807 # via openai -propcache==0.3.2 +propcache==0.4.1 # via aiohttp # via yarl pycparser==2.23 @@ -75,21 +75,22 @@ pydantic-core==2.41.5 # via pydantic python-dateutil==2.9.0.post0 # via pandas -pytz==2024.1 +pytz==2025.2 # via pandas -six==1.16.0 +six==1.17.0 # via python-dateutil -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via openai -sounddevice==0.5.1 +sounddevice==0.5.3 # via openai -tqdm==4.66.5 +tqdm==4.67.1 # via openai -types-pytz==2024.2.0.20241003 +types-pytz==2025.2.0.20251108 # via pandas-stubs typing-extensions==4.15.0 + # via aiosignal # via anyio + # via exceptiongroup # via multidict # via openai # via pydantic @@ -101,5 +102,5 @@ tzdata==2025.2 # via pandas websockets==15.0.1 # via openai -yarl==1.20.1 +yarl==1.22.0 # via aiohttp From 575bbac13b3a57731a4e07b67636ae94463d43fa Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Tue, 2 Dec 2025 11:29:14 +0000 Subject: [PATCH 6/8] fix(streaming): correct indentation --- src/openai/_streaming.py | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/src/openai/_streaming.py b/src/openai/_streaming.py index 74e54f74fa..61a742668a 100644 --- a/src/openai/_streaming.py +++ b/src/openai/_streaming.py @@ -60,42 +60,42 @@ def __stream__(self) -> Iterator[_T]: if sse.data.startswith("[DONE]"): break - # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data - if sse.event and sse.event.startswith("thread."): - data = sse.json() - - if sse.event == "error" and is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], - ) - - yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) - else: - data = sse.json() - if is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], - ) - - yield process_data(data=data, cast_to=cast_to, response=response) + # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data + if sse.event and sse.event.startswith("thread."): + data = sse.json() + + if sse.event == "error" and is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) + else: + data = sse.json() + if is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data(data=data, cast_to=cast_to, response=response) finally: # Ensure the response is closed even if the consumer doesn't read all data @@ -163,42 +163,42 @@ async def __stream__(self) -> AsyncIterator[_T]: if sse.data.startswith("[DONE]"): break - # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data - if sse.event and sse.event.startswith("thread."): - data = sse.json() - - if sse.event == "error" and is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], - ) - - yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) - else: - data = sse.json() - if is_mapping(data) and data.get("error"): - message = None - error = data.get("error") - if is_mapping(error): - message = error.get("message") - if not message or not isinstance(message, str): - message = "An error occurred during streaming" - - raise APIError( - message=message, - request=self.response.request, - body=data["error"], - ) - - yield process_data(data=data, cast_to=cast_to, response=response) + # we have to special case the Assistants `thread.` events since we won't have an "event" key in the data + if sse.event and sse.event.startswith("thread."): + data = sse.json() + + if sse.event == "error" and is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data(data={"data": data, "event": sse.event}, cast_to=cast_to, response=response) + else: + data = sse.json() + if is_mapping(data) and data.get("error"): + message = None + error = data.get("error") + if is_mapping(error): + message = error.get("message") + if not message or not isinstance(message, str): + message = "An error occurred during streaming" + + raise APIError( + message=message, + request=self.response.request, + body=data["error"], + ) + + yield process_data(data=data, cast_to=cast_to, response=response) finally: # Ensure the response is closed even if the consumer doesn't read all data From c2a3cd502bfb03f68f62f50aed15a40458c0996e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:23:11 +0000 Subject: [PATCH 7/8] chore(docs): use environment variables for authentication in code snippets --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 470707e1f3..b8050a4cd6 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ pip install openai[aiohttp] Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python +import os import asyncio from openai import DefaultAioHttpClient from openai import AsyncOpenAI @@ -167,7 +168,7 @@ from openai import AsyncOpenAI async def main() -> None: async with AsyncOpenAI( - api_key="My API Key", + api_key=os.environ.get("OPENAI_API_KEY"), # This is the default and can be omitted http_client=DefaultAioHttpClient(), ) as client: chat_completion = await client.chat.completions.create( From 19e629ffa3f0981afa70d45bd07bd0570ca484cd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 05:04:15 +0000 Subject: [PATCH 8/8] release: 2.8.2 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 18 ++++++++++++++++++ pyproject.toml | 2 +- src/openai/_version.py | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 108509ed29..dd01b611c8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.8.1" + ".": "2.8.2" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bfa59348f..ad81c3016c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 2.8.2 (2025-12-03) + +Full Changelog: [v2.8.1...v2.8.2](https://github.com/openai/openai-python/compare/v2.8.1...v2.8.2) + +### Bug Fixes + +* **client:** avoid mutating user-provided response config object ([#2700](https://github.com/openai/openai-python/issues/2700)) ([e040d22](https://github.com/openai/openai-python/commit/e040d22c2df068e908f69dc6b892e7f8b3fe6e99)) +* ensure streams are always closed ([0b1a27f](https://github.com/openai/openai-python/commit/0b1a27f08639d14dfe40bf80b48e2b8a1a51593c)) +* **streaming:** correct indentation ([575bbac](https://github.com/openai/openai-python/commit/575bbac13b3a57731a4e07b67636ae94463d43fa)) + + +### Chores + +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([22cd586](https://github.com/openai/openai-python/commit/22cd586dbd5484b47f625da55db697691116b22b)) +* **docs:** use environment variables for authentication in code snippets ([c2a3cd5](https://github.com/openai/openai-python/commit/c2a3cd502bfb03f68f62f50aed15a40458c0996e)) +* **internal:** codegen related update ([307a066](https://github.com/openai/openai-python/commit/307a0664383b9d1d4151bc1a05a78c4fdcdcc9b0)) +* update lockfile ([b4109c5](https://github.com/openai/openai-python/commit/b4109c5fcf971ccfb25b4bdaef0bf36999f9eca5)) + ## 2.8.1 (2025-11-17) Full Changelog: [v2.8.0...v2.8.1](https://github.com/openai/openai-python/compare/v2.8.0...v2.8.1) diff --git a/pyproject.toml b/pyproject.toml index 3ad3b4a58a..b6d016e2c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openai" -version = "2.8.1" +version = "2.8.2" description = "The official Python library for the openai API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/openai/_version.py b/src/openai/_version.py index 6109cebf91..440fe0042c 100644 --- a/src/openai/_version.py +++ b/src/openai/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "openai" -__version__ = "2.8.1" # x-release-please-version +__version__ = "2.8.2" # x-release-please-version