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
48 changes: 20 additions & 28 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Set up Rye
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true

- name: Install dependencies
run: rye sync --all-features
Expand All @@ -48,13 +46,11 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Set up Rye
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true

- name: Install dependencies
run: rye sync --all-features
Expand Down Expand Up @@ -89,13 +85,11 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Set up Rye
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true

- name: Bootstrap
run: ./scripts/bootstrap
Expand All @@ -112,13 +106,11 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Set up Rye
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true
- name: Install dependencies
run: |
rye sync --all-features
Expand Down
12 changes: 5 additions & 7 deletions .github/workflows/create-releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ jobs:
repo: ${{ github.event.repository.full_name }}
stainless-api-key: ${{ secrets.STAINLESS_API_KEY }}

- name: Install Rye
- name: Set up Rye
if: ${{ steps.release.outputs.releases_created }}
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true

- name: Publish to PyPI
if: ${{ steps.release.outputs.releases_created }}
Expand Down
27 changes: 11 additions & 16 deletions .github/workflows/detect-breaking-changes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ jobs:
# Ensure we can check out the pull request base in the script below.
fetch-depth: ${{ env.FETCH_DEPTH }}

- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Set up Rye
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true
- name: Install dependencies
run: |
rye sync --all-features
Expand All @@ -49,14 +47,12 @@ jobs:
with:
path: openai-python

- name: Install Rye
working-directory: openai-python
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Set up Rye
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true
working-directory: openai-python

- name: Install dependencies
working-directory: openai-python
Expand Down Expand Up @@ -85,4 +81,3 @@ jobs:
- name: Run integration type checks
working-directory: openai-agents-python
run: make mypy

12 changes: 5 additions & 7 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install Rye
run: |
curl -sSf https://rye.astral.sh/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
env:
RYE_VERSION: '0.44.0'
RYE_INSTALL_OPTION: '--yes'
- name: Set up Rye
uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8
with:
version: '0.44.0'
enable-cache: true

- name: Publish to PyPI
run: |
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.31.0"
".": "2.32.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 152
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-a6eca1bd01e0c434af356fe5275c206057216a4e626d1051d294c27016cd6d05.yml
openapi_spec_hash: 68abda9122013a9ae3f084cfdbe8e8c1
config_hash: 4975e16a94e8f9901428022044131888
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-7c540cce6eb30401259f4831ea9803b6d88501605d13734f98212cbb3b199e10.yml
openapi_spec_hash: 06e656be22bbb92689954253668b42fc
config_hash: 1a88b104658b6c854117996c080ebe6b
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 2.32.0 (2026-04-14)

Full Changelog: [v2.31.0...v2.32.0](https://github.com/openai/openai-python/compare/v2.31.0...v2.32.0)

### Features

* **api:** Add detail to InputFileContent ([60de21d](https://github.com/openai/openai-python/commit/60de21d1fcfbcadea0d9b8d884c73c9dc49d14ff))
* **api:** add OAuthErrorCode type ([0c8d2c3](https://github.com/openai/openai-python/commit/0c8d2c3b44242c9139dc554896ea489b56e236b8))
* **client:** add event handler implementation for websockets ([0280d05](https://github.com/openai/openai-python/commit/0280d0568f706684ecbf0aabf3575cdcb7fd22d5))
* **client:** allow enqueuing to websockets even when not connected ([67aa20e](https://github.com/openai/openai-python/commit/67aa20e69bc0e4a3b7694327c808606bfa24a966))
* **client:** support reconnection in websockets ([eb72a95](https://github.com/openai/openai-python/commit/eb72a953ea9dc5beec3eef537be6eb32292c3f65))


### Bug Fixes

* ensure file data are only sent as 1 parameter ([c0c2ecd](https://github.com/openai/openai-python/commit/c0c2ecd0f6b64fa5fafda6134bb06995b143a2cf))


### Documentation

* improve examples ([84712fa](https://github.com/openai/openai-python/commit/84712fa0f094b53151a0fe6ac85aa98018b2a7e2))

## 2.31.0 (2026-04-08)

Full Changelog: [v2.30.0...v2.31.0](https://github.com/openai/openai-python/compare/v2.30.0...v2.31.0)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "openai"
version = "2.31.0"
version = "2.32.0"
description = "The official Python library for the openai API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
7 changes: 7 additions & 0 deletions src/openai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@
InternalServerError,
PermissionDeniedError,
LengthFinishReasonError,
WebSocketQueueFullError,
UnprocessableEntityError,
APIResponseValidationError,
InvalidWebhookSignatureError,
ContentFilterFinishReasonError,
WebSocketConnectionClosedError,
)
from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
from ._utils._logs import setup_logging as _setup_logging
from ._legacy_response import HttpxBinaryResponseContent as HttpxBinaryResponseContent
from .types.websocket_reconnection import ReconnectingEvent, ReconnectingOverrides

__all__ = [
"types",
Expand Down Expand Up @@ -84,6 +87,10 @@
"DefaultHttpxClient",
"DefaultAsyncHttpxClient",
"DefaultAioHttpClient",
"ReconnectingEvent",
"ReconnectingOverrides",
"WebSocketQueueFullError",
"WebSocketConnectionClosedError",
]

if not _t.TYPE_CHECKING:
Expand Down
85 changes: 85 additions & 0 deletions src/openai/_event_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import threading
from typing import Any, Callable

EventHandler = Callable[..., Any]


class EventHandlerRegistry:
"""Thread-safe (optional) registry of event handlers."""

def __init__(self, *, use_lock: bool = False) -> None:
self._handlers: dict[str, list[EventHandler]] = {}
self._once_ids: set[int] = set()
self._lock: threading.Lock | None = threading.Lock() if use_lock else None

def _acquire(self) -> None:
if self._lock is not None:
self._lock.acquire()

def _release(self) -> None:
if self._lock is not None:
self._lock.release()

def add(self, event_type: str, handler: EventHandler, *, once: bool = False) -> None:
self._acquire()
try:
handlers = self._handlers.setdefault(event_type, [])
handlers.append(handler)
if once:
self._once_ids.add(id(handler))
finally:
self._release()

def remove(self, event_type: str, handler: EventHandler) -> None:
self._acquire()
try:
handlers = self._handlers.get(event_type)
if handlers is not None:
try:
handlers.remove(handler)
except ValueError:
pass
self._once_ids.discard(id(handler))
finally:
self._release()

def get_handlers(self, event_type: str) -> list[EventHandler]:
"""Return a snapshot of handlers for the given event type, removing once-handlers."""
self._acquire()
try:
handlers = self._handlers.get(event_type)
if not handlers:
return []
result = list(handlers)
to_remove = [h for h in result if id(h) in self._once_ids]
for h in to_remove:
handlers.remove(h)
self._once_ids.discard(id(h))
return result
finally:
self._release()

def has_handlers(self, event_type: str) -> bool:
self._acquire()
try:
handlers = self._handlers.get(event_type)
return bool(handlers)
finally:
self._release()

def merge_into(self, target: EventHandlerRegistry) -> None:
"""Move all handlers from this registry into *target*, then clear self."""
self._acquire()
try:
for event_type, handlers in self._handlers.items():
for handler in handlers:
once = id(handler) in self._once_ids
target.add(event_type, handler, once=once)
self._handlers.clear()
self._once_ids.clear()
finally:
self._release()
18 changes: 18 additions & 0 deletions src/openai/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"ContentFilterFinishReasonError",
"InvalidWebhookSignatureError",
"SubjectTokenProviderError",
"WebSocketConnectionClosedError",
"WebSocketQueueFullError",
]


Expand Down Expand Up @@ -187,3 +189,19 @@ def __init__(self) -> None:

class InvalidWebhookSignatureError(ValueError):
"""Raised when a webhook signature is invalid, meaning the computed signature does not match the expected signature."""


class WebSocketConnectionClosedError(OpenAIError):
"""Raised when a WebSocket connection closes with unsent messages."""

unsent_messages: list[str]

def __init__(self, message: str, *, unsent_messages: list[str]) -> None:
super().__init__(message)
self.unsent_messages = unsent_messages


class WebSocketQueueFullError(OpenAIError):
"""Raised when the outgoing WebSocket message queue exceeds its byte-size limit."""

pass
Loading
Loading