Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(client): add support for streaming binary responses #866

Merged
merged 1 commit into from
Nov 23, 2023
Merged
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
5 changes: 4 additions & 1 deletion examples/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
def main() -> None:
# Create text-to-speech audio file
response = openai.audio.speech.create(
model="tts-1", voice="alloy", input="the quick brown fox jumped over the lazy dogs"
model="tts-1",
voice="alloy",
input="the quick brown fox jumped over the lazy dogs",
stream=True,
)

response.stream_to_file(speech_file_path)
Expand Down
8 changes: 6 additions & 2 deletions src/openai/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ def _request(
self._prepare_request(request)

try:
response = self._client.send(request, auth=self.custom_auth, stream=stream)
response = self._client.send(request, auth=self.custom_auth, stream=stream or options.stream or False)
log.debug(
'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
)
Expand Down Expand Up @@ -1304,7 +1304,7 @@ async def _request(
await self._prepare_request(request)

try:
response = await self._client.send(request, auth=self.custom_auth, stream=stream)
response = await self._client.send(request, auth=self.custom_auth, stream=stream or options.stream or False)
log.debug(
'HTTP Request: %s %s "%i %s"', request.method, request.url, response.status_code, response.reason_phrase
)
Expand Down Expand Up @@ -1541,6 +1541,7 @@ def make_request_options(
idempotency_key: str | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
post_parser: PostParser | NotGiven = NOT_GIVEN,
stream: bool | None = None,
) -> RequestOptions:
"""Create a dict of type RequestOptions without keys of NotGiven values."""
options: RequestOptions = {}
Expand All @@ -1562,6 +1563,9 @@ def make_request_options(
if idempotency_key is not None:
options["idempotency_key"] = idempotency_key

if stream is not None:
options["stream"] = stream

if is_given(post_parser):
# internal
options["post_parser"] = post_parser # type: ignore
Expand Down
2 changes: 2 additions & 0 deletions src/openai/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
params: Query
headers: Headers
max_retries: int
stream: bool | None
timeout: float | Timeout | None
files: HttpxRequestFiles | None
idempotency_key: str
Expand All @@ -420,6 +421,7 @@ class FinalRequestOptions(pydantic.BaseModel):
timeout: Union[float, Timeout, None, NotGiven] = NotGiven()
files: Union[HttpxRequestFiles, None] = None
idempotency_key: Union[str, None] = None
stream: Union[bool, None] = None
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()

# It should be noted that we cannot use `json` here as that would override
Expand Down
23 changes: 21 additions & 2 deletions src/openai/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,16 @@ def stream_to_file(
chunk_size: int | None = None,
) -> None:
"""
Stream the output to the given file.
Stream the output to the given file. NOTE, requires passing `stream=True`
to the request for expected behavior, e.g.,

response = openai.audio.speech.create(
model="tts-1",
voice="alloy",
input="the quick brown fox jumped over the lazy dogs",
stream=True,
)
response.stream_to_file(speech_file_path)
"""
pass

Expand Down Expand Up @@ -185,7 +194,16 @@ async def astream_to_file(
chunk_size: int | None = None,
) -> None:
"""
Stream the output to the given file.
Stream the output to the given file. NOTE, requires passing `stream=True`
to the request for expected behavior, e.g.,

response = await openai.audio.speech.create(
model="tts-1",
voice="alloy",
input="the quick brown fox jumped over the lazy dogs",
stream=True,
)
response.stream_to_file(speech_file_path)
"""
pass

Expand Down Expand Up @@ -257,6 +275,7 @@ async def aclose(self) -> None:
class RequestOptions(TypedDict, total=False):
headers: Headers
max_retries: int
stream: bool
timeout: float | Timeout | None
params: Query
extra_json: AnyMapping
Expand Down
20 changes: 18 additions & 2 deletions src/openai/resources/audio/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
stream: bool | None = None,
) -> HttpxBinaryResponseContent:
"""
Generates audio from the input text.
Expand All @@ -67,6 +68,9 @@ def create(
extra_body: Add additional JSON properties to the request

timeout: Override the client-level default timeout for this request, in seconds

stream: Whether or not the response content should be streamed (i.e. not read to
completion immediately), default False
"""
return self._post(
"/audio/speech",
Expand All @@ -81,7 +85,11 @@ def create(
speech_create_params.SpeechCreateParams,
),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
stream=stream,
),
cast_to=HttpxBinaryResponseContent,
)
Expand All @@ -108,6 +116,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
stream: bool | None = None,
) -> HttpxBinaryResponseContent:
"""
Generates audio from the input text.
Expand All @@ -134,6 +143,9 @@ async def create(
extra_body: Add additional JSON properties to the request

timeout: Override the client-level default timeout for this request, in seconds

stream: Whether or not the response content should be streamed (i.e. not read to
completion immediately), default False
"""
return await self._post(
"/audio/speech",
Expand All @@ -148,7 +160,11 @@ async def create(
speech_create_params.SpeechCreateParams,
),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
stream=stream,
),
cast_to=HttpxBinaryResponseContent,
)
Expand Down
20 changes: 18 additions & 2 deletions src/openai/resources/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ def content(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
stream: bool | None = None,
) -> HttpxBinaryResponseContent:
"""
Returns the contents of the specified file.
Expand All @@ -224,11 +225,18 @@ def content(
extra_body: Add additional JSON properties to the request

timeout: Override the client-level default timeout for this request, in seconds

stream: Whether or not the response content should be streamed (i.e. not read to
completion immediately), default False
"""
return self._get(
f"/files/{file_id}/content",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
stream=stream,
),
cast_to=HttpxBinaryResponseContent,
)
Expand Down Expand Up @@ -475,6 +483,7 @@ async def content(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
stream: bool | None = None,
) -> HttpxBinaryResponseContent:
"""
Returns the contents of the specified file.
Expand All @@ -487,11 +496,18 @@ async def content(
extra_body: Add additional JSON properties to the request

timeout: Override the client-level default timeout for this request, in seconds

stream: Whether or not the response content should be streamed (i.e. not read to
completion immediately), default False
"""
return await self._get(
f"/files/{file_id}/content",
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
stream=stream,
),
cast_to=HttpxBinaryResponseContent,
)
Expand Down