From 9adfa0779a6a039fc276342308a0e101b3cae800 Mon Sep 17 00:00:00 2001 From: Avi Seth Date: Mon, 24 Aug 2026 23:08:07 +0200 Subject: [PATCH] perf(lib): stop pulling openai.types.beta into every import openai openai/__init__.py imports openai.lib.streaming, whose _assistants module imported openai.types.beta at module scope. That loaded 318 modules on every import openai, for annotations that from __future__ import annotations already defers and two runtime paths that can import locally. Move the annotation-only imports under TYPE_CHECKING and import RunStep and MessageContent inside the two functions that construct them at runtime. import openai drops from 1234 to 916 modules and from 359ms to 257ms median cold import (1.40x, python 3.13, 25 runs). No public API change: openai.AssistantEventHandler and openai.AsyncAssistantEventHandler stay eagerly exported. Refs #2819 --- src/openai/lib/streaming/_assistants.py | 30 ++++++++++++--------- tests/lib/test_streaming_lazy_types.py | 35 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 tests/lib/test_streaming_lazy_types.py diff --git a/src/openai/lib/streaming/_assistants.py b/src/openai/lib/streaming/_assistants.py index 314961230d..a854fc2889 100644 --- a/src/openai/lib/streaming/_assistants.py +++ b/src/openai/lib/streaming/_assistants.py @@ -10,18 +10,20 @@ from ..._httpx2 import timeout_exceptions from ..._models import construct_type from ..._streaming import Stream, AsyncStream -from ...types.beta import AssistantStreamEvent -from ...types.beta.threads import ( - Run, - Text, - Message, - ImageFile, - TextDelta, - MessageDelta, - MessageContent, - MessageContentDelta, -) -from ...types.beta.threads.runs import RunStep, ToolCall, RunStepDelta, ToolCallDelta + +if TYPE_CHECKING: + from ...types.beta import AssistantStreamEvent + from ...types.beta.threads import ( + Run, + Text, + Message, + ImageFile, + TextDelta, + MessageDelta, + MessageContent, + MessageContentDelta, + ) + from ...types.beta.threads.runs import RunStep, ToolCall, RunStepDelta, ToolCallDelta def _timeout_exceptions() -> tuple[type[Exception], ...]: @@ -903,6 +905,8 @@ def accumulate_run_step( return if event.event == "thread.run.step.delta": + from ...types.beta.threads.runs import RunStep + data = event.data snapshot = run_step_snapshots[data.id] @@ -928,6 +932,8 @@ def accumulate_event( current_message_snapshot: Message | None, ) -> tuple[Message | None, list[MessageContentDelta]]: """Returns a tuple of message snapshot and newly created text message deltas""" + from ...types.beta.threads import MessageContent + if event.event == "thread.message.created": return event.data, [] diff --git a/tests/lib/test_streaming_lazy_types.py b/tests/lib/test_streaming_lazy_types.py new file mode 100644 index 0000000000..10528ac687 --- /dev/null +++ b/tests/lib/test_streaming_lazy_types.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +import sys +import subprocess + +import openai + + +def _modules_after_import_openai() -> set[str]: + """Return the module names loaded by a bare `import openai` in a fresh interpreter.""" + output = subprocess.run( + [ + sys.executable, + "-c", + "import sys\nimport openai\nprint('\\n'.join(sys.modules))\n", + ], + check=True, + capture_output=True, + text=True, + ).stdout + return set(output.split()) + + +def test_import_openai_does_not_load_beta_types() -> None: + # `openai.lib.streaming` only needs `openai.types.beta` for annotations and for two + # narrow runtime paths, so importing the package must not pull the namespace in. + modules = _modules_after_import_openai() + + assert "openai" in modules + assert not [module for module in modules if module.startswith("openai.types.beta")] + + +def test_assistant_event_handlers_are_still_eagerly_exported() -> None: + assert openai.AssistantEventHandler.__name__ == "AssistantEventHandler" + assert openai.AsyncAssistantEventHandler.__name__ == "AsyncAssistantEventHandler"