From d0899b3ec3fc7be239052d2d0b99b82d34249e3a Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:19:31 +0100 Subject: [PATCH] fix: preserve explicit defaults in realtime events --- .../resources/beta/realtime/realtime.py | 4 +- src/openai/resources/realtime/realtime.py | 12 +-- .../lib/test_realtime_event_serialization.py | 83 +++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 tests/lib/test_realtime_event_serialization.py diff --git a/src/openai/resources/beta/realtime/realtime.py b/src/openai/resources/beta/realtime/realtime.py index 7124287c0e..c968619329 100644 --- a/src/openai/resources/beta/realtime/realtime.py +++ b/src/openai/resources/beta/realtime/realtime.py @@ -282,7 +282,7 @@ async def recv_bytes(self) -> bytes: async def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: data = ( - event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + event.to_json(use_api_names=True, exclude_unset=True) if isinstance(event, BaseModel) else json.dumps(await async_maybe_transform(event, RealtimeClientEventParam)) ) @@ -467,7 +467,7 @@ def recv_bytes(self) -> bytes: def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: data = ( - event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + event.to_json(use_api_names=True, exclude_unset=True) if isinstance(event, BaseModel) else json.dumps(maybe_transform(event, RealtimeClientEventParam)) ) diff --git a/src/openai/resources/realtime/realtime.py b/src/openai/resources/realtime/realtime.py index 3cb815bddf..e4ecb1071a 100644 --- a/src/openai/resources/realtime/realtime.py +++ b/src/openai/resources/realtime/realtime.py @@ -345,7 +345,7 @@ async def recv_bytes(self) -> bytes: async def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: data = ( - event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + event.to_json(use_api_names=True, exclude_unset=True) if isinstance(event, BaseModel) else json.dumps(await async_maybe_transform(event, RealtimeClientEventParam)) ) @@ -603,9 +603,7 @@ def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: are automatically sent once the WebSocket connection opens. """ data = ( - event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) - if isinstance(event, BaseModel) - else json.dumps(event) + event.to_json(use_api_names=True, exclude_unset=True) if isinstance(event, BaseModel) else json.dumps(event) ) self.__send_queue.enqueue(data) @@ -827,7 +825,7 @@ def recv_bytes(self) -> bytes: def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: data = ( - event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) + event.to_json(use_api_names=True, exclude_unset=True) if isinstance(event, BaseModel) else json.dumps(maybe_transform(event, RealtimeClientEventParam)) ) @@ -1073,9 +1071,7 @@ def send(self, event: RealtimeClientEvent | RealtimeClientEventParam) -> None: are automatically sent once the WebSocket connection opens. """ data = ( - event.to_json(use_api_names=True, exclude_defaults=True, exclude_unset=True) - if isinstance(event, BaseModel) - else json.dumps(event) + event.to_json(use_api_names=True, exclude_unset=True) if isinstance(event, BaseModel) else json.dumps(event) ) self.__send_queue.enqueue(data) diff --git a/tests/lib/test_realtime_event_serialization.py b/tests/lib/test_realtime_event_serialization.py new file mode 100644 index 0000000000..9b14f6fd8f --- /dev/null +++ b/tests/lib/test_realtime_event_serialization.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import json +from unittest.mock import Mock, AsyncMock + +import pytest + +from openai.resources.realtime.realtime import ( + RealtimeConnection, + AsyncRealtimeConnection, +) +from openai.resources.beta.realtime.realtime import ( + RealtimeConnection as BetaRealtimeConnection, + AsyncRealtimeConnection as BetaAsyncRealtimeConnection, +) +from openai.types.realtime.session_update_event import SessionUpdateEvent +from openai.types.beta.realtime.session_update_event import ( + Session as BetaSession, + SessionUpdateEvent as BetaSessionUpdateEvent, +) +from openai.types.realtime.realtime_session_create_request import RealtimeSessionCreateRequest + + +def test_sync_realtime_serializes_explicit_default_value() -> None: + websocket = Mock() + connection = RealtimeConnection(websocket) + event = SessionUpdateEvent( + type="session.update", + session=RealtimeSessionCreateRequest(type="realtime", instructions=None), + ) + + connection.send(event) + + payload = json.loads(websocket.send.call_args.args[0]) + assert payload["session"]["instructions"] is None + assert "event_id" not in payload + + +@pytest.mark.asyncio +async def test_async_realtime_serializes_explicit_default_value() -> None: + websocket = AsyncMock() + connection = AsyncRealtimeConnection(websocket) + event = SessionUpdateEvent( + type="session.update", + session=RealtimeSessionCreateRequest(type="realtime", instructions=None), + ) + + await connection.send(event) + + payload = json.loads(websocket.send.call_args.args[0]) + assert payload["session"]["instructions"] is None + assert "event_id" not in payload + + +def test_sync_beta_realtime_serializes_explicit_null_turn_detection() -> None: + websocket = Mock() + connection = BetaRealtimeConnection(websocket) + event = BetaSessionUpdateEvent( + type="session.update", + session=BetaSession(turn_detection=None), + ) + + connection.send(event) + + payload = json.loads(websocket.send.call_args.args[0]) + assert payload["session"]["turn_detection"] is None + assert "event_id" not in payload + + +@pytest.mark.asyncio +async def test_async_beta_realtime_serializes_explicit_null_turn_detection() -> None: + websocket = AsyncMock() + connection = BetaAsyncRealtimeConnection(websocket) + event = BetaSessionUpdateEvent( + type="session.update", + session=BetaSession(turn_detection=None), + ) + + await connection.send(event) + + payload = json.loads(websocket.send.call_args.args[0]) + assert payload["session"]["turn_detection"] is None + assert "event_id" not in payload