Commit dbf975c8

Robert Craigie <robert@craigie.dev>
2025-03-21 21:32:27
fix(package): make sounddevice and numpy optional dependencies
1 parent 35e0e11
src/openai/_extras/__init__.py
@@ -1,2 +1,3 @@
 from .numpy_proxy import numpy as numpy, has_numpy as has_numpy
 from .pandas_proxy import pandas as pandas
+from .sounddevice_proxy import sounddevice as sounddevice
src/openai/_extras/numpy_proxy.py
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
     import numpy as numpy
 
 
-NUMPY_INSTRUCTIONS = format_instructions(library="numpy", extra="datalib")
+NUMPY_INSTRUCTIONS = format_instructions(library="numpy", extra="audio")
 
 
 class NumpyProxy(LazyProxy[Any]):
src/openai/_extras/sounddevice_proxy.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+from typing_extensions import override
+
+from .._utils import LazyProxy
+from ._common import MissingDependencyError, format_instructions
+
+if TYPE_CHECKING:
+    import sounddevice as sounddevice  # type: ignore
+
+
+SOUNDDEVICE_INSTRUCTIONS = format_instructions(library="sounddevice", extra="audio")
+
+
+class SounddeviceProxy(LazyProxy[Any]):
+    @override
+    def __load__(self) -> Any:
+        try:
+            import sounddevice  # type: ignore
+        except ImportError as err:
+            raise MissingDependencyError(SOUNDDEVICE_INSTRUCTIONS) from err
+
+        return sounddevice
+
+
+if not TYPE_CHECKING:
+    sounddevice = SounddeviceProxy()
src/openai/helpers/local_audio_player.py
@@ -1,15 +1,18 @@
 # mypy: ignore-errors
+from __future__ import annotations
+
 import queue
 import asyncio
 from typing import Any, Union, Callable, AsyncGenerator, cast
-
-import numpy as np
-import sounddevice as sd  # type: ignore
-import numpy.typing as npt
+from typing_extensions import TYPE_CHECKING
 
 from .. import _legacy_response
+from .._extras import numpy as np, sounddevice as sd
 from .._response import StreamedBinaryAPIResponse, AsyncStreamedBinaryAPIResponse
 
+if TYPE_CHECKING:
+    import numpy.typing as npt
+
 SAMPLE_RATE = 24000
 
 
@@ -62,7 +65,7 @@ class LocalAudioPlayer:
             if input.dtype == np.int16 and self.dtype == np.float32:
                 audio_content = (input.astype(np.float32) / 32767.0).reshape(-1, self.channels)
             elif input.dtype == np.float32:
-                audio_content = cast(npt.NDArray[np.float32], input)
+                audio_content = cast('npt.NDArray[np.float32]', input)
             else:
                 raise ValueError(f"Unsupported dtype: {input.dtype}")
         else:
src/openai/helpers/microphone.py
@@ -1,16 +1,18 @@
 # mypy: ignore-errors
+from __future__ import annotations
+
 import io
 import time
 import wave
 import asyncio
 from typing import Any, Type, Union, Generic, TypeVar, Callable, overload
-from typing_extensions import Literal
+from typing_extensions import TYPE_CHECKING, Literal
 
-import numpy as np
-import sounddevice as sd  # type: ignore
-import numpy.typing as npt
+from .._types import FileTypes, FileContent
+from .._extras import numpy as np, sounddevice as sd
 
-from openai._types import FileTypes, FileContent
+if TYPE_CHECKING:
+    import numpy.typing as npt
 
 SAMPLE_RATE = 24000
 
pyproject.toml
@@ -16,8 +16,6 @@ dependencies = [
     "sniffio",
     "tqdm > 4",
     "jiter>=0.4.0, <1",
-    "sounddevice>=0.5.1",
-    "numpy>=2.0.2",
 ]
 requires-python = ">= 3.8"
 classifiers = [
@@ -47,6 +45,7 @@ openai = "openai.cli:main"
 [project.optional-dependencies]
 realtime = ["websockets >= 13, < 15"]
 datalib = ["numpy >= 1", "pandas >= 1.2.3", "pandas-stubs >= 1.1.0.11"]
+audio = ["sounddevice>=0.5.1", "numpy>=2.0.2"]
 
 [tool.rye]
 managed = true