From 0e93c3b5bc9cfa041e91962fd82c0d9358125024 Mon Sep 17 00:00:00 2001 From: Stainless Bot <107565488+stainless-bot@users.noreply.github.com> Date: Mon, 15 Jan 2024 09:11:27 -0500 Subject: [PATCH] feat(client): add support for streaming raw responses (#1072) As an alternative to `with_raw_response` we now provide `with_streaming_response` as well. When using these methods you will have to use a context manager to ensure that the response is always cleaned up. --- README.md | 37 +- examples/audio.py | 16 +- src/openai/__init__.py | 1 + src/openai/_base_client.py | 279 +++++---- src/openai/_client.py | 32 + src/openai/_constants.py | 2 +- src/openai/_legacy_response.py | 385 ++++++++++++ src/openai/_response.py | 570 ++++++++++++++++-- src/openai/_types.py | 166 +---- src/openai/resources/__init__.py | 110 +++- src/openai/resources/audio/__init__.py | 37 +- src/openai/resources/audio/audio.py | 42 +- src/openai/resources/audio/speech.py | 45 +- src/openai/resources/audio/transcriptions.py | 29 +- src/openai/resources/audio/translations.py | 29 +- src/openai/resources/beta/__init__.py | 33 +- .../resources/beta/assistants/__init__.py | 22 +- .../resources/beta/assistants/assistants.py | 82 ++- src/openai/resources/beta/assistants/files.py | 59 +- src/openai/resources/beta/beta.py | 38 +- src/openai/resources/beta/threads/__init__.py | 33 +- .../beta/threads/messages/__init__.py | 22 +- .../resources/beta/threads/messages/files.py | 39 +- .../beta/threads/messages/messages.py | 72 ++- .../resources/beta/threads/runs/__init__.py | 22 +- .../resources/beta/threads/runs/runs.py | 92 ++- .../resources/beta/threads/runs/steps.py | 39 +- src/openai/resources/beta/threads/threads.py | 93 ++- src/openai/resources/chat/__init__.py | 22 +- src/openai/resources/chat/chat.py | 27 +- src/openai/resources/chat/completions.py | 29 +- src/openai/resources/completions.py | 29 +- src/openai/resources/embeddings.py | 29 +- src/openai/resources/files.py | 109 +++- src/openai/resources/fine_tuning/__init__.py | 22 +- .../resources/fine_tuning/fine_tuning.py | 27 +- src/openai/resources/fine_tuning/jobs.py | 69 ++- src/openai/resources/images.py | 49 +- src/openai/resources/models.py | 49 +- src/openai/resources/moderations.py | 29 +- tests/api_resources/audio/test_speech.py | 66 +- .../audio/test_transcriptions.py | 33 + .../api_resources/audio/test_translations.py | 33 + .../beta/assistants/test_files.py | 127 ++++ tests/api_resources/beta/test_assistants.py | 147 +++++ tests/api_resources/beta/test_threads.py | 147 +++++ .../beta/threads/messages/test_files.py | 67 ++ .../beta/threads/runs/test_steps.py | 67 ++ .../beta/threads/test_messages.py | 129 ++++ tests/api_resources/beta/threads/test_runs.py | 193 ++++++ tests/api_resources/chat/test_completions.py | 103 +++- tests/api_resources/fine_tuning/test_jobs.py | 149 +++++ tests/api_resources/test_completions.py | 83 ++- tests/api_resources/test_embeddings.py | 33 + tests/api_resources/test_files.py | 201 +++++- tests/api_resources/test_images.py | 93 +++ tests/api_resources/test_models.py | 87 +++ tests/api_resources/test_moderations.py | 31 + tests/test_client.py | 76 ++- tests/test_response.py | 50 ++ tests/utils.py | 5 + 61 files changed, 4273 insertions(+), 563 deletions(-) create mode 100644 src/openai/_legacy_response.py create mode 100644 tests/test_response.py diff --git a/README.md b/README.md index e86ac6553..22e7ac795 100644 --- a/README.md +++ b/README.md @@ -414,7 +414,7 @@ if response.my_field is None: ### Accessing raw response data (e.g. headers) -The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call. +The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., ```py from openai import OpenAI @@ -433,7 +433,40 @@ completion = response.parse() # get the object that `chat.completions.create()` print(completion) ``` -These methods return an [`APIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_response.py) object. +These methods return an [`LegacyAPIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. + +For the sync client this will mostly be the same with the exception +of `content` & `text` will be methods instead of properties. In the +async client, all methods will be async. + +A migration script will be provided & the migration in general should +be smooth. + +#### `.with_streaming_response` + +The above interface eagerly reads the full response body when you make the request, which may not always be what you want. + +To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. + +As such, `.with_streaming_response` methods return a different [`APIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_response.py) object, and the async client returns an [`AsyncAPIResponse`](https://github.com/openai/openai-python/tree/main/src/openai/_response.py) object. + +```python +with client.chat.completions.with_streaming_response.create( + messages=[ + { + "role": "user", + "content": "Say this is a test", + } + ], + model="gpt-3.5-turbo", +) as response: + print(response.headers.get("X-My-Header")) + + for line in response.iter_lines(): + print(line) +``` + +The context manager is required so that the response will reliably be closed. ### Configuring the HTTP client diff --git a/examples/audio.py b/examples/audio.py index a5f535dcd..73491090f 100755 --- a/examples/audio.py +++ b/examples/audio.py @@ -12,14 +12,18 @@ 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" - ) - - response.stream_to_file(speech_file_path) + with openai.audio.speech.with_streaming_response.create( + model="tts-1", + voice="alloy", + input="the quick brown fox jumped over the lazy dogs", + ) as response: + response.stream_to_file(speech_file_path) # Create transcription from audio file - transcription = openai.audio.transcriptions.create(model="whisper-1", file=speech_file_path) + transcription = openai.audio.transcriptions.create( + model="whisper-1", + file=speech_file_path, + ) print(transcription.text) # Create translation from audio file diff --git a/src/openai/__init__.py b/src/openai/__init__.py index 64c93e944..0de58b332 100644 --- a/src/openai/__init__.py +++ b/src/openai/__init__.py @@ -10,6 +10,7 @@ from ._utils import file_from_path from ._client import Client, OpenAI, Stream, Timeout, Transport, AsyncClient, AsyncOpenAI, AsyncStream, RequestOptions from ._version import __title__, __version__ +from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse from ._exceptions import ( APIError, OpenAIError, diff --git a/src/openai/_base_client.py b/src/openai/_base_client.py index c2c2db5f4..1dfbd7dfb 100644 --- a/src/openai/_base_client.py +++ b/src/openai/_base_client.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os import json import time import uuid @@ -31,7 +30,7 @@ overload, ) from functools import lru_cache -from typing_extensions import Literal, override +from typing_extensions import Literal, override, get_origin import anyio import httpx @@ -61,18 +60,22 @@ AsyncTransport, RequestOptions, ModelBuilderProtocol, - BinaryResponseContent, ) from ._utils import is_dict, is_given, is_mapping from ._compat import model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type -from ._response import APIResponse +from ._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + extract_response_type, +) from ._constants import ( DEFAULT_LIMITS, DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, RAW_RESPONSE_HEADER, - STREAMED_RAW_RESPONSE_HEADER, + OVERRIDE_CAST_TO_HEADER, ) from ._streaming import Stream, AsyncStream from ._exceptions import ( @@ -81,6 +84,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._legacy_response import LegacyAPIResponse log: logging.Logger = logging.getLogger(__name__) @@ -493,28 +497,25 @@ def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, o serialized[key] = value return serialized - def _process_response( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - response: httpx.Response, - stream: bool, - stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, - ) -> ResponseT: - api_response = APIResponse( - raw=response, - client=self, - cast_to=cast_to, - stream=stream, - stream_cls=stream_cls, - options=options, - ) + def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]: + if not is_given(options.headers): + return cast_to - if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": - return cast(ResponseT, api_response) + # make a copy of the headers so we don't mutate user-input + headers = dict(options.headers) - return api_response.parse() + # we internally support defining a temporary header to override the + # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` + # see _response.py for implementation details + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + if is_given(override_cast_to): + options.headers = headers + return cast(Type[ResponseT], override_cast_to) + + return cast_to + + def _should_stream_response_body(self, request: httpx.Request) -> bool: + return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return] def _process_response_data( self, @@ -540,12 +541,6 @@ def _process_response_data( except pydantic.ValidationError as err: raise APIResponseValidationError(response=response, body=data) from err - def _should_stream_response_body(self, *, request: httpx.Request) -> bool: - if request.headers.get(STREAMED_RAW_RESPONSE_HEADER) == "true": - return True - - return False - @property def qs(self) -> Querystring: return Querystring() @@ -610,6 +605,8 @@ def _calculate_retry_timeout( if response_headers is not None: retry_header = response_headers.get("retry-after") try: + # note: the spec indicates that this should only ever be an integer + # but if someone sends a float there's no reason for us to not respect it retry_after = float(retry_header) except Exception: retry_date_tuple = email.utils.parsedate_tz(retry_header) @@ -873,6 +870,7 @@ def _request( stream: bool, stream_cls: type[_StreamT] | None, ) -> ResponseT | _StreamT: + cast_to = self._maybe_override_cast_to(cast_to, options) self._prepare_options(options) retries = self._remaining_retries(remaining_retries, options) @@ -987,6 +985,63 @@ def _retry_request( stream_cls=stream_cls, ) + def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + ) -> ResponseT: + if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": + return cast( + ResponseT, + LegacyAPIResponse( + raw=response, + client=self, + cast_to=cast_to, + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + origin = get_origin(cast_to) or cast_to + + if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if not issubclass(origin, APIResponse): + raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + ResponseT, + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = APIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return api_response.parse() + def _request_api_list( self, model: Type[object], @@ -1353,6 +1408,7 @@ async def _request( stream_cls: type[_AsyncStreamT] | None, remaining_retries: int | None, ) -> ResponseT | _AsyncStreamT: + cast_to = self._maybe_override_cast_to(cast_to, options) await self._prepare_options(options) retries = self._remaining_retries(remaining_retries, options) @@ -1428,7 +1484,7 @@ async def _request( log.debug("Re-raising status error") raise self._make_status_error_from_response(err.response) from None - return self._process_response( + return await self._process_response( cast_to=cast_to, options=options, response=response, @@ -1465,6 +1521,63 @@ async def _retry_request( stream_cls=stream_cls, ) + async def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + ) -> ResponseT: + if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": + return cast( + ResponseT, + LegacyAPIResponse( + raw=response, + client=self, + cast_to=cast_to, + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + origin = get_origin(cast_to) or cast_to + + if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if not issubclass(origin, AsyncAPIResponse): + raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + "ResponseT", + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = AsyncAPIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return await api_response.parse() + def _request_api_list( self, model: Type[_T], @@ -1783,105 +1896,3 @@ def _merge_mappings( """ merged = {**obj1, **obj2} return {key: value for key, value in merged.items() if not isinstance(value, Omit)} - - -class HttpxBinaryResponseContent(BinaryResponseContent): - response: httpx.Response - - def __init__(self, response: httpx.Response) -> None: - self.response = response - - @property - @override - def content(self) -> bytes: - return self.response.content - - @property - @override - def text(self) -> str: - return self.response.text - - @property - @override - def encoding(self) -> Optional[str]: - return self.response.encoding - - @property - @override - def charset_encoding(self) -> Optional[str]: - return self.response.charset_encoding - - @override - def json(self, **kwargs: Any) -> Any: - return self.response.json(**kwargs) - - @override - def read(self) -> bytes: - return self.response.read() - - @override - def iter_bytes(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - return self.response.iter_bytes(chunk_size) - - @override - def iter_text(self, chunk_size: Optional[int] = None) -> Iterator[str]: - return self.response.iter_text(chunk_size) - - @override - def iter_lines(self) -> Iterator[str]: - return self.response.iter_lines() - - @override - def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - return self.response.iter_raw(chunk_size) - - @override - def stream_to_file( - self, - file: str | os.PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - with open(file, mode="wb") as f: - for data in self.response.iter_bytes(chunk_size): - f.write(data) - - @override - def close(self) -> None: - return self.response.close() - - @override - async def aread(self) -> bytes: - return await self.response.aread() - - @override - async def aiter_bytes(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - return self.response.aiter_bytes(chunk_size) - - @override - async def aiter_text(self, chunk_size: Optional[int] = None) -> AsyncIterator[str]: - return self.response.aiter_text(chunk_size) - - @override - async def aiter_lines(self) -> AsyncIterator[str]: - return self.response.aiter_lines() - - @override - async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - return self.response.aiter_raw(chunk_size) - - @override - async def astream_to_file( - self, - file: str | os.PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - path = anyio.Path(file) - async with await path.open(mode="wb") as f: - async for data in self.response.aiter_bytes(chunk_size): - await f.write(data) - - @override - async def aclose(self) -> None: - return await self.response.aclose() diff --git a/src/openai/_client.py b/src/openai/_client.py index 09f54e1b1..5043d60e2 100644 --- a/src/openai/_client.py +++ b/src/openai/_client.py @@ -58,6 +58,7 @@ class OpenAI(SyncAPIClient): fine_tuning: resources.FineTuning beta: resources.Beta with_raw_response: OpenAIWithRawResponse + with_streaming_response: OpenAIWithStreamedResponse # client options api_key: str @@ -132,6 +133,7 @@ def __init__( self.fine_tuning = resources.FineTuning(self) self.beta = resources.Beta(self) self.with_raw_response = OpenAIWithRawResponse(self) + self.with_streaming_response = OpenAIWithStreamedResponse(self) @property @override @@ -254,6 +256,7 @@ class AsyncOpenAI(AsyncAPIClient): fine_tuning: resources.AsyncFineTuning beta: resources.AsyncBeta with_raw_response: AsyncOpenAIWithRawResponse + with_streaming_response: AsyncOpenAIWithStreamedResponse # client options api_key: str @@ -328,6 +331,7 @@ def __init__( self.fine_tuning = resources.AsyncFineTuning(self) self.beta = resources.AsyncBeta(self) self.with_raw_response = AsyncOpenAIWithRawResponse(self) + self.with_streaming_response = AsyncOpenAIWithStreamedResponse(self) @property @override @@ -466,6 +470,34 @@ def __init__(self, client: AsyncOpenAI) -> None: self.beta = resources.AsyncBetaWithRawResponse(client.beta) +class OpenAIWithStreamedResponse: + def __init__(self, client: OpenAI) -> None: + self.completions = resources.CompletionsWithStreamingResponse(client.completions) + self.chat = resources.ChatWithStreamingResponse(client.chat) + self.embeddings = resources.EmbeddingsWithStreamingResponse(client.embeddings) + self.files = resources.FilesWithStreamingResponse(client.files) + self.images = resources.ImagesWithStreamingResponse(client.images) + self.audio = resources.AudioWithStreamingResponse(client.audio) + self.moderations = resources.ModerationsWithStreamingResponse(client.moderations) + self.models = resources.ModelsWithStreamingResponse(client.models) + self.fine_tuning = resources.FineTuningWithStreamingResponse(client.fine_tuning) + self.beta = resources.BetaWithStreamingResponse(client.beta) + + +class AsyncOpenAIWithStreamedResponse: + def __init__(self, client: AsyncOpenAI) -> None: + self.completions = resources.AsyncCompletionsWithStreamingResponse(client.completions) + self.chat = resources.AsyncChatWithStreamingResponse(client.chat) + self.embeddings = resources.AsyncEmbeddingsWithStreamingResponse(client.embeddings) + self.files = resources.AsyncFilesWithStreamingResponse(client.files) + self.images = resources.AsyncImagesWithStreamingResponse(client.images) + self.audio = resources.AsyncAudioWithStreamingResponse(client.audio) + self.moderations = resources.AsyncModerationsWithStreamingResponse(client.moderations) + self.models = resources.AsyncModelsWithStreamingResponse(client.models) + self.fine_tuning = resources.AsyncFineTuningWithStreamingResponse(client.fine_tuning) + self.beta = resources.AsyncBetaWithStreamingResponse(client.beta) + + Client = OpenAI AsyncClient = AsyncOpenAI diff --git a/src/openai/_constants.py b/src/openai/_constants.py index 7c13feaa2..af9a04b80 100644 --- a/src/openai/_constants.py +++ b/src/openai/_constants.py @@ -3,7 +3,7 @@ import httpx RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response" -STREAMED_RAW_RESPONSE_HEADER = "X-Stainless-Streamed-Raw-Response" +OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 10 minutes DEFAULT_TIMEOUT = httpx.Timeout(timeout=600.0, connect=5.0) diff --git a/src/openai/_legacy_response.py b/src/openai/_legacy_response.py new file mode 100644 index 000000000..5a398efeb --- /dev/null +++ b/src/openai/_legacy_response.py @@ -0,0 +1,385 @@ +from __future__ import annotations + +import os +import inspect +import logging +import datetime +import functools +from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, Iterator, AsyncIterator, cast +from typing_extensions import Awaitable, ParamSpec, get_args, override, deprecated, get_origin + +import anyio +import httpx + +from ._types import NoneType +from ._utils import is_given +from ._models import BaseModel, is_basemodel +from ._constants import RAW_RESPONSE_HEADER +from ._exceptions import APIResponseValidationError + +if TYPE_CHECKING: + from ._models import FinalRequestOptions + from ._base_client import Stream, BaseClient, AsyncStream + + +P = ParamSpec("P") +R = TypeVar("R") + +log: logging.Logger = logging.getLogger(__name__) + + +class LegacyAPIResponse(Generic[R]): + """This is a legacy class as it will be replaced by `APIResponse` + and `AsyncAPIResponse` in the `_response.py` file in the next major + release. + + For the sync client this will mostly be the same with the exception + of `content` & `text` will be methods instead of properties. In the + async client, all methods will be async. + + A migration script will be provided & the migration in general should + be smooth. + """ + + _cast_to: type[R] + _client: BaseClient[Any, Any] + _parsed: R | None + _stream: bool + _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None + _options: FinalRequestOptions + + http_response: httpx.Response + + def __init__( + self, + *, + raw: httpx.Response, + cast_to: type[R], + client: BaseClient[Any, Any], + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + options: FinalRequestOptions, + ) -> None: + self._cast_to = cast_to + self._client = client + self._parsed = None + self._stream = stream + self._stream_cls = stream_cls + self._options = options + self.http_response = raw + + def parse(self) -> R: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + NOTE: For the async client: this will become a coroutine in the next major version. + """ + if self._parsed is not None: + return self._parsed + + parsed = self._parse() + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed = parsed + return parsed + + @property + def headers(self) -> httpx.Headers: + return self.http_response.headers + + @property + def http_request(self) -> httpx.Request: + return self.http_response.request + + @property + def status_code(self) -> int: + return self.http_response.status_code + + @property + def url(self) -> httpx.URL: + return self.http_response.url + + @property + def method(self) -> str: + return self.http_request.method + + @property + def content(self) -> bytes: + """Return the binary response content. + + NOTE: this will be removed in favour of `.read()` in the + next major version. + """ + return self.http_response.content + + @property + def text(self) -> str: + """Return the decoded response content. + + NOTE: this will be turned into a method in the next major version. + """ + return self.http_response.text + + @property + def http_version(self) -> str: + return self.http_response.http_version + + @property + def is_closed(self) -> bool: + return self.http_response.is_closed + + @property + def elapsed(self) -> datetime.timedelta: + """The time taken for the complete request/response cycle to complete.""" + return self.http_response.elapsed + + def _parse(self) -> R: + if self._stream: + if self._stream_cls: + return cast( + R, + self._stream_cls( + cast_to=_extract_stream_chunk_type(self._stream_cls), + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls) + if stream_cls is None: + raise MissingStreamClassError() + + return cast( + R, + stream_cls( + cast_to=self._cast_to, + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + cast_to = self._cast_to + if cast_to is NoneType: + return cast(R, None) + + response = self.http_response + if cast_to == str: + return cast(R, response.text) + + origin = get_origin(cast_to) or cast_to + + if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent): + return cast(R, cast_to(response)) # type: ignore + + if origin == LegacyAPIResponse: + raise RuntimeError("Unexpected state - cast_to is `APIResponse`") + + if inspect.isclass(origin) and issubclass(origin, httpx.Response): + # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response + # and pass that class to our request functions. We cannot change the variance to be either + # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct + # the response class ourselves but that is something that should be supported directly in httpx + # as it would be easy to incorrectly construct the Response object due to the multitude of arguments. + if cast_to != httpx.Response: + raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") + return cast(R, response) + + # The check here is necessary as we are subverting the the type system + # with casts as the relationship between TypeVars and Types are very strict + # which means we must return *exactly* what was input or transform it in a + # way that retains the TypeVar state. As we cannot do that in this function + # then we have to resort to using `cast`. At the time of writing, we know this + # to be safe as we have handled all the types that could be bound to the + # `ResponseT` TypeVar, however if that TypeVar is ever updated in the future, then + # this function would become unsafe but a type checker would not report an error. + if ( + cast_to is not object + and not origin is list + and not origin is dict + and not origin is Union + and not issubclass(origin, BaseModel) + ): + raise RuntimeError( + f"Invalid state, expected {cast_to} to be a subclass type of {BaseModel}, {dict}, {list} or {Union}." + ) + + # split is required to handle cases where additional information is included + # in the response, e.g. application/json; charset=utf-8 + content_type, *_ = response.headers.get("content-type").split(";") + if content_type != "application/json": + if is_basemodel(cast_to): + try: + data = response.json() + except Exception as exc: + log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc) + else: + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + if self._client._strict_response_validation: + raise APIResponseValidationError( + response=response, + message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.", + body=response.text, + ) + + # If the API responds with content that isn't JSON then we just return + # the (decoded) text without performing any parsing so that you can still + # handle the response however you need to. + return response.text # type: ignore + + data = response.json() + + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + @override + def __repr__(self) -> str: + return f"" + + +class MissingStreamClassError(TypeError): + def __init__(self) -> None: + super().__init__( + "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `openai._streaming` for reference", + ) + + +def _extract_stream_chunk_type(stream_cls: type) -> type: + args = get_args(stream_cls) + if not args: + raise TypeError( + f"Expected stream_cls to have been given a generic type argument, e.g. Stream[Foo] but received {stream_cls}", + ) + return cast(type, args[0]) + + +def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, LegacyAPIResponse[R]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "true" + + kwargs["extra_headers"] = extra_headers + + return cast(LegacyAPIResponse[R], func(*args, **kwargs)) + + return wrapped + + +def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[LegacyAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + async def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "true" + + kwargs["extra_headers"] = extra_headers + + return cast(LegacyAPIResponse[R], await func(*args, **kwargs)) + + return wrapped + + +class HttpxBinaryResponseContent: + response: httpx.Response + + def __init__(self, response: httpx.Response) -> None: + self.response = response + + @property + def content(self) -> bytes: + return self.response.content + + @property + def text(self) -> str: + return self.response.text + + @property + def encoding(self) -> str | None: + return self.response.encoding + + @property + def charset_encoding(self) -> str | None: + return self.response.charset_encoding + + def json(self, **kwargs: Any) -> Any: + return self.response.json(**kwargs) + + def read(self) -> bytes: + return self.response.read() + + def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: + return self.response.iter_bytes(chunk_size) + + def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: + return self.response.iter_text(chunk_size) + + def iter_lines(self) -> Iterator[str]: + return self.response.iter_lines() + + def iter_raw(self, chunk_size: int | None = None) -> Iterator[bytes]: + return self.response.iter_raw(chunk_size) + + @deprecated( + "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead" + ) + def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + with open(file, mode="wb") as f: + for data in self.response.iter_bytes(chunk_size): + f.write(data) + + def close(self) -> None: + return self.response.close() + + async def aread(self) -> bytes: + return await self.response.aread() + + async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + return self.response.aiter_bytes(chunk_size) + + async def aiter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: + return self.response.aiter_text(chunk_size) + + async def aiter_lines(self) -> AsyncIterator[str]: + return self.response.aiter_lines() + + async def aiter_raw(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + return self.response.aiter_raw(chunk_size) + + @deprecated( + "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead" + ) + async def astream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.response.aiter_bytes(chunk_size): + await f.write(data) + + async def aclose(self) -> None: + return await self.response.aclose() diff --git a/src/openai/_response.py b/src/openai/_response.py index bf72d18fd..15a323afa 100644 --- a/src/openai/_response.py +++ b/src/openai/_response.py @@ -1,19 +1,32 @@ from __future__ import annotations +import os import inspect import logging import datetime import functools -from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Union, + Generic, + TypeVar, + Callable, + Iterator, + AsyncIterator, + cast, +) from typing_extensions import Awaitable, ParamSpec, override, get_origin +import anyio import httpx -from ._types import NoneType, BinaryResponseContent +from ._types import NoneType from ._utils import is_given, extract_type_var_from_base from ._models import BaseModel, is_basemodel -from ._constants import RAW_RESPONSE_HEADER -from ._exceptions import APIResponseValidationError +from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER +from ._exceptions import OpenAIError, APIResponseValidationError if TYPE_CHECKING: from ._models import FinalRequestOptions @@ -22,15 +35,17 @@ P = ParamSpec("P") R = TypeVar("R") +_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]") +_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]") log: logging.Logger = logging.getLogger(__name__) -class APIResponse(Generic[R]): +class BaseAPIResponse(Generic[R]): _cast_to: type[R] _client: BaseClient[Any, Any] _parsed: R | None - _stream: bool + _is_sse_stream: bool _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None _options: FinalRequestOptions @@ -49,28 +64,18 @@ def __init__( self._cast_to = cast_to self._client = client self._parsed = None - self._stream = stream + self._is_sse_stream = stream self._stream_cls = stream_cls self._options = options self.http_response = raw - def parse(self) -> R: - if self._parsed is not None: - return self._parsed - - parsed = self._parse() - if is_given(self._options.post_parser): - parsed = self._options.post_parser(parsed) - - self._parsed = parsed - return parsed - @property def headers(self) -> httpx.Headers: return self.http_response.headers @property def http_request(self) -> httpx.Request: + """Returns the httpx Request instance associated with the current response.""" return self.http_response.request @property @@ -79,20 +84,13 @@ def status_code(self) -> int: @property def url(self) -> httpx.URL: + """Returns the URL for which the request was made.""" return self.http_response.url @property def method(self) -> str: return self.http_request.method - @property - def content(self) -> bytes: - return self.http_response.content - - @property - def text(self) -> str: - return self.http_response.text - @property def http_version(self) -> str: return self.http_response.http_version @@ -102,13 +100,29 @@ def elapsed(self) -> datetime.timedelta: """The time taken for the complete request/response cycle to complete.""" return self.http_response.elapsed + @property + def is_closed(self) -> bool: + """Whether or not the response body has been closed. + + If this is False then there is response data that has not been read yet. + You must either fully consume the response body or call `.close()` + before discarding the response to prevent resource leaks. + """ + return self.http_response.is_closed + + @override + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>" + ) + def _parse(self) -> R: - if self._stream: + if self._is_sse_stream: if self._stream_cls: return cast( R, self._stream_cls( - cast_to=_extract_stream_chunk_type(self._stream_cls), + cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), ), @@ -135,9 +149,13 @@ def _parse(self) -> R: if cast_to == str: return cast(R, response.text) + if cast_to == bytes: + return cast(R, response.content) + origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BinaryResponseContent): + # handle the legacy binary response case + if inspect.isclass(cast_to) and cast_to.__name__ == "HttpxBinaryResponseContent": return cast(R, cast_to(response)) # type: ignore if origin == APIResponse: @@ -208,9 +226,227 @@ def _parse(self) -> R: response=response, ) - @override - def __repr__(self) -> str: - return f"" + +class APIResponse(BaseAPIResponse[R]): + def parse(self) -> R: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + """ + if self._parsed is not None: + return self._parsed + + if not self._is_sse_stream: + self.read() + + parsed = self._parse() + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed = parsed + return parsed + + def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return self.http_response.read() + except httpx.StreamConsumed as exc: + # The default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message. + raise StreamAlreadyConsumed() from exc + + def text(self) -> str: + """Read and decode the response content into a string.""" + self.read() + return self.http_response.text + + def json(self) -> object: + """Read and decode the JSON response content.""" + self.read() + return self.http_response.json() + + def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.http_response.close() + + def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + for chunk in self.http_response.iter_bytes(chunk_size): + yield chunk + + def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + for chunk in self.http_response.iter_text(chunk_size): + yield chunk + + def iter_lines(self) -> Iterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + for chunk in self.http_response.iter_lines(): + yield chunk + + +class AsyncAPIResponse(BaseAPIResponse[R]): + async def parse(self) -> R: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + """ + if self._parsed is not None: + return self._parsed + + if not self._is_sse_stream: + await self.read() + + parsed = self._parse() + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed = parsed + return parsed + + async def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return await self.http_response.aread() + except httpx.StreamConsumed as exc: + # the default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message + raise StreamAlreadyConsumed() from exc + + async def text(self) -> str: + """Read and decode the response content into a string.""" + await self.read() + return self.http_response.text + + async def json(self) -> object: + """Read and decode the JSON response content.""" + await self.read() + return self.http_response.json() + + async def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.http_response.aclose() + + async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + async for chunk in self.http_response.aiter_bytes(chunk_size): + yield chunk + + async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + async for chunk in self.http_response.aiter_text(chunk_size): + yield chunk + + async def iter_lines(self) -> AsyncIterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + async for chunk in self.http_response.aiter_lines(): + yield chunk + + +class BinaryAPIResponse(APIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(): + f.write(data) + + +class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + async def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(): + await f.write(data) + + +class StreamedBinaryAPIResponse(APIResponse[bytes]): + def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(chunk_size): + f.write(data) + + +class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]): + async def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(chunk_size): + await f.write(data) class MissingStreamClassError(TypeError): @@ -220,14 +456,176 @@ def __init__(self) -> None: ) -def _extract_stream_chunk_type(stream_cls: type) -> type: - from ._base_client import Stream, AsyncStream +class StreamAlreadyConsumed(OpenAIError): + """ + Attempted to read or stream content, but the content has already + been streamed. - return extract_type_var_from_base( - stream_cls, - index=0, - generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), - ) + This can happen if you use a method like `.iter_lines()` and then attempt + to read th entire response body afterwards, e.g. + + ```py + response = await client.post(...) + async for line in response.iter_lines(): + ... # do something with `line` + + content = await response.read() + # ^ error + ``` + + If you want this behaviour you'll need to either manually accumulate the response + content or call `await response.read()` before iterating over the stream. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream some content, but the content has " + "already been streamed. " + "This could be due to attempting to stream the response " + "content more than once." + "\n\n" + "You can fix this by manually accumulating the response content while streaming " + "or by calling `.read()` before starting to stream." + ) + super().__init__(message) + + +class ResponseContextManager(Generic[_APIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, request_func: Callable[[], _APIResponseT]) -> None: + self._request_func = request_func + self.__response: _APIResponseT | None = None + + def __enter__(self) -> _APIResponseT: + self.__response = self._request_func() + return self.__response + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + self.__response.close() + + +class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None: + self._api_request = api_request + self.__response: _AsyncAPIResponseT | None = None + + async def __aenter__(self) -> _AsyncAPIResponseT: + self.__response = await self._api_request + return self.__response + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + await self.__response.close() + + +def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request)) + + return wrapped + + +def async_to_streamed_response_wrapper( + func: Callable[P, Awaitable[R]], +) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request)) + + return wrapped + + +def to_custom_streamed_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, ResponseContextManager[_APIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request)) + + return wrapped + + +def async_to_custom_streamed_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request)) + + return wrapped def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]: @@ -238,7 +636,7 @@ def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]] @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "true" + extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers @@ -247,18 +645,102 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: return wrapped -def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[APIResponse[R]]]: +def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]: """Higher order function that takes one of our bound API methods and wraps it to support returning the raw `APIResponse` object directly. """ @functools.wraps(func) - async def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: + async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "true" + extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers - return cast(APIResponse[R], await func(*args, **kwargs)) + return cast(AsyncAPIResponse[R], await func(*args, **kwargs)) return wrapped + + +def to_custom_raw_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, _APIResponseT]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(_APIResponseT, func(*args, **kwargs)) + + return wrapped + + +def async_to_custom_raw_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, Awaitable[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs)) + + return wrapped + + +def extract_stream_chunk_type(stream_cls: type) -> type: + """Given a type like `Stream[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyStream(Stream[bytes]): + ... + + extract_stream_chunk_type(MyStream) -> bytes + ``` + """ + from ._base_client import Stream, AsyncStream + + return extract_type_var_from_base( + stream_cls, + index=0, + generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), + ) + + +def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type: + """Given a type like `APIResponse[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(APIResponse[bytes]): + ... + + extract_response_type(MyResponse) -> bytes + ``` + """ + return extract_type_var_from_base( + typ, + generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)), + index=0, + ) diff --git a/src/openai/_types.py b/src/openai/_types.py index e6b83b2a3..b5bf8f8af 100644 --- a/src/openai/_types.py +++ b/src/openai/_types.py @@ -1,7 +1,6 @@ from __future__ import annotations from os import PathLike -from abc import ABC, abstractmethod from typing import ( IO, TYPE_CHECKING, @@ -14,10 +13,8 @@ Mapping, TypeVar, Callable, - Iterator, Optional, Sequence, - AsyncIterator, ) from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable @@ -27,6 +24,8 @@ if TYPE_CHECKING: from ._models import BaseModel + from ._response import APIResponse, AsyncAPIResponse + from ._legacy_response import HttpxBinaryResponseContent Transport = BaseTransport AsyncTransport = AsyncBaseTransport @@ -37,162 +36,6 @@ _T = TypeVar("_T") -class BinaryResponseContent(ABC): - @abstractmethod - def __init__( - self, - response: Any, - ) -> None: - ... - - @property - @abstractmethod - def content(self) -> bytes: - pass - - @property - @abstractmethod - def text(self) -> str: - pass - - @property - @abstractmethod - def encoding(self) -> Optional[str]: - """ - Return an encoding to use for decoding the byte content into text. - The priority for determining this is given by... - - * `.encoding = <>` has been set explicitly. - * The encoding as specified by the charset parameter in the Content-Type header. - * The encoding as determined by `default_encoding`, which may either be - a string like "utf-8" indicating the encoding to use, or may be a callable - which enables charset autodetection. - """ - pass - - @property - @abstractmethod - def charset_encoding(self) -> Optional[str]: - """ - Return the encoding, as specified by the Content-Type header. - """ - pass - - @abstractmethod - def json(self, **kwargs: Any) -> Any: - pass - - @abstractmethod - def read(self) -> bytes: - """ - Read and return the response content. - """ - pass - - @abstractmethod - def iter_bytes(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - """ - A byte-iterator over the decoded response content. - This allows us to handle gzip, deflate, and brotli encoded responses. - """ - pass - - @abstractmethod - def iter_text(self, chunk_size: Optional[int] = None) -> Iterator[str]: - """ - A str-iterator over the decoded response content - that handles both gzip, deflate, etc but also detects the content's - string encoding. - """ - pass - - @abstractmethod - def iter_lines(self) -> Iterator[str]: - pass - - @abstractmethod - def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - """ - A byte-iterator over the raw response content. - """ - pass - - @abstractmethod - def stream_to_file( - self, - file: str | PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - """ - Stream the output to the given file. - """ - pass - - @abstractmethod - def close(self) -> None: - """ - Close the response and release the connection. - Automatically called if the response body is read to completion. - """ - pass - - @abstractmethod - async def aread(self) -> bytes: - """ - Read and return the response content. - """ - pass - - @abstractmethod - async def aiter_bytes(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - """ - A byte-iterator over the decoded response content. - This allows us to handle gzip, deflate, and brotli encoded responses. - """ - pass - - @abstractmethod - async def aiter_text(self, chunk_size: Optional[int] = None) -> AsyncIterator[str]: - """ - A str-iterator over the decoded response content - that handles both gzip, deflate, etc but also detects the content's - string encoding. - """ - pass - - @abstractmethod - async def aiter_lines(self) -> AsyncIterator[str]: - pass - - @abstractmethod - async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - """ - A byte-iterator over the raw response content. - """ - pass - - @abstractmethod - async def astream_to_file( - self, - file: str | PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - """ - Stream the output to the given file. - """ - pass - - @abstractmethod - async def aclose(self) -> None: - """ - Close the response and release the connection. - Automatically called if the response body is read to completion. - """ - pass - - # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] @@ -343,7 +186,9 @@ def get(self, __key: str) -> str | None: Dict[str, Any], Response, ModelBuilderProtocol, - BinaryResponseContent, + "APIResponse[Any]", + "AsyncAPIResponse[Any]", + "HttpxBinaryResponseContent", ], ) @@ -359,6 +204,7 @@ def get(self, __key: str) -> str | None: @runtime_checkable class InheritsGeneric(Protocol): """Represents a type that has inherited from `Generic` + The `__orig_bases__` property can be used to determine the resolved type variable for a given base class. """ diff --git a/src/openai/resources/__init__.py b/src/openai/resources/__init__.py index 8219be12e..1fb4aa62e 100644 --- a/src/openai/resources/__init__.py +++ b/src/openai/resources/__init__.py @@ -1,55 +1,145 @@ # File generated from our OpenAPI spec by Stainless. -from .beta import Beta, AsyncBeta, BetaWithRawResponse, AsyncBetaWithRawResponse -from .chat import Chat, AsyncChat, ChatWithRawResponse, AsyncChatWithRawResponse -from .audio import Audio, AsyncAudio, AudioWithRawResponse, AsyncAudioWithRawResponse -from .files import Files, AsyncFiles, FilesWithRawResponse, AsyncFilesWithRawResponse -from .images import Images, AsyncImages, ImagesWithRawResponse, AsyncImagesWithRawResponse -from .models import Models, AsyncModels, ModelsWithRawResponse, AsyncModelsWithRawResponse -from .embeddings import Embeddings, AsyncEmbeddings, EmbeddingsWithRawResponse, AsyncEmbeddingsWithRawResponse -from .completions import Completions, AsyncCompletions, CompletionsWithRawResponse, AsyncCompletionsWithRawResponse -from .fine_tuning import FineTuning, AsyncFineTuning, FineTuningWithRawResponse, AsyncFineTuningWithRawResponse -from .moderations import Moderations, AsyncModerations, ModerationsWithRawResponse, AsyncModerationsWithRawResponse +from .beta import ( + Beta, + AsyncBeta, + BetaWithRawResponse, + AsyncBetaWithRawResponse, + BetaWithStreamingResponse, + AsyncBetaWithStreamingResponse, +) +from .chat import ( + Chat, + AsyncChat, + ChatWithRawResponse, + AsyncChatWithRawResponse, + ChatWithStreamingResponse, + AsyncChatWithStreamingResponse, +) +from .audio import ( + Audio, + AsyncAudio, + AudioWithRawResponse, + AsyncAudioWithRawResponse, + AudioWithStreamingResponse, + AsyncAudioWithStreamingResponse, +) +from .files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) +from .images import ( + Images, + AsyncImages, + ImagesWithRawResponse, + AsyncImagesWithRawResponse, + ImagesWithStreamingResponse, + AsyncImagesWithStreamingResponse, +) +from .models import ( + Models, + AsyncModels, + ModelsWithRawResponse, + AsyncModelsWithRawResponse, + ModelsWithStreamingResponse, + AsyncModelsWithStreamingResponse, +) +from .embeddings import ( + Embeddings, + AsyncEmbeddings, + EmbeddingsWithRawResponse, + AsyncEmbeddingsWithRawResponse, + EmbeddingsWithStreamingResponse, + AsyncEmbeddingsWithStreamingResponse, +) +from .completions import ( + Completions, + AsyncCompletions, + CompletionsWithRawResponse, + AsyncCompletionsWithRawResponse, + CompletionsWithStreamingResponse, + AsyncCompletionsWithStreamingResponse, +) +from .fine_tuning import ( + FineTuning, + AsyncFineTuning, + FineTuningWithRawResponse, + AsyncFineTuningWithRawResponse, + FineTuningWithStreamingResponse, + AsyncFineTuningWithStreamingResponse, +) +from .moderations import ( + Moderations, + AsyncModerations, + ModerationsWithRawResponse, + AsyncModerationsWithRawResponse, + ModerationsWithStreamingResponse, + AsyncModerationsWithStreamingResponse, +) __all__ = [ "Completions", "AsyncCompletions", "CompletionsWithRawResponse", "AsyncCompletionsWithRawResponse", + "CompletionsWithStreamingResponse", + "AsyncCompletionsWithStreamingResponse", "Chat", "AsyncChat", "ChatWithRawResponse", "AsyncChatWithRawResponse", + "ChatWithStreamingResponse", + "AsyncChatWithStreamingResponse", "Embeddings", "AsyncEmbeddings", "EmbeddingsWithRawResponse", "AsyncEmbeddingsWithRawResponse", + "EmbeddingsWithStreamingResponse", + "AsyncEmbeddingsWithStreamingResponse", "Files", "AsyncFiles", "FilesWithRawResponse", "AsyncFilesWithRawResponse", + "FilesWithStreamingResponse", + "AsyncFilesWithStreamingResponse", "Images", "AsyncImages", "ImagesWithRawResponse", "AsyncImagesWithRawResponse", + "ImagesWithStreamingResponse", + "AsyncImagesWithStreamingResponse", "Audio", "AsyncAudio", "AudioWithRawResponse", "AsyncAudioWithRawResponse", + "AudioWithStreamingResponse", + "AsyncAudioWithStreamingResponse", "Moderations", "AsyncModerations", "ModerationsWithRawResponse", "AsyncModerationsWithRawResponse", + "ModerationsWithStreamingResponse", + "AsyncModerationsWithStreamingResponse", "Models", "AsyncModels", "ModelsWithRawResponse", "AsyncModelsWithRawResponse", + "ModelsWithStreamingResponse", + "AsyncModelsWithStreamingResponse", "FineTuning", "AsyncFineTuning", "FineTuningWithRawResponse", "AsyncFineTuningWithRawResponse", + "FineTuningWithStreamingResponse", + "AsyncFineTuningWithStreamingResponse", "Beta", "AsyncBeta", "BetaWithRawResponse", "AsyncBetaWithRawResponse", + "BetaWithStreamingResponse", + "AsyncBetaWithStreamingResponse", ] diff --git a/src/openai/resources/audio/__init__.py b/src/openai/resources/audio/__init__.py index b6ff4322d..63d06494b 100644 --- a/src/openai/resources/audio/__init__.py +++ b/src/openai/resources/audio/__init__.py @@ -1,13 +1,36 @@ # File generated from our OpenAPI spec by Stainless. -from .audio import Audio, AsyncAudio, AudioWithRawResponse, AsyncAudioWithRawResponse -from .speech import Speech, AsyncSpeech, SpeechWithRawResponse, AsyncSpeechWithRawResponse -from .translations import Translations, AsyncTranslations, TranslationsWithRawResponse, AsyncTranslationsWithRawResponse +from .audio import ( + Audio, + AsyncAudio, + AudioWithRawResponse, + AsyncAudioWithRawResponse, + AudioWithStreamingResponse, + AsyncAudioWithStreamingResponse, +) +from .speech import ( + Speech, + AsyncSpeech, + SpeechWithRawResponse, + AsyncSpeechWithRawResponse, + SpeechWithStreamingResponse, + AsyncSpeechWithStreamingResponse, +) +from .translations import ( + Translations, + AsyncTranslations, + TranslationsWithRawResponse, + AsyncTranslationsWithRawResponse, + TranslationsWithStreamingResponse, + AsyncTranslationsWithStreamingResponse, +) from .transcriptions import ( Transcriptions, AsyncTranscriptions, TranscriptionsWithRawResponse, AsyncTranscriptionsWithRawResponse, + TranscriptionsWithStreamingResponse, + AsyncTranscriptionsWithStreamingResponse, ) __all__ = [ @@ -15,16 +38,24 @@ "AsyncTranscriptions", "TranscriptionsWithRawResponse", "AsyncTranscriptionsWithRawResponse", + "TranscriptionsWithStreamingResponse", + "AsyncTranscriptionsWithStreamingResponse", "Translations", "AsyncTranslations", "TranslationsWithRawResponse", "AsyncTranslationsWithRawResponse", + "TranslationsWithStreamingResponse", + "AsyncTranslationsWithStreamingResponse", "Speech", "AsyncSpeech", "SpeechWithRawResponse", "AsyncSpeechWithRawResponse", + "SpeechWithStreamingResponse", + "AsyncSpeechWithStreamingResponse", "Audio", "AsyncAudio", "AudioWithRawResponse", "AsyncAudioWithRawResponse", + "AudioWithStreamingResponse", + "AsyncAudioWithStreamingResponse", ] diff --git a/src/openai/resources/audio/audio.py b/src/openai/resources/audio/audio.py index 4e3ca0ed4..b14e64cff 100644 --- a/src/openai/resources/audio/audio.py +++ b/src/openai/resources/audio/audio.py @@ -2,15 +2,31 @@ from __future__ import annotations -from .speech import Speech, AsyncSpeech, SpeechWithRawResponse, AsyncSpeechWithRawResponse +from .speech import ( + Speech, + AsyncSpeech, + SpeechWithRawResponse, + AsyncSpeechWithRawResponse, + SpeechWithStreamingResponse, + AsyncSpeechWithStreamingResponse, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from .translations import Translations, AsyncTranslations, TranslationsWithRawResponse, AsyncTranslationsWithRawResponse +from .translations import ( + Translations, + AsyncTranslations, + TranslationsWithRawResponse, + AsyncTranslationsWithRawResponse, + TranslationsWithStreamingResponse, + AsyncTranslationsWithStreamingResponse, +) from .transcriptions import ( Transcriptions, AsyncTranscriptions, TranscriptionsWithRawResponse, AsyncTranscriptionsWithRawResponse, + TranscriptionsWithStreamingResponse, + AsyncTranscriptionsWithStreamingResponse, ) __all__ = ["Audio", "AsyncAudio"] @@ -33,6 +49,10 @@ def speech(self) -> Speech: def with_raw_response(self) -> AudioWithRawResponse: return AudioWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AudioWithStreamingResponse: + return AudioWithStreamingResponse(self) + class AsyncAudio(AsyncAPIResource): @cached_property @@ -51,6 +71,10 @@ def speech(self) -> AsyncSpeech: def with_raw_response(self) -> AsyncAudioWithRawResponse: return AsyncAudioWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncAudioWithStreamingResponse: + return AsyncAudioWithStreamingResponse(self) + class AudioWithRawResponse: def __init__(self, audio: Audio) -> None: @@ -64,3 +88,17 @@ def __init__(self, audio: AsyncAudio) -> None: self.transcriptions = AsyncTranscriptionsWithRawResponse(audio.transcriptions) self.translations = AsyncTranslationsWithRawResponse(audio.translations) self.speech = AsyncSpeechWithRawResponse(audio.speech) + + +class AudioWithStreamingResponse: + def __init__(self, audio: Audio) -> None: + self.transcriptions = TranscriptionsWithStreamingResponse(audio.transcriptions) + self.translations = TranslationsWithStreamingResponse(audio.translations) + self.speech = SpeechWithStreamingResponse(audio.speech) + + +class AsyncAudioWithStreamingResponse: + def __init__(self, audio: AsyncAudio) -> None: + self.transcriptions = AsyncTranscriptionsWithStreamingResponse(audio.transcriptions) + self.translations = AsyncTranslationsWithStreamingResponse(audio.translations) + self.speech = AsyncSpeechWithStreamingResponse(audio.speech) diff --git a/src/openai/resources/audio/speech.py b/src/openai/resources/audio/speech.py index b7cd3733a..9c051624d 100644 --- a/src/openai/resources/audio/speech.py +++ b/src/openai/resources/audio/speech.py @@ -7,14 +7,19 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import ( + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_custom_streamed_response_wrapper, + async_to_custom_streamed_response_wrapper, +) from ...types.audio import speech_create_params from ..._base_client import ( - HttpxBinaryResponseContent, make_request_options, ) @@ -26,6 +31,10 @@ class Speech(SyncAPIResource): def with_raw_response(self) -> SpeechWithRawResponse: return SpeechWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> SpeechWithStreamingResponse: + return SpeechWithStreamingResponse(self) + def create( self, *, @@ -40,7 +49,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> HttpxBinaryResponseContent: + ) -> _legacy_response.HttpxBinaryResponseContent: """ Generates audio from the input text. @@ -84,7 +93,7 @@ def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=HttpxBinaryResponseContent, + cast_to=_legacy_response.HttpxBinaryResponseContent, ) @@ -93,6 +102,10 @@ class AsyncSpeech(AsyncAPIResource): def with_raw_response(self) -> AsyncSpeechWithRawResponse: return AsyncSpeechWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncSpeechWithStreamingResponse: + return AsyncSpeechWithStreamingResponse(self) + async def create( self, *, @@ -107,7 +120,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> HttpxBinaryResponseContent: + ) -> _legacy_response.HttpxBinaryResponseContent: """ Generates audio from the input text. @@ -151,19 +164,35 @@ async def create( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=HttpxBinaryResponseContent, + cast_to=_legacy_response.HttpxBinaryResponseContent, ) class SpeechWithRawResponse: def __init__(self, speech: Speech) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( speech.create, ) class AsyncSpeechWithRawResponse: def __init__(self, speech: AsyncSpeech) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + speech.create, + ) + + +class SpeechWithStreamingResponse: + def __init__(self, speech: Speech) -> None: + self.create = to_custom_streamed_response_wrapper( + speech.create, + StreamedBinaryAPIResponse, + ) + + +class AsyncSpeechWithStreamingResponse: + def __init__(self, speech: AsyncSpeech) -> None: + self.create = async_to_custom_streamed_response_wrapper( speech.create, + AsyncStreamedBinaryAPIResponse, ) diff --git a/src/openai/resources/audio/transcriptions.py b/src/openai/resources/audio/transcriptions.py index 7d7441a9f..053ac3009 100644 --- a/src/openai/resources/audio/transcriptions.py +++ b/src/openai/resources/audio/transcriptions.py @@ -7,11 +7,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes from ..._utils import extract_files, maybe_transform, deepcopy_minimal from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...types.audio import Transcription, transcription_create_params from ..._base_client import ( make_request_options, @@ -25,6 +26,10 @@ class Transcriptions(SyncAPIResource): def with_raw_response(self) -> TranscriptionsWithRawResponse: return TranscriptionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> TranscriptionsWithStreamingResponse: + return TranscriptionsWithStreamingResponse(self) + def create( self, *, @@ -110,6 +115,10 @@ class AsyncTranscriptions(AsyncAPIResource): def with_raw_response(self) -> AsyncTranscriptionsWithRawResponse: return AsyncTranscriptionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncTranscriptionsWithStreamingResponse: + return AsyncTranscriptionsWithStreamingResponse(self) + async def create( self, *, @@ -192,13 +201,27 @@ async def create( class TranscriptionsWithRawResponse: def __init__(self, transcriptions: Transcriptions) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( transcriptions.create, ) class AsyncTranscriptionsWithRawResponse: def __init__(self, transcriptions: AsyncTranscriptions) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + transcriptions.create, + ) + + +class TranscriptionsWithStreamingResponse: + def __init__(self, transcriptions: Transcriptions) -> None: + self.create = to_streamed_response_wrapper( + transcriptions.create, + ) + + +class AsyncTranscriptionsWithStreamingResponse: + def __init__(self, transcriptions: AsyncTranscriptions) -> None: + self.create = async_to_streamed_response_wrapper( transcriptions.create, ) diff --git a/src/openai/resources/audio/translations.py b/src/openai/resources/audio/translations.py index 7f5f65b6c..db41b194b 100644 --- a/src/openai/resources/audio/translations.py +++ b/src/openai/resources/audio/translations.py @@ -7,11 +7,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes from ..._utils import extract_files, maybe_transform, deepcopy_minimal from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...types.audio import Translation, translation_create_params from ..._base_client import ( make_request_options, @@ -25,6 +26,10 @@ class Translations(SyncAPIResource): def with_raw_response(self) -> TranslationsWithRawResponse: return TranslationsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> TranslationsWithStreamingResponse: + return TranslationsWithStreamingResponse(self) + def create( self, *, @@ -103,6 +108,10 @@ class AsyncTranslations(AsyncAPIResource): def with_raw_response(self) -> AsyncTranslationsWithRawResponse: return AsyncTranslationsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncTranslationsWithStreamingResponse: + return AsyncTranslationsWithStreamingResponse(self) + async def create( self, *, @@ -178,13 +187,27 @@ async def create( class TranslationsWithRawResponse: def __init__(self, translations: Translations) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( translations.create, ) class AsyncTranslationsWithRawResponse: def __init__(self, translations: AsyncTranslations) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + translations.create, + ) + + +class TranslationsWithStreamingResponse: + def __init__(self, translations: Translations) -> None: + self.create = to_streamed_response_wrapper( + translations.create, + ) + + +class AsyncTranslationsWithStreamingResponse: + def __init__(self, translations: AsyncTranslations) -> None: + self.create = async_to_streamed_response_wrapper( translations.create, ) diff --git a/src/openai/resources/beta/__init__.py b/src/openai/resources/beta/__init__.py index 561f8bef6..973c6ba54 100644 --- a/src/openai/resources/beta/__init__.py +++ b/src/openai/resources/beta/__init__.py @@ -1,20 +1,47 @@ # File generated from our OpenAPI spec by Stainless. -from .beta import Beta, AsyncBeta, BetaWithRawResponse, AsyncBetaWithRawResponse -from .threads import Threads, AsyncThreads, ThreadsWithRawResponse, AsyncThreadsWithRawResponse -from .assistants import Assistants, AsyncAssistants, AssistantsWithRawResponse, AsyncAssistantsWithRawResponse +from .beta import ( + Beta, + AsyncBeta, + BetaWithRawResponse, + AsyncBetaWithRawResponse, + BetaWithStreamingResponse, + AsyncBetaWithStreamingResponse, +) +from .threads import ( + Threads, + AsyncThreads, + ThreadsWithRawResponse, + AsyncThreadsWithRawResponse, + ThreadsWithStreamingResponse, + AsyncThreadsWithStreamingResponse, +) +from .assistants import ( + Assistants, + AsyncAssistants, + AssistantsWithRawResponse, + AsyncAssistantsWithRawResponse, + AssistantsWithStreamingResponse, + AsyncAssistantsWithStreamingResponse, +) __all__ = [ "Assistants", "AsyncAssistants", "AssistantsWithRawResponse", "AsyncAssistantsWithRawResponse", + "AssistantsWithStreamingResponse", + "AsyncAssistantsWithStreamingResponse", "Threads", "AsyncThreads", "ThreadsWithRawResponse", "AsyncThreadsWithRawResponse", + "ThreadsWithStreamingResponse", + "AsyncThreadsWithStreamingResponse", "Beta", "AsyncBeta", "BetaWithRawResponse", "AsyncBetaWithRawResponse", + "BetaWithStreamingResponse", + "AsyncBetaWithStreamingResponse", ] diff --git a/src/openai/resources/beta/assistants/__init__.py b/src/openai/resources/beta/assistants/__init__.py index 205b2cf0f..ad04a7157 100644 --- a/src/openai/resources/beta/assistants/__init__.py +++ b/src/openai/resources/beta/assistants/__init__.py @@ -1,15 +1,33 @@ # File generated from our OpenAPI spec by Stainless. -from .files import Files, AsyncFiles, FilesWithRawResponse, AsyncFilesWithRawResponse -from .assistants import Assistants, AsyncAssistants, AssistantsWithRawResponse, AsyncAssistantsWithRawResponse +from .files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) +from .assistants import ( + Assistants, + AsyncAssistants, + AssistantsWithRawResponse, + AsyncAssistantsWithRawResponse, + AssistantsWithStreamingResponse, + AsyncAssistantsWithStreamingResponse, +) __all__ = [ "Files", "AsyncFiles", "FilesWithRawResponse", "AsyncFilesWithRawResponse", + "FilesWithStreamingResponse", + "AsyncFilesWithStreamingResponse", "Assistants", "AsyncAssistants", "AssistantsWithRawResponse", "AsyncAssistantsWithRawResponse", + "AssistantsWithStreamingResponse", + "AsyncAssistantsWithStreamingResponse", ] diff --git a/src/openai/resources/beta/assistants/assistants.py b/src/openai/resources/beta/assistants/assistants.py index 0ae054795..176bf0551 100644 --- a/src/openai/resources/beta/assistants/assistants.py +++ b/src/openai/resources/beta/assistants/assistants.py @@ -7,12 +7,20 @@ import httpx -from .files import Files, AsyncFiles, FilesWithRawResponse, AsyncFilesWithRawResponse +from .... import _legacy_response +from .files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ....pagination import SyncCursorPage, AsyncCursorPage from ....types.beta import ( Assistant, @@ -38,6 +46,10 @@ def files(self) -> Files: def with_raw_response(self) -> AssistantsWithRawResponse: return AssistantsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AssistantsWithStreamingResponse: + return AssistantsWithStreamingResponse(self) + def create( self, *, @@ -331,6 +343,10 @@ def files(self) -> AsyncFiles: def with_raw_response(self) -> AsyncAssistantsWithRawResponse: return AsyncAssistantsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncAssistantsWithStreamingResponse: + return AsyncAssistantsWithStreamingResponse(self) + async def create( self, *, @@ -619,19 +635,19 @@ class AssistantsWithRawResponse: def __init__(self, assistants: Assistants) -> None: self.files = FilesWithRawResponse(assistants.files) - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( assistants.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( assistants.retrieve, ) - self.update = to_raw_response_wrapper( + self.update = _legacy_response.to_raw_response_wrapper( assistants.update, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( assistants.list, ) - self.delete = to_raw_response_wrapper( + self.delete = _legacy_response.to_raw_response_wrapper( assistants.delete, ) @@ -640,18 +656,60 @@ class AsyncAssistantsWithRawResponse: def __init__(self, assistants: AsyncAssistants) -> None: self.files = AsyncFilesWithRawResponse(assistants.files) - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + assistants.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + assistants.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + assistants.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + assistants.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + assistants.delete, + ) + + +class AssistantsWithStreamingResponse: + def __init__(self, assistants: Assistants) -> None: + self.files = FilesWithStreamingResponse(assistants.files) + + self.create = to_streamed_response_wrapper( + assistants.create, + ) + self.retrieve = to_streamed_response_wrapper( + assistants.retrieve, + ) + self.update = to_streamed_response_wrapper( + assistants.update, + ) + self.list = to_streamed_response_wrapper( + assistants.list, + ) + self.delete = to_streamed_response_wrapper( + assistants.delete, + ) + + +class AsyncAssistantsWithStreamingResponse: + def __init__(self, assistants: AsyncAssistants) -> None: + self.files = AsyncFilesWithStreamingResponse(assistants.files) + + self.create = async_to_streamed_response_wrapper( assistants.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( assistants.retrieve, ) - self.update = async_to_raw_response_wrapper( + self.update = async_to_streamed_response_wrapper( assistants.update, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( assistants.list, ) - self.delete = async_to_raw_response_wrapper( + self.delete = async_to_streamed_response_wrapper( assistants.delete, ) diff --git a/src/openai/resources/beta/assistants/files.py b/src/openai/resources/beta/assistants/files.py index 0624e562f..9e45ce46d 100644 --- a/src/openai/resources/beta/assistants/files.py +++ b/src/openai/resources/beta/assistants/files.py @@ -6,11 +6,12 @@ import httpx +from .... import _legacy_response from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ....pagination import SyncCursorPage, AsyncCursorPage from ...._base_client import ( AsyncPaginator, @@ -26,6 +27,10 @@ class Files(SyncAPIResource): def with_raw_response(self) -> FilesWithRawResponse: return FilesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> FilesWithStreamingResponse: + return FilesWithStreamingResponse(self) + def create( self, assistant_id: str, @@ -203,6 +208,10 @@ class AsyncFiles(AsyncAPIResource): def with_raw_response(self) -> AsyncFilesWithRawResponse: return AsyncFilesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncFilesWithStreamingResponse: + return AsyncFilesWithStreamingResponse(self) + async def create( self, assistant_id: str, @@ -377,31 +386,63 @@ async def delete( class FilesWithRawResponse: def __init__(self, files: Files) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( files.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( files.retrieve, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( files.list, ) - self.delete = to_raw_response_wrapper( + self.delete = _legacy_response.to_raw_response_wrapper( files.delete, ) class AsyncFilesWithRawResponse: def __init__(self, files: AsyncFiles) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + files.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + files.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + files.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + files.delete, + ) + + +class FilesWithStreamingResponse: + def __init__(self, files: Files) -> None: + self.create = to_streamed_response_wrapper( + files.create, + ) + self.retrieve = to_streamed_response_wrapper( + files.retrieve, + ) + self.list = to_streamed_response_wrapper( + files.list, + ) + self.delete = to_streamed_response_wrapper( + files.delete, + ) + + +class AsyncFilesWithStreamingResponse: + def __init__(self, files: AsyncFiles) -> None: + self.create = async_to_streamed_response_wrapper( files.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( files.retrieve, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( files.list, ) - self.delete = async_to_raw_response_wrapper( + self.delete = async_to_streamed_response_wrapper( files.delete, ) diff --git a/src/openai/resources/beta/beta.py b/src/openai/resources/beta/beta.py index d87406ac9..b11a706d5 100644 --- a/src/openai/resources/beta/beta.py +++ b/src/openai/resources/beta/beta.py @@ -2,9 +2,23 @@ from __future__ import annotations -from .threads import Threads, AsyncThreads, ThreadsWithRawResponse, AsyncThreadsWithRawResponse +from .threads import ( + Threads, + AsyncThreads, + ThreadsWithRawResponse, + AsyncThreadsWithRawResponse, + ThreadsWithStreamingResponse, + AsyncThreadsWithStreamingResponse, +) from ..._compat import cached_property -from .assistants import Assistants, AsyncAssistants, AssistantsWithRawResponse, AsyncAssistantsWithRawResponse +from .assistants import ( + Assistants, + AsyncAssistants, + AssistantsWithRawResponse, + AsyncAssistantsWithRawResponse, + AssistantsWithStreamingResponse, + AsyncAssistantsWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource from .threads.threads import Threads, AsyncThreads from .assistants.assistants import Assistants, AsyncAssistants @@ -25,6 +39,10 @@ def threads(self) -> Threads: def with_raw_response(self) -> BetaWithRawResponse: return BetaWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> BetaWithStreamingResponse: + return BetaWithStreamingResponse(self) + class AsyncBeta(AsyncAPIResource): @cached_property @@ -39,6 +57,10 @@ def threads(self) -> AsyncThreads: def with_raw_response(self) -> AsyncBetaWithRawResponse: return AsyncBetaWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncBetaWithStreamingResponse: + return AsyncBetaWithStreamingResponse(self) + class BetaWithRawResponse: def __init__(self, beta: Beta) -> None: @@ -50,3 +72,15 @@ class AsyncBetaWithRawResponse: def __init__(self, beta: AsyncBeta) -> None: self.assistants = AsyncAssistantsWithRawResponse(beta.assistants) self.threads = AsyncThreadsWithRawResponse(beta.threads) + + +class BetaWithStreamingResponse: + def __init__(self, beta: Beta) -> None: + self.assistants = AssistantsWithStreamingResponse(beta.assistants) + self.threads = ThreadsWithStreamingResponse(beta.threads) + + +class AsyncBetaWithStreamingResponse: + def __init__(self, beta: AsyncBeta) -> None: + self.assistants = AsyncAssistantsWithStreamingResponse(beta.assistants) + self.threads = AsyncThreadsWithStreamingResponse(beta.threads) diff --git a/src/openai/resources/beta/threads/__init__.py b/src/openai/resources/beta/threads/__init__.py index fe7c5e5a2..886574b32 100644 --- a/src/openai/resources/beta/threads/__init__.py +++ b/src/openai/resources/beta/threads/__init__.py @@ -1,20 +1,47 @@ # File generated from our OpenAPI spec by Stainless. -from .runs import Runs, AsyncRuns, RunsWithRawResponse, AsyncRunsWithRawResponse -from .threads import Threads, AsyncThreads, ThreadsWithRawResponse, AsyncThreadsWithRawResponse -from .messages import Messages, AsyncMessages, MessagesWithRawResponse, AsyncMessagesWithRawResponse +from .runs import ( + Runs, + AsyncRuns, + RunsWithRawResponse, + AsyncRunsWithRawResponse, + RunsWithStreamingResponse, + AsyncRunsWithStreamingResponse, +) +from .threads import ( + Threads, + AsyncThreads, + ThreadsWithRawResponse, + AsyncThreadsWithRawResponse, + ThreadsWithStreamingResponse, + AsyncThreadsWithStreamingResponse, +) +from .messages import ( + Messages, + AsyncMessages, + MessagesWithRawResponse, + AsyncMessagesWithRawResponse, + MessagesWithStreamingResponse, + AsyncMessagesWithStreamingResponse, +) __all__ = [ "Runs", "AsyncRuns", "RunsWithRawResponse", "AsyncRunsWithRawResponse", + "RunsWithStreamingResponse", + "AsyncRunsWithStreamingResponse", "Messages", "AsyncMessages", "MessagesWithRawResponse", "AsyncMessagesWithRawResponse", + "MessagesWithStreamingResponse", + "AsyncMessagesWithStreamingResponse", "Threads", "AsyncThreads", "ThreadsWithRawResponse", "AsyncThreadsWithRawResponse", + "ThreadsWithStreamingResponse", + "AsyncThreadsWithStreamingResponse", ] diff --git a/src/openai/resources/beta/threads/messages/__init__.py b/src/openai/resources/beta/threads/messages/__init__.py index cef618ed1..0acb0ab20 100644 --- a/src/openai/resources/beta/threads/messages/__init__.py +++ b/src/openai/resources/beta/threads/messages/__init__.py @@ -1,15 +1,33 @@ # File generated from our OpenAPI spec by Stainless. -from .files import Files, AsyncFiles, FilesWithRawResponse, AsyncFilesWithRawResponse -from .messages import Messages, AsyncMessages, MessagesWithRawResponse, AsyncMessagesWithRawResponse +from .files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) +from .messages import ( + Messages, + AsyncMessages, + MessagesWithRawResponse, + AsyncMessagesWithRawResponse, + MessagesWithStreamingResponse, + AsyncMessagesWithStreamingResponse, +) __all__ = [ "Files", "AsyncFiles", "FilesWithRawResponse", "AsyncFilesWithRawResponse", + "FilesWithStreamingResponse", + "AsyncFilesWithStreamingResponse", "Messages", "AsyncMessages", "MessagesWithRawResponse", "AsyncMessagesWithRawResponse", + "MessagesWithStreamingResponse", + "AsyncMessagesWithStreamingResponse", ] diff --git a/src/openai/resources/beta/threads/messages/files.py b/src/openai/resources/beta/threads/messages/files.py index 4b95b200e..d0a963f1a 100644 --- a/src/openai/resources/beta/threads/messages/files.py +++ b/src/openai/resources/beta/threads/messages/files.py @@ -6,11 +6,12 @@ import httpx +from ..... import _legacy_response from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ....._utils import maybe_transform from ....._compat import cached_property from ....._resource import SyncAPIResource, AsyncAPIResource -from ....._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .....pagination import SyncCursorPage, AsyncCursorPage from ....._base_client import ( AsyncPaginator, @@ -26,6 +27,10 @@ class Files(SyncAPIResource): def with_raw_response(self) -> FilesWithRawResponse: return FilesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> FilesWithStreamingResponse: + return FilesWithStreamingResponse(self) + def retrieve( self, file_id: str, @@ -133,6 +138,10 @@ class AsyncFiles(AsyncAPIResource): def with_raw_response(self) -> AsyncFilesWithRawResponse: return AsyncFilesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncFilesWithStreamingResponse: + return AsyncFilesWithStreamingResponse(self) + async def retrieve( self, file_id: str, @@ -237,19 +246,39 @@ def list( class FilesWithRawResponse: def __init__(self, files: Files) -> None: - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( files.retrieve, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( files.list, ) class AsyncFilesWithRawResponse: def __init__(self, files: AsyncFiles) -> None: - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + files.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + files.list, + ) + + +class FilesWithStreamingResponse: + def __init__(self, files: Files) -> None: + self.retrieve = to_streamed_response_wrapper( + files.retrieve, + ) + self.list = to_streamed_response_wrapper( + files.list, + ) + + +class AsyncFilesWithStreamingResponse: + def __init__(self, files: AsyncFiles) -> None: + self.retrieve = async_to_streamed_response_wrapper( files.retrieve, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( files.list, ) diff --git a/src/openai/resources/beta/threads/messages/messages.py b/src/openai/resources/beta/threads/messages/messages.py index 146f66562..1a15dd36c 100644 --- a/src/openai/resources/beta/threads/messages/messages.py +++ b/src/openai/resources/beta/threads/messages/messages.py @@ -7,12 +7,20 @@ import httpx -from .files import Files, AsyncFiles, FilesWithRawResponse, AsyncFilesWithRawResponse +from ..... import _legacy_response +from .files import ( + Files, + AsyncFiles, + FilesWithRawResponse, + AsyncFilesWithRawResponse, + FilesWithStreamingResponse, + AsyncFilesWithStreamingResponse, +) from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ....._utils import maybe_transform from ....._compat import cached_property from ....._resource import SyncAPIResource, AsyncAPIResource -from ....._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .....pagination import SyncCursorPage, AsyncCursorPage from ....._base_client import ( AsyncPaginator, @@ -32,6 +40,10 @@ def files(self) -> Files: def with_raw_response(self) -> MessagesWithRawResponse: return MessagesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> MessagesWithStreamingResponse: + return MessagesWithStreamingResponse(self) + def create( self, thread_id: str, @@ -240,6 +252,10 @@ def files(self) -> AsyncFiles: def with_raw_response(self) -> AsyncMessagesWithRawResponse: return AsyncMessagesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncMessagesWithStreamingResponse: + return AsyncMessagesWithStreamingResponse(self) + async def create( self, thread_id: str, @@ -443,16 +459,16 @@ class MessagesWithRawResponse: def __init__(self, messages: Messages) -> None: self.files = FilesWithRawResponse(messages.files) - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( messages.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( messages.retrieve, ) - self.update = to_raw_response_wrapper( + self.update = _legacy_response.to_raw_response_wrapper( messages.update, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( messages.list, ) @@ -461,15 +477,51 @@ class AsyncMessagesWithRawResponse: def __init__(self, messages: AsyncMessages) -> None: self.files = AsyncFilesWithRawResponse(messages.files) - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + messages.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + messages.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + messages.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + messages.list, + ) + + +class MessagesWithStreamingResponse: + def __init__(self, messages: Messages) -> None: + self.files = FilesWithStreamingResponse(messages.files) + + self.create = to_streamed_response_wrapper( + messages.create, + ) + self.retrieve = to_streamed_response_wrapper( + messages.retrieve, + ) + self.update = to_streamed_response_wrapper( + messages.update, + ) + self.list = to_streamed_response_wrapper( + messages.list, + ) + + +class AsyncMessagesWithStreamingResponse: + def __init__(self, messages: AsyncMessages) -> None: + self.files = AsyncFilesWithStreamingResponse(messages.files) + + self.create = async_to_streamed_response_wrapper( messages.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( messages.retrieve, ) - self.update = async_to_raw_response_wrapper( + self.update = async_to_streamed_response_wrapper( messages.update, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( messages.list, ) diff --git a/src/openai/resources/beta/threads/runs/__init__.py b/src/openai/resources/beta/threads/runs/__init__.py index 6b6181397..659c96acf 100644 --- a/src/openai/resources/beta/threads/runs/__init__.py +++ b/src/openai/resources/beta/threads/runs/__init__.py @@ -1,15 +1,33 @@ # File generated from our OpenAPI spec by Stainless. -from .runs import Runs, AsyncRuns, RunsWithRawResponse, AsyncRunsWithRawResponse -from .steps import Steps, AsyncSteps, StepsWithRawResponse, AsyncStepsWithRawResponse +from .runs import ( + Runs, + AsyncRuns, + RunsWithRawResponse, + AsyncRunsWithRawResponse, + RunsWithStreamingResponse, + AsyncRunsWithStreamingResponse, +) +from .steps import ( + Steps, + AsyncSteps, + StepsWithRawResponse, + AsyncStepsWithRawResponse, + StepsWithStreamingResponse, + AsyncStepsWithStreamingResponse, +) __all__ = [ "Steps", "AsyncSteps", "StepsWithRawResponse", "AsyncStepsWithRawResponse", + "StepsWithStreamingResponse", + "AsyncStepsWithStreamingResponse", "Runs", "AsyncRuns", "RunsWithRawResponse", "AsyncRunsWithRawResponse", + "RunsWithStreamingResponse", + "AsyncRunsWithStreamingResponse", ] diff --git a/src/openai/resources/beta/threads/runs/runs.py b/src/openai/resources/beta/threads/runs/runs.py index 87e62eb36..eb6c974ea 100644 --- a/src/openai/resources/beta/threads/runs/runs.py +++ b/src/openai/resources/beta/threads/runs/runs.py @@ -7,12 +7,20 @@ import httpx -from .steps import Steps, AsyncSteps, StepsWithRawResponse, AsyncStepsWithRawResponse +from ..... import _legacy_response +from .steps import ( + Steps, + AsyncSteps, + StepsWithRawResponse, + AsyncStepsWithRawResponse, + StepsWithStreamingResponse, + AsyncStepsWithStreamingResponse, +) from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ....._utils import maybe_transform from ....._compat import cached_property from ....._resource import SyncAPIResource, AsyncAPIResource -from ....._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .....pagination import SyncCursorPage, AsyncCursorPage from ....._base_client import ( AsyncPaginator, @@ -38,6 +46,10 @@ def steps(self) -> Steps: def with_raw_response(self) -> RunsWithRawResponse: return RunsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> RunsWithStreamingResponse: + return RunsWithStreamingResponse(self) + def create( self, thread_id: str, @@ -335,6 +347,10 @@ def steps(self) -> AsyncSteps: def with_raw_response(self) -> AsyncRunsWithRawResponse: return AsyncRunsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncRunsWithStreamingResponse: + return AsyncRunsWithStreamingResponse(self) + async def create( self, thread_id: str, @@ -627,22 +643,22 @@ class RunsWithRawResponse: def __init__(self, runs: Runs) -> None: self.steps = StepsWithRawResponse(runs.steps) - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( runs.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( runs.retrieve, ) - self.update = to_raw_response_wrapper( + self.update = _legacy_response.to_raw_response_wrapper( runs.update, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( runs.list, ) - self.cancel = to_raw_response_wrapper( + self.cancel = _legacy_response.to_raw_response_wrapper( runs.cancel, ) - self.submit_tool_outputs = to_raw_response_wrapper( + self.submit_tool_outputs = _legacy_response.to_raw_response_wrapper( runs.submit_tool_outputs, ) @@ -651,21 +667,69 @@ class AsyncRunsWithRawResponse: def __init__(self, runs: AsyncRuns) -> None: self.steps = AsyncStepsWithRawResponse(runs.steps) - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + runs.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + runs.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + runs.update, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + runs.list, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + runs.cancel, + ) + self.submit_tool_outputs = _legacy_response.async_to_raw_response_wrapper( + runs.submit_tool_outputs, + ) + + +class RunsWithStreamingResponse: + def __init__(self, runs: Runs) -> None: + self.steps = StepsWithStreamingResponse(runs.steps) + + self.create = to_streamed_response_wrapper( + runs.create, + ) + self.retrieve = to_streamed_response_wrapper( + runs.retrieve, + ) + self.update = to_streamed_response_wrapper( + runs.update, + ) + self.list = to_streamed_response_wrapper( + runs.list, + ) + self.cancel = to_streamed_response_wrapper( + runs.cancel, + ) + self.submit_tool_outputs = to_streamed_response_wrapper( + runs.submit_tool_outputs, + ) + + +class AsyncRunsWithStreamingResponse: + def __init__(self, runs: AsyncRuns) -> None: + self.steps = AsyncStepsWithStreamingResponse(runs.steps) + + self.create = async_to_streamed_response_wrapper( runs.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( runs.retrieve, ) - self.update = async_to_raw_response_wrapper( + self.update = async_to_streamed_response_wrapper( runs.update, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( runs.list, ) - self.cancel = async_to_raw_response_wrapper( + self.cancel = async_to_streamed_response_wrapper( runs.cancel, ) - self.submit_tool_outputs = async_to_raw_response_wrapper( + self.submit_tool_outputs = async_to_streamed_response_wrapper( runs.submit_tool_outputs, ) diff --git a/src/openai/resources/beta/threads/runs/steps.py b/src/openai/resources/beta/threads/runs/steps.py index 439926a41..566ad9e4d 100644 --- a/src/openai/resources/beta/threads/runs/steps.py +++ b/src/openai/resources/beta/threads/runs/steps.py @@ -6,11 +6,12 @@ import httpx +from ..... import _legacy_response from ....._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ....._utils import maybe_transform from ....._compat import cached_property from ....._resource import SyncAPIResource, AsyncAPIResource -from ....._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ....._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .....pagination import SyncCursorPage, AsyncCursorPage from ....._base_client import ( AsyncPaginator, @@ -26,6 +27,10 @@ class Steps(SyncAPIResource): def with_raw_response(self) -> StepsWithRawResponse: return StepsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> StepsWithStreamingResponse: + return StepsWithStreamingResponse(self) + def retrieve( self, step_id: str, @@ -132,6 +137,10 @@ class AsyncSteps(AsyncAPIResource): def with_raw_response(self) -> AsyncStepsWithRawResponse: return AsyncStepsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncStepsWithStreamingResponse: + return AsyncStepsWithStreamingResponse(self) + async def retrieve( self, step_id: str, @@ -235,19 +244,39 @@ def list( class StepsWithRawResponse: def __init__(self, steps: Steps) -> None: - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( steps.retrieve, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( steps.list, ) class AsyncStepsWithRawResponse: def __init__(self, steps: AsyncSteps) -> None: - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + steps.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + steps.list, + ) + + +class StepsWithStreamingResponse: + def __init__(self, steps: Steps) -> None: + self.retrieve = to_streamed_response_wrapper( + steps.retrieve, + ) + self.list = to_streamed_response_wrapper( + steps.list, + ) + + +class AsyncStepsWithStreamingResponse: + def __init__(self, steps: AsyncSteps) -> None: + self.retrieve = async_to_streamed_response_wrapper( steps.retrieve, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( steps.list, ) diff --git a/src/openai/resources/beta/threads/threads.py b/src/openai/resources/beta/threads/threads.py index 0ae409bb2..14bfbe9bb 100644 --- a/src/openai/resources/beta/threads/threads.py +++ b/src/openai/resources/beta/threads/threads.py @@ -6,14 +6,29 @@ import httpx -from .runs import Runs, AsyncRuns, RunsWithRawResponse, AsyncRunsWithRawResponse -from .messages import Messages, AsyncMessages, MessagesWithRawResponse, AsyncMessagesWithRawResponse +from .... import _legacy_response +from .runs import ( + Runs, + AsyncRuns, + RunsWithRawResponse, + AsyncRunsWithRawResponse, + RunsWithStreamingResponse, + AsyncRunsWithStreamingResponse, +) +from .messages import ( + Messages, + AsyncMessages, + MessagesWithRawResponse, + AsyncMessagesWithRawResponse, + MessagesWithStreamingResponse, + AsyncMessagesWithStreamingResponse, +) from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from .runs.runs import Runs, AsyncRuns from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ....types.beta import ( Thread, ThreadDeleted, @@ -43,6 +58,10 @@ def messages(self) -> Messages: def with_raw_response(self) -> ThreadsWithRawResponse: return ThreadsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ThreadsWithStreamingResponse: + return ThreadsWithStreamingResponse(self) + def create( self, *, @@ -278,6 +297,10 @@ def messages(self) -> AsyncMessages: def with_raw_response(self) -> AsyncThreadsWithRawResponse: return AsyncThreadsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncThreadsWithStreamingResponse: + return AsyncThreadsWithStreamingResponse(self) + async def create( self, *, @@ -505,19 +528,19 @@ def __init__(self, threads: Threads) -> None: self.runs = RunsWithRawResponse(threads.runs) self.messages = MessagesWithRawResponse(threads.messages) - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( threads.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( threads.retrieve, ) - self.update = to_raw_response_wrapper( + self.update = _legacy_response.to_raw_response_wrapper( threads.update, ) - self.delete = to_raw_response_wrapper( + self.delete = _legacy_response.to_raw_response_wrapper( threads.delete, ) - self.create_and_run = to_raw_response_wrapper( + self.create_and_run = _legacy_response.to_raw_response_wrapper( threads.create_and_run, ) @@ -527,18 +550,62 @@ def __init__(self, threads: AsyncThreads) -> None: self.runs = AsyncRunsWithRawResponse(threads.runs) self.messages = AsyncMessagesWithRawResponse(threads.messages) - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + threads.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + threads.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + threads.update, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + threads.delete, + ) + self.create_and_run = _legacy_response.async_to_raw_response_wrapper( + threads.create_and_run, + ) + + +class ThreadsWithStreamingResponse: + def __init__(self, threads: Threads) -> None: + self.runs = RunsWithStreamingResponse(threads.runs) + self.messages = MessagesWithStreamingResponse(threads.messages) + + self.create = to_streamed_response_wrapper( + threads.create, + ) + self.retrieve = to_streamed_response_wrapper( + threads.retrieve, + ) + self.update = to_streamed_response_wrapper( + threads.update, + ) + self.delete = to_streamed_response_wrapper( + threads.delete, + ) + self.create_and_run = to_streamed_response_wrapper( + threads.create_and_run, + ) + + +class AsyncThreadsWithStreamingResponse: + def __init__(self, threads: AsyncThreads) -> None: + self.runs = AsyncRunsWithStreamingResponse(threads.runs) + self.messages = AsyncMessagesWithStreamingResponse(threads.messages) + + self.create = async_to_streamed_response_wrapper( threads.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( threads.retrieve, ) - self.update = async_to_raw_response_wrapper( + self.update = async_to_streamed_response_wrapper( threads.update, ) - self.delete = async_to_raw_response_wrapper( + self.delete = async_to_streamed_response_wrapper( threads.delete, ) - self.create_and_run = async_to_raw_response_wrapper( + self.create_and_run = async_to_streamed_response_wrapper( threads.create_and_run, ) diff --git a/src/openai/resources/chat/__init__.py b/src/openai/resources/chat/__init__.py index 85b246509..a9668053c 100644 --- a/src/openai/resources/chat/__init__.py +++ b/src/openai/resources/chat/__init__.py @@ -1,15 +1,33 @@ # File generated from our OpenAPI spec by Stainless. -from .chat import Chat, AsyncChat, ChatWithRawResponse, AsyncChatWithRawResponse -from .completions import Completions, AsyncCompletions, CompletionsWithRawResponse, AsyncCompletionsWithRawResponse +from .chat import ( + Chat, + AsyncChat, + ChatWithRawResponse, + AsyncChatWithRawResponse, + ChatWithStreamingResponse, + AsyncChatWithStreamingResponse, +) +from .completions import ( + Completions, + AsyncCompletions, + CompletionsWithRawResponse, + AsyncCompletionsWithRawResponse, + CompletionsWithStreamingResponse, + AsyncCompletionsWithStreamingResponse, +) __all__ = [ "Completions", "AsyncCompletions", "CompletionsWithRawResponse", "AsyncCompletionsWithRawResponse", + "CompletionsWithStreamingResponse", + "AsyncCompletionsWithStreamingResponse", "Chat", "AsyncChat", "ChatWithRawResponse", "AsyncChatWithRawResponse", + "ChatWithStreamingResponse", + "AsyncChatWithStreamingResponse", ] diff --git a/src/openai/resources/chat/chat.py b/src/openai/resources/chat/chat.py index 000520de2..467a5e401 100644 --- a/src/openai/resources/chat/chat.py +++ b/src/openai/resources/chat/chat.py @@ -4,7 +4,14 @@ from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from .completions import Completions, AsyncCompletions, CompletionsWithRawResponse, AsyncCompletionsWithRawResponse +from .completions import ( + Completions, + AsyncCompletions, + CompletionsWithRawResponse, + AsyncCompletionsWithRawResponse, + CompletionsWithStreamingResponse, + AsyncCompletionsWithStreamingResponse, +) __all__ = ["Chat", "AsyncChat"] @@ -18,6 +25,10 @@ def completions(self) -> Completions: def with_raw_response(self) -> ChatWithRawResponse: return ChatWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ChatWithStreamingResponse: + return ChatWithStreamingResponse(self) + class AsyncChat(AsyncAPIResource): @cached_property @@ -28,6 +39,10 @@ def completions(self) -> AsyncCompletions: def with_raw_response(self) -> AsyncChatWithRawResponse: return AsyncChatWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncChatWithStreamingResponse: + return AsyncChatWithStreamingResponse(self) + class ChatWithRawResponse: def __init__(self, chat: Chat) -> None: @@ -37,3 +52,13 @@ def __init__(self, chat: Chat) -> None: class AsyncChatWithRawResponse: def __init__(self, chat: AsyncChat) -> None: self.completions = AsyncCompletionsWithRawResponse(chat.completions) + + +class ChatWithStreamingResponse: + def __init__(self, chat: Chat) -> None: + self.completions = CompletionsWithStreamingResponse(chat.completions) + + +class AsyncChatWithStreamingResponse: + def __init__(self, chat: AsyncChat) -> None: + self.completions = AsyncCompletionsWithStreamingResponse(chat.completions) diff --git a/src/openai/resources/chat/completions.py b/src/openai/resources/chat/completions.py index fa096784d..53645a9eb 100644 --- a/src/openai/resources/chat/completions.py +++ b/src/openai/resources/chat/completions.py @@ -7,11 +7,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import required_args, maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..._streaming import Stream, AsyncStream from ...types.chat import ( ChatCompletion, @@ -33,6 +34,10 @@ class Completions(SyncAPIResource): def with_raw_response(self) -> CompletionsWithRawResponse: return CompletionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> CompletionsWithStreamingResponse: + return CompletionsWithStreamingResponse(self) + @overload def create( self, @@ -681,6 +686,10 @@ class AsyncCompletions(AsyncAPIResource): def with_raw_response(self) -> AsyncCompletionsWithRawResponse: return AsyncCompletionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncCompletionsWithStreamingResponse: + return AsyncCompletionsWithStreamingResponse(self) + @overload async def create( self, @@ -1326,13 +1335,27 @@ async def create( class CompletionsWithRawResponse: def __init__(self, completions: Completions) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( completions.create, ) class AsyncCompletionsWithRawResponse: def __init__(self, completions: AsyncCompletions) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + completions.create, + ) + + +class CompletionsWithStreamingResponse: + def __init__(self, completions: Completions) -> None: + self.create = to_streamed_response_wrapper( + completions.create, + ) + + +class AsyncCompletionsWithStreamingResponse: + def __init__(self, completions: AsyncCompletions) -> None: + self.create = async_to_streamed_response_wrapper( completions.create, ) diff --git a/src/openai/resources/completions.py b/src/openai/resources/completions.py index 87dd09005..43a994752 100644 --- a/src/openai/resources/completions.py +++ b/src/openai/resources/completions.py @@ -7,12 +7,13 @@ import httpx +from .. import _legacy_response from ..types import Completion, completion_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import required_args, maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._streaming import Stream, AsyncStream from .._base_client import ( make_request_options, @@ -26,6 +27,10 @@ class Completions(SyncAPIResource): def with_raw_response(self) -> CompletionsWithRawResponse: return CompletionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> CompletionsWithStreamingResponse: + return CompletionsWithStreamingResponse(self) + @overload def create( self, @@ -536,6 +541,10 @@ class AsyncCompletions(AsyncAPIResource): def with_raw_response(self) -> AsyncCompletionsWithRawResponse: return AsyncCompletionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncCompletionsWithStreamingResponse: + return AsyncCompletionsWithStreamingResponse(self) + @overload async def create( self, @@ -1043,13 +1052,27 @@ async def create( class CompletionsWithRawResponse: def __init__(self, completions: Completions) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( completions.create, ) class AsyncCompletionsWithRawResponse: def __init__(self, completions: AsyncCompletions) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + completions.create, + ) + + +class CompletionsWithStreamingResponse: + def __init__(self, completions: Completions) -> None: + self.create = to_streamed_response_wrapper( + completions.create, + ) + + +class AsyncCompletionsWithStreamingResponse: + def __init__(self, completions: AsyncCompletions) -> None: + self.create = async_to_streamed_response_wrapper( completions.create, ) diff --git a/src/openai/resources/embeddings.py b/src/openai/resources/embeddings.py index e93b29d45..49ce0f2fc 100644 --- a/src/openai/resources/embeddings.py +++ b/src/openai/resources/embeddings.py @@ -8,13 +8,14 @@ import httpx +from .. import _legacy_response from ..types import CreateEmbeddingResponse, embedding_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import is_given, maybe_transform from .._compat import cached_property from .._extras import numpy as np, has_numpy from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._base_client import ( make_request_options, ) @@ -27,6 +28,10 @@ class Embeddings(SyncAPIResource): def with_raw_response(self) -> EmbeddingsWithRawResponse: return EmbeddingsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> EmbeddingsWithStreamingResponse: + return EmbeddingsWithStreamingResponse(self) + def create( self, *, @@ -119,6 +124,10 @@ class AsyncEmbeddings(AsyncAPIResource): def with_raw_response(self) -> AsyncEmbeddingsWithRawResponse: return AsyncEmbeddingsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncEmbeddingsWithStreamingResponse: + return AsyncEmbeddingsWithStreamingResponse(self) + async def create( self, *, @@ -208,13 +217,27 @@ def parser(obj: CreateEmbeddingResponse) -> CreateEmbeddingResponse: class EmbeddingsWithRawResponse: def __init__(self, embeddings: Embeddings) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( embeddings.create, ) class AsyncEmbeddingsWithRawResponse: def __init__(self, embeddings: AsyncEmbeddings) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + embeddings.create, + ) + + +class EmbeddingsWithStreamingResponse: + def __init__(self, embeddings: Embeddings) -> None: + self.create = to_streamed_response_wrapper( + embeddings.create, + ) + + +class AsyncEmbeddingsWithStreamingResponse: + def __init__(self, embeddings: AsyncEmbeddings) -> None: + self.create = async_to_streamed_response_wrapper( embeddings.create, ) diff --git a/src/openai/resources/files.py b/src/openai/resources/files.py index 1acf6f806..f435e70a2 100644 --- a/src/openai/resources/files.py +++ b/src/openai/resources/files.py @@ -9,16 +9,23 @@ import httpx +from .. import _legacy_response from ..types import FileObject, FileDeleted, file_list_params, file_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes from .._utils import extract_files, maybe_transform, deepcopy_minimal from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import ( + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_streamed_response_wrapper, + async_to_streamed_response_wrapper, + to_custom_streamed_response_wrapper, + async_to_custom_streamed_response_wrapper, +) from ..pagination import SyncPage, AsyncPage from .._base_client import ( AsyncPaginator, - HttpxBinaryResponseContent, make_request_options, ) @@ -30,6 +37,10 @@ class Files(SyncAPIResource): def with_raw_response(self) -> FilesWithRawResponse: return FilesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> FilesWithStreamingResponse: + return FilesWithStreamingResponse(self) + def create( self, *, @@ -209,7 +220,7 @@ def content( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> HttpxBinaryResponseContent: + ) -> _legacy_response.HttpxBinaryResponseContent: """ Returns the contents of the specified file. @@ -227,7 +238,7 @@ def content( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=HttpxBinaryResponseContent, + cast_to=_legacy_response.HttpxBinaryResponseContent, ) @typing_extensions.deprecated("The `.content()` method should be used instead") @@ -292,6 +303,10 @@ class AsyncFiles(AsyncAPIResource): def with_raw_response(self) -> AsyncFilesWithRawResponse: return AsyncFilesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncFilesWithStreamingResponse: + return AsyncFilesWithStreamingResponse(self) + async def create( self, *, @@ -471,7 +486,7 @@ async def content( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> HttpxBinaryResponseContent: + ) -> _legacy_response.HttpxBinaryResponseContent: """ Returns the contents of the specified file. @@ -489,7 +504,7 @@ async def content( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=HttpxBinaryResponseContent, + cast_to=_legacy_response.HttpxBinaryResponseContent, ) @typing_extensions.deprecated("The `.content()` method should be used instead") @@ -551,43 +566,97 @@ async def wait_for_processing( class FilesWithRawResponse: def __init__(self, files: Files) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( files.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( files.retrieve, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( files.list, ) - self.delete = to_raw_response_wrapper( + self.delete = _legacy_response.to_raw_response_wrapper( files.delete, ) - self.content = to_raw_response_wrapper( + self.content = _legacy_response.to_raw_response_wrapper( files.content, ) - self.retrieve_content = to_raw_response_wrapper( # pyright: ignore[reportDeprecated] - files.retrieve_content # pyright: ignore[reportDeprecated], + self.retrieve_content = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + files.retrieve_content # pyright: ignore[reportDeprecated], + ) ) class AsyncFilesWithRawResponse: def __init__(self, files: AsyncFiles) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + files.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + files.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + files.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + files.delete, + ) + self.content = _legacy_response.async_to_raw_response_wrapper( + files.content, + ) + self.retrieve_content = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + files.retrieve_content # pyright: ignore[reportDeprecated], + ) + ) + + +class FilesWithStreamingResponse: + def __init__(self, files: Files) -> None: + self.create = to_streamed_response_wrapper( + files.create, + ) + self.retrieve = to_streamed_response_wrapper( + files.retrieve, + ) + self.list = to_streamed_response_wrapper( + files.list, + ) + self.delete = to_streamed_response_wrapper( + files.delete, + ) + self.content = to_custom_streamed_response_wrapper( + files.content, + StreamedBinaryAPIResponse, + ) + self.retrieve_content = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + files.retrieve_content # pyright: ignore[reportDeprecated], + ) + ) + + +class AsyncFilesWithStreamingResponse: + def __init__(self, files: AsyncFiles) -> None: + self.create = async_to_streamed_response_wrapper( files.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( files.retrieve, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( files.list, ) - self.delete = async_to_raw_response_wrapper( + self.delete = async_to_streamed_response_wrapper( files.delete, ) - self.content = async_to_raw_response_wrapper( + self.content = async_to_custom_streamed_response_wrapper( files.content, + AsyncStreamedBinaryAPIResponse, ) - self.retrieve_content = async_to_raw_response_wrapper( # pyright: ignore[reportDeprecated] - files.retrieve_content # pyright: ignore[reportDeprecated], + self.retrieve_content = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + files.retrieve_content # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/openai/resources/fine_tuning/__init__.py b/src/openai/resources/fine_tuning/__init__.py index 27445fb70..ab0c28ef4 100644 --- a/src/openai/resources/fine_tuning/__init__.py +++ b/src/openai/resources/fine_tuning/__init__.py @@ -1,15 +1,33 @@ # File generated from our OpenAPI spec by Stainless. -from .jobs import Jobs, AsyncJobs, JobsWithRawResponse, AsyncJobsWithRawResponse -from .fine_tuning import FineTuning, AsyncFineTuning, FineTuningWithRawResponse, AsyncFineTuningWithRawResponse +from .jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) +from .fine_tuning import ( + FineTuning, + AsyncFineTuning, + FineTuningWithRawResponse, + AsyncFineTuningWithRawResponse, + FineTuningWithStreamingResponse, + AsyncFineTuningWithStreamingResponse, +) __all__ = [ "Jobs", "AsyncJobs", "JobsWithRawResponse", "AsyncJobsWithRawResponse", + "JobsWithStreamingResponse", + "AsyncJobsWithStreamingResponse", "FineTuning", "AsyncFineTuning", "FineTuningWithRawResponse", "AsyncFineTuningWithRawResponse", + "FineTuningWithStreamingResponse", + "AsyncFineTuningWithStreamingResponse", ] diff --git a/src/openai/resources/fine_tuning/fine_tuning.py b/src/openai/resources/fine_tuning/fine_tuning.py index a5a68b08e..197d46fb8 100644 --- a/src/openai/resources/fine_tuning/fine_tuning.py +++ b/src/openai/resources/fine_tuning/fine_tuning.py @@ -2,7 +2,14 @@ from __future__ import annotations -from .jobs import Jobs, AsyncJobs, JobsWithRawResponse, AsyncJobsWithRawResponse +from .jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource @@ -18,6 +25,10 @@ def jobs(self) -> Jobs: def with_raw_response(self) -> FineTuningWithRawResponse: return FineTuningWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> FineTuningWithStreamingResponse: + return FineTuningWithStreamingResponse(self) + class AsyncFineTuning(AsyncAPIResource): @cached_property @@ -28,6 +39,10 @@ def jobs(self) -> AsyncJobs: def with_raw_response(self) -> AsyncFineTuningWithRawResponse: return AsyncFineTuningWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncFineTuningWithStreamingResponse: + return AsyncFineTuningWithStreamingResponse(self) + class FineTuningWithRawResponse: def __init__(self, fine_tuning: FineTuning) -> None: @@ -37,3 +52,13 @@ def __init__(self, fine_tuning: FineTuning) -> None: class AsyncFineTuningWithRawResponse: def __init__(self, fine_tuning: AsyncFineTuning) -> None: self.jobs = AsyncJobsWithRawResponse(fine_tuning.jobs) + + +class FineTuningWithStreamingResponse: + def __init__(self, fine_tuning: FineTuning) -> None: + self.jobs = JobsWithStreamingResponse(fine_tuning.jobs) + + +class AsyncFineTuningWithStreamingResponse: + def __init__(self, fine_tuning: AsyncFineTuning) -> None: + self.jobs = AsyncJobsWithStreamingResponse(fine_tuning.jobs) diff --git a/src/openai/resources/fine_tuning/jobs.py b/src/openai/resources/fine_tuning/jobs.py index 7537b48da..f337b136a 100644 --- a/src/openai/resources/fine_tuning/jobs.py +++ b/src/openai/resources/fine_tuning/jobs.py @@ -7,11 +7,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncCursorPage, AsyncCursorPage from ..._base_client import ( AsyncPaginator, @@ -33,6 +34,10 @@ class Jobs(SyncAPIResource): def with_raw_response(self) -> JobsWithRawResponse: return JobsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> JobsWithStreamingResponse: + return JobsWithStreamingResponse(self) + def create( self, *, @@ -284,6 +289,10 @@ class AsyncJobs(AsyncAPIResource): def with_raw_response(self) -> AsyncJobsWithRawResponse: return AsyncJobsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncJobsWithStreamingResponse: + return AsyncJobsWithStreamingResponse(self) + async def create( self, *, @@ -532,37 +541,75 @@ def list_events( class JobsWithRawResponse: def __init__(self, jobs: Jobs) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( jobs.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( jobs.retrieve, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( jobs.list, ) - self.cancel = to_raw_response_wrapper( + self.cancel = _legacy_response.to_raw_response_wrapper( jobs.cancel, ) - self.list_events = to_raw_response_wrapper( + self.list_events = _legacy_response.to_raw_response_wrapper( jobs.list_events, ) class AsyncJobsWithRawResponse: def __init__(self, jobs: AsyncJobs) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + jobs.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + jobs.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + jobs.list, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + jobs.cancel, + ) + self.list_events = _legacy_response.async_to_raw_response_wrapper( + jobs.list_events, + ) + + +class JobsWithStreamingResponse: + def __init__(self, jobs: Jobs) -> None: + self.create = to_streamed_response_wrapper( + jobs.create, + ) + self.retrieve = to_streamed_response_wrapper( + jobs.retrieve, + ) + self.list = to_streamed_response_wrapper( + jobs.list, + ) + self.cancel = to_streamed_response_wrapper( + jobs.cancel, + ) + self.list_events = to_streamed_response_wrapper( + jobs.list_events, + ) + + +class AsyncJobsWithStreamingResponse: + def __init__(self, jobs: AsyncJobs) -> None: + self.create = async_to_streamed_response_wrapper( jobs.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( jobs.retrieve, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( jobs.list, ) - self.cancel = async_to_raw_response_wrapper( + self.cancel = async_to_streamed_response_wrapper( jobs.cancel, ) - self.list_events = async_to_raw_response_wrapper( + self.list_events = async_to_streamed_response_wrapper( jobs.list_events, ) diff --git a/src/openai/resources/images.py b/src/openai/resources/images.py index 8e9c288af..6f1de221e 100644 --- a/src/openai/resources/images.py +++ b/src/openai/resources/images.py @@ -7,6 +7,7 @@ import httpx +from .. import _legacy_response from ..types import ( ImagesResponse, image_edit_params, @@ -17,7 +18,7 @@ from .._utils import extract_files, maybe_transform, deepcopy_minimal from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._base_client import ( make_request_options, ) @@ -30,6 +31,10 @@ class Images(SyncAPIResource): def with_raw_response(self) -> ImagesWithRawResponse: return ImagesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ImagesWithStreamingResponse: + return ImagesWithStreamingResponse(self) + def create_variation( self, *, @@ -273,6 +278,10 @@ class AsyncImages(AsyncAPIResource): def with_raw_response(self) -> AsyncImagesWithRawResponse: return AsyncImagesWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncImagesWithStreamingResponse: + return AsyncImagesWithStreamingResponse(self) + async def create_variation( self, *, @@ -513,25 +522,51 @@ async def generate( class ImagesWithRawResponse: def __init__(self, images: Images) -> None: - self.create_variation = to_raw_response_wrapper( + self.create_variation = _legacy_response.to_raw_response_wrapper( images.create_variation, ) - self.edit = to_raw_response_wrapper( + self.edit = _legacy_response.to_raw_response_wrapper( images.edit, ) - self.generate = to_raw_response_wrapper( + self.generate = _legacy_response.to_raw_response_wrapper( images.generate, ) class AsyncImagesWithRawResponse: def __init__(self, images: AsyncImages) -> None: - self.create_variation = async_to_raw_response_wrapper( + self.create_variation = _legacy_response.async_to_raw_response_wrapper( + images.create_variation, + ) + self.edit = _legacy_response.async_to_raw_response_wrapper( + images.edit, + ) + self.generate = _legacy_response.async_to_raw_response_wrapper( + images.generate, + ) + + +class ImagesWithStreamingResponse: + def __init__(self, images: Images) -> None: + self.create_variation = to_streamed_response_wrapper( + images.create_variation, + ) + self.edit = to_streamed_response_wrapper( + images.edit, + ) + self.generate = to_streamed_response_wrapper( + images.generate, + ) + + +class AsyncImagesWithStreamingResponse: + def __init__(self, images: AsyncImages) -> None: + self.create_variation = async_to_streamed_response_wrapper( images.create_variation, ) - self.edit = async_to_raw_response_wrapper( + self.edit = async_to_streamed_response_wrapper( images.edit, ) - self.generate = async_to_raw_response_wrapper( + self.generate = async_to_streamed_response_wrapper( images.generate, ) diff --git a/src/openai/resources/models.py b/src/openai/resources/models.py index 48888d98b..b431ef84f 100644 --- a/src/openai/resources/models.py +++ b/src/openai/resources/models.py @@ -4,11 +4,12 @@ import httpx +from .. import _legacy_response from ..types import Model, ModelDeleted from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncPage, AsyncPage from .._base_client import ( AsyncPaginator, @@ -23,6 +24,10 @@ class Models(SyncAPIResource): def with_raw_response(self) -> ModelsWithRawResponse: return ModelsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ModelsWithStreamingResponse: + return ModelsWithStreamingResponse(self) + def retrieve( self, model: str, @@ -117,6 +122,10 @@ class AsyncModels(AsyncAPIResource): def with_raw_response(self) -> AsyncModelsWithRawResponse: return AsyncModelsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncModelsWithStreamingResponse: + return AsyncModelsWithStreamingResponse(self) + async def retrieve( self, model: str, @@ -208,25 +217,51 @@ async def delete( class ModelsWithRawResponse: def __init__(self, models: Models) -> None: - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( models.retrieve, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( models.list, ) - self.delete = to_raw_response_wrapper( + self.delete = _legacy_response.to_raw_response_wrapper( models.delete, ) class AsyncModelsWithRawResponse: def __init__(self, models: AsyncModels) -> None: - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + models.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + models.list, + ) + self.delete = _legacy_response.async_to_raw_response_wrapper( + models.delete, + ) + + +class ModelsWithStreamingResponse: + def __init__(self, models: Models) -> None: + self.retrieve = to_streamed_response_wrapper( + models.retrieve, + ) + self.list = to_streamed_response_wrapper( + models.list, + ) + self.delete = to_streamed_response_wrapper( + models.delete, + ) + + +class AsyncModelsWithStreamingResponse: + def __init__(self, models: AsyncModels) -> None: + self.retrieve = async_to_streamed_response_wrapper( models.retrieve, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( models.list, ) - self.delete = async_to_raw_response_wrapper( + self.delete = async_to_streamed_response_wrapper( models.delete, ) diff --git a/src/openai/resources/moderations.py b/src/openai/resources/moderations.py index 120a49918..e7681f626 100644 --- a/src/openai/resources/moderations.py +++ b/src/openai/resources/moderations.py @@ -7,12 +7,13 @@ import httpx +from .. import _legacy_response from ..types import ModerationCreateResponse, moderation_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._base_client import ( make_request_options, ) @@ -25,6 +26,10 @@ class Moderations(SyncAPIResource): def with_raw_response(self) -> ModerationsWithRawResponse: return ModerationsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ModerationsWithStreamingResponse: + return ModerationsWithStreamingResponse(self) + def create( self, *, @@ -81,6 +86,10 @@ class AsyncModerations(AsyncAPIResource): def with_raw_response(self) -> AsyncModerationsWithRawResponse: return AsyncModerationsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncModerationsWithStreamingResponse: + return AsyncModerationsWithStreamingResponse(self) + async def create( self, *, @@ -134,13 +143,27 @@ async def create( class ModerationsWithRawResponse: def __init__(self, moderations: Moderations) -> None: - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( moderations.create, ) class AsyncModerationsWithRawResponse: def __init__(self, moderations: AsyncModerations) -> None: - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( + moderations.create, + ) + + +class ModerationsWithStreamingResponse: + def __init__(self, moderations: Moderations) -> None: + self.create = to_streamed_response_wrapper( + moderations.create, + ) + + +class AsyncModerationsWithStreamingResponse: + def __init__(self, moderations: AsyncModerations) -> None: + self.create = async_to_streamed_response_wrapper( moderations.create, ) diff --git a/tests/api_resources/audio/test_speech.py b/tests/api_resources/audio/test_speech.py index 23f530315..a689c0d22 100644 --- a/tests/api_resources/audio/test_speech.py +++ b/tests/api_resources/audio/test_speech.py @@ -3,15 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import httpx import pytest from respx import MockRouter +import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI -from openai._types import BinaryResponseContent +from tests.utils import assert_matches_type from openai._client import OpenAI, AsyncOpenAI +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" @@ -21,7 +25,6 @@ class TestSpeech: loose_client = OpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=False) parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) - @pytest.mark.skip(reason="Mocked tests are currently broken") @parametrize @pytest.mark.respx(base_url=base_url) def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: @@ -31,10 +34,9 @@ def test_method_create(self, client: OpenAI, respx_mock: MockRouter) -> None: model="string", voice="alloy", ) - assert isinstance(speech, BinaryResponseContent) + assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) assert speech.json() == {"foo": "bar"} - @pytest.mark.skip(reason="Mocked tests are currently broken") @parametrize @pytest.mark.respx(base_url=base_url) def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRouter) -> None: @@ -46,23 +48,41 @@ def test_method_create_with_all_params(self, client: OpenAI, respx_mock: MockRou response_format="mp3", speed=0.25, ) - assert isinstance(speech, BinaryResponseContent) + assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) assert speech.json() == {"foo": "bar"} - @pytest.mark.skip(reason="Mocked tests are currently broken") @parametrize @pytest.mark.respx(base_url=base_url) def test_raw_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + response = client.audio.speech.with_raw_response.create( input="string", model="string", voice="alloy", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" speech = response.parse() - assert isinstance(speech, BinaryResponseContent) - assert speech.json() == {"foo": "bar"} + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, speech, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_create(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.audio.speech.with_streaming_response.create( + input="string", + model="string", + voice="alloy", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + speech = response.parse() + assert_matches_type(bytes, speech, path=["response"]) + + assert cast(Any, response.is_closed) is True class TestAsyncSpeech: @@ -70,7 +90,6 @@ class TestAsyncSpeech: loose_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=False) parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) - @pytest.mark.skip(reason="Mocked tests are currently broken") @parametrize @pytest.mark.respx(base_url=base_url) async def test_method_create(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: @@ -80,10 +99,9 @@ async def test_method_create(self, client: AsyncOpenAI, respx_mock: MockRouter) model="string", voice="alloy", ) - assert isinstance(speech, BinaryResponseContent) + assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) assert speech.json() == {"foo": "bar"} - @pytest.mark.skip(reason="Mocked tests are currently broken") @parametrize @pytest.mark.respx(base_url=base_url) async def test_method_create_with_all_params(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: @@ -95,20 +113,38 @@ async def test_method_create_with_all_params(self, client: AsyncOpenAI, respx_mo response_format="mp3", speed=0.25, ) - assert isinstance(speech, BinaryResponseContent) + assert isinstance(speech, _legacy_response.HttpxBinaryResponseContent) assert speech.json() == {"foo": "bar"} - @pytest.mark.skip(reason="Mocked tests are currently broken") @parametrize @pytest.mark.respx(base_url=base_url) async def test_raw_response_create(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + response = await client.audio.speech.with_raw_response.create( input="string", model="string", voice="alloy", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" speech = response.parse() - assert isinstance(speech, BinaryResponseContent) - assert speech.json() == {"foo": "bar"} + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, speech, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_create(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.post("/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with client.audio.speech.with_streaming_response.create( + input="string", + model="string", + voice="alloy", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + speech = await response.parse() + assert_matches_type(bytes, speech, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/audio/test_transcriptions.py b/tests/api_resources/audio/test_transcriptions.py index aefdf1790..992adbabd 100644 --- a/tests/api_resources/audio/test_transcriptions.py +++ b/tests/api_resources/audio/test_transcriptions.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -46,10 +47,26 @@ def test_raw_response_create(self, client: OpenAI) -> None: file=b"raw file contents", model="whisper-1", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transcription = response.parse() assert_matches_type(Transcription, transcription, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.audio.transcriptions.with_streaming_response.create( + file=b"raw file contents", + model="whisper-1", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transcription = response.parse() + assert_matches_type(Transcription, transcription, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncTranscriptions: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -82,6 +99,22 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: file=b"raw file contents", model="whisper-1", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" transcription = response.parse() assert_matches_type(Transcription, transcription, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.audio.transcriptions.with_streaming_response.create( + file=b"raw file contents", + model="whisper-1", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + transcription = await response.parse() + assert_matches_type(Transcription, transcription, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/audio/test_translations.py b/tests/api_resources/audio/test_translations.py index 0657e80eb..913c443a7 100644 --- a/tests/api_resources/audio/test_translations.py +++ b/tests/api_resources/audio/test_translations.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -45,10 +46,26 @@ def test_raw_response_create(self, client: OpenAI) -> None: file=b"raw file contents", model="whisper-1", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" translation = response.parse() assert_matches_type(Translation, translation, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.audio.translations.with_streaming_response.create( + file=b"raw file contents", + model="whisper-1", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + translation = response.parse() + assert_matches_type(Translation, translation, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncTranslations: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -80,6 +97,22 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: file=b"raw file contents", model="whisper-1", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" translation = response.parse() assert_matches_type(Translation, translation, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.audio.translations.with_streaming_response.create( + file=b"raw file contents", + model="whisper-1", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + translation = await response.parse() + assert_matches_type(Translation, translation, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/assistants/test_files.py b/tests/api_resources/beta/assistants/test_files.py index 27c12e447..443408bd4 100644 --- a/tests/api_resources/beta/assistants/test_files.py +++ b/tests/api_resources/beta/assistants/test_files.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -35,10 +36,26 @@ def test_raw_response_create(self, client: OpenAI) -> None: "file-abc123", file_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(AssistantFile, file, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.beta.assistants.files.with_streaming_response.create( + "file-abc123", + file_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(AssistantFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: OpenAI) -> None: file = client.beta.assistants.files.retrieve( @@ -53,10 +70,26 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: "string", assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(AssistantFile, file, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.assistants.files.with_streaming_response.retrieve( + "string", + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(AssistantFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: file = client.beta.assistants.files.list( @@ -80,10 +113,25 @@ def test_raw_response_list(self, client: OpenAI) -> None: response = client.beta.assistants.files.with_raw_response.list( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(SyncCursorPage[AssistantFile], file, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.beta.assistants.files.with_streaming_response.list( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(SyncCursorPage[AssistantFile], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_delete(self, client: OpenAI) -> None: file = client.beta.assistants.files.delete( @@ -98,10 +146,26 @@ def test_raw_response_delete(self, client: OpenAI) -> None: "string", assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileDeleteResponse, file, path=["response"]) + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.beta.assistants.files.with_streaming_response.delete( + "string", + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncFiles: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -122,10 +186,26 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: "file-abc123", file_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(AssistantFile, file, path=["response"]) + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.files.with_streaming_response.create( + "file-abc123", + file_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(AssistantFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_retrieve(self, client: AsyncOpenAI) -> None: file = await client.beta.assistants.files.retrieve( @@ -140,10 +220,26 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: "string", assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(AssistantFile, file, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.files.with_streaming_response.retrieve( + "string", + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(AssistantFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: file = await client.beta.assistants.files.list( @@ -167,10 +263,25 @@ async def test_raw_response_list(self, client: AsyncOpenAI) -> None: response = await client.beta.assistants.files.with_raw_response.list( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(AsyncCursorPage[AssistantFile], file, path=["response"]) + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.files.with_streaming_response.list( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(AsyncCursorPage[AssistantFile], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_delete(self, client: AsyncOpenAI) -> None: file = await client.beta.assistants.files.delete( @@ -185,6 +296,22 @@ async def test_raw_response_delete(self, client: AsyncOpenAI) -> None: "string", assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileDeleteResponse, file, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.files.with_streaming_response.delete( + "string", + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileDeleteResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/test_assistants.py b/tests/api_resources/beta/test_assistants.py index 97e74c61e..fbafac03c 100644 --- a/tests/api_resources/beta/test_assistants.py +++ b/tests/api_resources/beta/test_assistants.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -49,10 +50,25 @@ def test_raw_response_create(self, client: OpenAI) -> None: response = client.beta.assistants.with_raw_response.create( model="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(Assistant, assistant, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.beta.assistants.with_streaming_response.create( + model="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: OpenAI) -> None: assistant = client.beta.assistants.retrieve( @@ -65,10 +81,25 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: response = client.beta.assistants.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(Assistant, assistant, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.assistants.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_update(self, client: OpenAI) -> None: assistant = client.beta.assistants.update( @@ -95,10 +126,25 @@ def test_raw_response_update(self, client: OpenAI) -> None: response = client.beta.assistants.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(Assistant, assistant, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.beta.assistants.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: assistant = client.beta.assistants.list() @@ -117,10 +163,23 @@ def test_method_list_with_all_params(self, client: OpenAI) -> None: @parametrize def test_raw_response_list(self, client: OpenAI) -> None: response = client.beta.assistants.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(SyncCursorPage[Assistant], assistant, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.beta.assistants.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = response.parse() + assert_matches_type(SyncCursorPage[Assistant], assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_delete(self, client: OpenAI) -> None: assistant = client.beta.assistants.delete( @@ -133,10 +192,25 @@ def test_raw_response_delete(self, client: OpenAI) -> None: response = client.beta.assistants.with_raw_response.delete( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(AssistantDeleted, assistant, path=["response"]) + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.beta.assistants.with_streaming_response.delete( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = response.parse() + assert_matches_type(AssistantDeleted, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncAssistants: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -168,10 +242,25 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: response = await client.beta.assistants.with_raw_response.create( model="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(Assistant, assistant, path=["response"]) + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.with_streaming_response.create( + model="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = await response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_retrieve(self, client: AsyncOpenAI) -> None: assistant = await client.beta.assistants.retrieve( @@ -184,10 +273,25 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: response = await client.beta.assistants.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(Assistant, assistant, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = await response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_update(self, client: AsyncOpenAI) -> None: assistant = await client.beta.assistants.update( @@ -214,10 +318,25 @@ async def test_raw_response_update(self, client: AsyncOpenAI) -> None: response = await client.beta.assistants.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(Assistant, assistant, path=["response"]) + @parametrize + async def test_streaming_response_update(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = await response.parse() + assert_matches_type(Assistant, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: assistant = await client.beta.assistants.list() @@ -236,10 +355,23 @@ async def test_method_list_with_all_params(self, client: AsyncOpenAI) -> None: @parametrize async def test_raw_response_list(self, client: AsyncOpenAI) -> None: response = await client.beta.assistants.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(AsyncCursorPage[Assistant], assistant, path=["response"]) + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = await response.parse() + assert_matches_type(AsyncCursorPage[Assistant], assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_delete(self, client: AsyncOpenAI) -> None: assistant = await client.beta.assistants.delete( @@ -252,6 +384,21 @@ async def test_raw_response_delete(self, client: AsyncOpenAI) -> None: response = await client.beta.assistants.with_raw_response.delete( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" assistant = response.parse() assert_matches_type(AssistantDeleted, assistant, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, client: AsyncOpenAI) -> None: + async with client.beta.assistants.with_streaming_response.delete( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + assistant = await response.parse() + assert_matches_type(AssistantDeleted, assistant, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/test_threads.py b/tests/api_resources/beta/test_threads.py index 860159ffb..488ce38c1 100644 --- a/tests/api_resources/beta/test_threads.py +++ b/tests/api_resources/beta/test_threads.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -59,10 +60,23 @@ def test_method_create_with_all_params(self, client: OpenAI) -> None: @parametrize def test_raw_response_create(self, client: OpenAI) -> None: response = client.beta.threads.with_raw_response.create() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Thread, thread, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.beta.threads.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(Thread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: OpenAI) -> None: thread = client.beta.threads.retrieve( @@ -75,10 +89,25 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: response = client.beta.threads.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Thread, thread, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.threads.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(Thread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_update(self, client: OpenAI) -> None: thread = client.beta.threads.update( @@ -99,10 +128,25 @@ def test_raw_response_update(self, client: OpenAI) -> None: response = client.beta.threads.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Thread, thread, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.beta.threads.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(Thread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_delete(self, client: OpenAI) -> None: thread = client.beta.threads.delete( @@ -115,10 +159,25 @@ def test_raw_response_delete(self, client: OpenAI) -> None: response = client.beta.threads.with_raw_response.delete( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(ThreadDeleted, thread, path=["response"]) + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.beta.threads.with_streaming_response.delete( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(ThreadDeleted, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_create_and_run(self, client: OpenAI) -> None: thread = client.beta.threads.create_and_run( @@ -165,10 +224,25 @@ def test_raw_response_create_and_run(self, client: OpenAI) -> None: response = client.beta.threads.with_raw_response.create_and_run( assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Run, thread, path=["response"]) + @parametrize + def test_streaming_response_create_and_run(self, client: OpenAI) -> None: + with client.beta.threads.with_streaming_response.create_and_run( + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = response.parse() + assert_matches_type(Run, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncThreads: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -210,10 +284,23 @@ async def test_method_create_with_all_params(self, client: AsyncOpenAI) -> None: @parametrize async def test_raw_response_create(self, client: AsyncOpenAI) -> None: response = await client.beta.threads.with_raw_response.create() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Thread, thread, path=["response"]) + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(Thread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_retrieve(self, client: AsyncOpenAI) -> None: thread = await client.beta.threads.retrieve( @@ -226,10 +313,25 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: response = await client.beta.threads.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Thread, thread, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(Thread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_update(self, client: AsyncOpenAI) -> None: thread = await client.beta.threads.update( @@ -250,10 +352,25 @@ async def test_raw_response_update(self, client: AsyncOpenAI) -> None: response = await client.beta.threads.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Thread, thread, path=["response"]) + @parametrize + async def test_streaming_response_update(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(Thread, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_delete(self, client: AsyncOpenAI) -> None: thread = await client.beta.threads.delete( @@ -266,10 +383,25 @@ async def test_raw_response_delete(self, client: AsyncOpenAI) -> None: response = await client.beta.threads.with_raw_response.delete( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(ThreadDeleted, thread, path=["response"]) + @parametrize + async def test_streaming_response_delete(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.with_streaming_response.delete( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(ThreadDeleted, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_create_and_run(self, client: AsyncOpenAI) -> None: thread = await client.beta.threads.create_and_run( @@ -316,6 +448,21 @@ async def test_raw_response_create_and_run(self, client: AsyncOpenAI) -> None: response = await client.beta.threads.with_raw_response.create_and_run( assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" thread = response.parse() assert_matches_type(Run, thread, path=["response"]) + + @parametrize + async def test_streaming_response_create_and_run(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.with_streaming_response.create_and_run( + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + thread = await response.parse() + assert_matches_type(Run, thread, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/threads/messages/test_files.py b/tests/api_resources/beta/threads/messages/test_files.py index b97e4debd..5de352c0d 100644 --- a/tests/api_resources/beta/threads/messages/test_files.py +++ b/tests/api_resources/beta/threads/messages/test_files.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -37,10 +38,27 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: thread_id="thread_abc123", message_id="msg_abc123", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(MessageFile, file, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.threads.messages.files.with_streaming_response.retrieve( + "file-abc123", + thread_id="thread_abc123", + message_id="msg_abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(MessageFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: file = client.beta.threads.messages.files.list( @@ -67,10 +85,26 @@ def test_raw_response_list(self, client: OpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(SyncCursorPage[MessageFile], file, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.beta.threads.messages.files.with_streaming_response.list( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(SyncCursorPage[MessageFile], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncFiles: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -93,10 +127,27 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: thread_id="thread_abc123", message_id="msg_abc123", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(MessageFile, file, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.messages.files.with_streaming_response.retrieve( + "file-abc123", + thread_id="thread_abc123", + message_id="msg_abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(MessageFile, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: file = await client.beta.threads.messages.files.list( @@ -123,6 +174,22 @@ async def test_raw_response_list(self, client: AsyncOpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(AsyncCursorPage[MessageFile], file, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.messages.files.with_streaming_response.list( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(AsyncCursorPage[MessageFile], file, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/threads/runs/test_steps.py b/tests/api_resources/beta/threads/runs/test_steps.py index 3f4f8c102..f13970fc1 100644 --- a/tests/api_resources/beta/threads/runs/test_steps.py +++ b/tests/api_resources/beta/threads/runs/test_steps.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -37,10 +38,27 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: thread_id="string", run_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" step = response.parse() assert_matches_type(RunStep, step, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.threads.runs.steps.with_streaming_response.retrieve( + "string", + thread_id="string", + run_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + step = response.parse() + assert_matches_type(RunStep, step, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: step = client.beta.threads.runs.steps.list( @@ -67,10 +85,26 @@ def test_raw_response_list(self, client: OpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" step = response.parse() assert_matches_type(SyncCursorPage[RunStep], step, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.beta.threads.runs.steps.with_streaming_response.list( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + step = response.parse() + assert_matches_type(SyncCursorPage[RunStep], step, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncSteps: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -93,10 +127,27 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: thread_id="string", run_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" step = response.parse() assert_matches_type(RunStep, step, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.steps.with_streaming_response.retrieve( + "string", + thread_id="string", + run_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + step = await response.parse() + assert_matches_type(RunStep, step, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: step = await client.beta.threads.runs.steps.list( @@ -123,6 +174,22 @@ async def test_raw_response_list(self, client: AsyncOpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" step = response.parse() assert_matches_type(AsyncCursorPage[RunStep], step, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.steps.with_streaming_response.list( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + step = await response.parse() + assert_matches_type(AsyncCursorPage[RunStep], step, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/threads/test_messages.py b/tests/api_resources/beta/threads/test_messages.py index f3fe7dc2b..87b6eca03 100644 --- a/tests/api_resources/beta/threads/test_messages.py +++ b/tests/api_resources/beta/threads/test_messages.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -48,10 +49,27 @@ def test_raw_response_create(self, client: OpenAI) -> None: content="x", role="user", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(ThreadMessage, message, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.beta.threads.messages.with_streaming_response.create( + "string", + content="x", + role="user", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(ThreadMessage, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: OpenAI) -> None: message = client.beta.threads.messages.retrieve( @@ -66,10 +84,26 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(ThreadMessage, message, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.threads.messages.with_streaming_response.retrieve( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(ThreadMessage, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_update(self, client: OpenAI) -> None: message = client.beta.threads.messages.update( @@ -93,10 +127,26 @@ def test_raw_response_update(self, client: OpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(ThreadMessage, message, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.beta.threads.messages.with_streaming_response.update( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(ThreadMessage, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: message = client.beta.threads.messages.list( @@ -120,10 +170,25 @@ def test_raw_response_list(self, client: OpenAI) -> None: response = client.beta.threads.messages.with_raw_response.list( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(SyncCursorPage[ThreadMessage], message, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.beta.threads.messages.with_streaming_response.list( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(SyncCursorPage[ThreadMessage], message, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncMessages: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -157,10 +222,27 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: content="x", role="user", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(ThreadMessage, message, path=["response"]) + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.messages.with_streaming_response.create( + "string", + content="x", + role="user", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(ThreadMessage, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_retrieve(self, client: AsyncOpenAI) -> None: message = await client.beta.threads.messages.retrieve( @@ -175,10 +257,26 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(ThreadMessage, message, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.messages.with_streaming_response.retrieve( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(ThreadMessage, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_update(self, client: AsyncOpenAI) -> None: message = await client.beta.threads.messages.update( @@ -202,10 +300,26 @@ async def test_raw_response_update(self, client: AsyncOpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(ThreadMessage, message, path=["response"]) + @parametrize + async def test_streaming_response_update(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.messages.with_streaming_response.update( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(ThreadMessage, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: message = await client.beta.threads.messages.list( @@ -229,6 +343,21 @@ async def test_raw_response_list(self, client: AsyncOpenAI) -> None: response = await client.beta.threads.messages.with_raw_response.list( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" message = response.parse() assert_matches_type(AsyncCursorPage[ThreadMessage], message, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.messages.with_streaming_response.list( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(AsyncCursorPage[ThreadMessage], message, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/beta/threads/test_runs.py b/tests/api_resources/beta/threads/test_runs.py index 9d04a95c8..e0070c339 100644 --- a/tests/api_resources/beta/threads/test_runs.py +++ b/tests/api_resources/beta/threads/test_runs.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -50,10 +51,26 @@ def test_raw_response_create(self, client: OpenAI) -> None: "string", assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.beta.threads.runs.with_streaming_response.create( + "string", + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: OpenAI) -> None: run = client.beta.threads.runs.retrieve( @@ -68,10 +85,26 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.beta.threads.runs.with_streaming_response.retrieve( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_update(self, client: OpenAI) -> None: run = client.beta.threads.runs.update( @@ -95,10 +128,26 @@ def test_raw_response_update(self, client: OpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: OpenAI) -> None: + with client.beta.threads.runs.with_streaming_response.update( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: run = client.beta.threads.runs.list( @@ -122,10 +171,25 @@ def test_raw_response_list(self, client: OpenAI) -> None: response = client.beta.threads.runs.with_raw_response.list( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(SyncCursorPage[Run], run, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.beta.threads.runs.with_streaming_response.list( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(SyncCursorPage[Run], run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_cancel(self, client: OpenAI) -> None: run = client.beta.threads.runs.cancel( @@ -140,10 +204,26 @@ def test_raw_response_cancel(self, client: OpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + def test_streaming_response_cancel(self, client: OpenAI) -> None: + with client.beta.threads.runs.with_streaming_response.cancel( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_submit_tool_outputs(self, client: OpenAI) -> None: run = client.beta.threads.runs.submit_tool_outputs( @@ -160,10 +240,27 @@ def test_raw_response_submit_tool_outputs(self, client: OpenAI) -> None: thread_id="string", tool_outputs=[{}, {}, {}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + def test_streaming_response_submit_tool_outputs(self, client: OpenAI) -> None: + with client.beta.threads.runs.with_streaming_response.submit_tool_outputs( + "string", + thread_id="string", + tool_outputs=[{}, {}, {}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncRuns: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -197,10 +294,26 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: "string", assistant_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.with_streaming_response.create( + "string", + assistant_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_retrieve(self, client: AsyncOpenAI) -> None: run = await client.beta.threads.runs.retrieve( @@ -215,10 +328,26 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.with_streaming_response.retrieve( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_update(self, client: AsyncOpenAI) -> None: run = await client.beta.threads.runs.update( @@ -242,10 +371,26 @@ async def test_raw_response_update(self, client: AsyncOpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + async def test_streaming_response_update(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.with_streaming_response.update( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: run = await client.beta.threads.runs.list( @@ -269,10 +414,25 @@ async def test_raw_response_list(self, client: AsyncOpenAI) -> None: response = await client.beta.threads.runs.with_raw_response.list( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(AsyncCursorPage[Run], run, path=["response"]) + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.with_streaming_response.list( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(AsyncCursorPage[Run], run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_cancel(self, client: AsyncOpenAI) -> None: run = await client.beta.threads.runs.cancel( @@ -287,10 +447,26 @@ async def test_raw_response_cancel(self, client: AsyncOpenAI) -> None: "string", thread_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + @parametrize + async def test_streaming_response_cancel(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.with_streaming_response.cancel( + "string", + thread_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_submit_tool_outputs(self, client: AsyncOpenAI) -> None: run = await client.beta.threads.runs.submit_tool_outputs( @@ -307,6 +483,23 @@ async def test_raw_response_submit_tool_outputs(self, client: AsyncOpenAI) -> No thread_id="string", tool_outputs=[{}, {}, {}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" run = response.parse() assert_matches_type(Run, run, path=["response"]) + + @parametrize + async def test_streaming_response_submit_tool_outputs(self, client: AsyncOpenAI) -> None: + async with client.beta.threads.runs.with_streaming_response.submit_tool_outputs( + "string", + thread_id="string", + tool_outputs=[{}, {}, {}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(Run, run, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/chat/test_completions.py b/tests/api_resources/chat/test_completions.py index 985d5f1c0..860ec80f4 100644 --- a/tests/api_resources/chat/test_completions.py +++ b/tests/api_resources/chat/test_completions.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -107,13 +108,34 @@ def test_raw_response_create_overload_1(self, client: OpenAI) -> None: ], model="gpt-3.5-turbo", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = response.parse() assert_matches_type(ChatCompletion, completion, path=["response"]) + @parametrize + def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: + with client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "system", + } + ], + model="gpt-3.5-turbo", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_create_overload_2(self, client: OpenAI) -> None: - client.chat.completions.create( + completion_stream = client.chat.completions.create( messages=[ { "content": "string", @@ -123,10 +145,11 @@ def test_method_create_overload_2(self, client: OpenAI) -> None: model="gpt-3.5-turbo", stream=True, ) + completion_stream.response.close() @parametrize def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: - client.chat.completions.create( + completion_stream = client.chat.completions.create( messages=[ { "content": "string", @@ -185,6 +208,7 @@ def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: top_p=1, user="user-1234", ) + completion_stream.response.close() @parametrize def test_raw_response_create_overload_2(self, client: OpenAI) -> None: @@ -198,8 +222,30 @@ def test_raw_response_create_overload_2(self, client: OpenAI) -> None: model="gpt-3.5-turbo", stream=True, ) + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - response.parse() + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: + with client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "system", + } + ], + model="gpt-3.5-turbo", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True class TestAsyncCompletions: @@ -294,13 +340,34 @@ async def test_raw_response_create_overload_1(self, client: AsyncOpenAI) -> None ], model="gpt-3.5-turbo", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = response.parse() assert_matches_type(ChatCompletion, completion, path=["response"]) + @parametrize + async def test_streaming_response_create_overload_1(self, client: AsyncOpenAI) -> None: + async with client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "system", + } + ], + model="gpt-3.5-turbo", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = await response.parse() + assert_matches_type(ChatCompletion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_create_overload_2(self, client: AsyncOpenAI) -> None: - await client.chat.completions.create( + completion_stream = await client.chat.completions.create( messages=[ { "content": "string", @@ -310,10 +377,11 @@ async def test_method_create_overload_2(self, client: AsyncOpenAI) -> None: model="gpt-3.5-turbo", stream=True, ) + await completion_stream.response.aclose() @parametrize async def test_method_create_with_all_params_overload_2(self, client: AsyncOpenAI) -> None: - await client.chat.completions.create( + completion_stream = await client.chat.completions.create( messages=[ { "content": "string", @@ -372,6 +440,7 @@ async def test_method_create_with_all_params_overload_2(self, client: AsyncOpenA top_p=1, user="user-1234", ) + await completion_stream.response.aclose() @parametrize async def test_raw_response_create_overload_2(self, client: AsyncOpenAI) -> None: @@ -385,5 +454,27 @@ async def test_raw_response_create_overload_2(self, client: AsyncOpenAI) -> None model="gpt-3.5-turbo", stream=True, ) + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - response.parse() + stream = response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_create_overload_2(self, client: AsyncOpenAI) -> None: + async with client.chat.completions.with_streaming_response.create( + messages=[ + { + "content": "string", + "role": "system", + } + ], + model="gpt-3.5-turbo", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/fine_tuning/test_jobs.py b/tests/api_resources/fine_tuning/test_jobs.py index 927ca9bbd..3db0cdc0a 100644 --- a/tests/api_resources/fine_tuning/test_jobs.py +++ b/tests/api_resources/fine_tuning/test_jobs.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -53,10 +54,26 @@ def test_raw_response_create(self, client: OpenAI) -> None: model="gpt-3.5-turbo", training_file="file-abc123", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(FineTuningJob, job, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.fine_tuning.jobs.with_streaming_response.create( + model="gpt-3.5-turbo", + training_file="file-abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: OpenAI) -> None: job = client.fine_tuning.jobs.retrieve( @@ -69,10 +86,25 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: response = client.fine_tuning.jobs.with_raw_response.retrieve( "ft-AF1WoRqd3aJAHsqc9NY7iL8F", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(FineTuningJob, job, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.fine_tuning.jobs.with_streaming_response.retrieve( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: job = client.fine_tuning.jobs.list() @@ -89,10 +121,23 @@ def test_method_list_with_all_params(self, client: OpenAI) -> None: @parametrize def test_raw_response_list(self, client: OpenAI) -> None: response = client.fine_tuning.jobs.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(SyncCursorPage[FineTuningJob], job, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.fine_tuning.jobs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(SyncCursorPage[FineTuningJob], job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_cancel(self, client: OpenAI) -> None: job = client.fine_tuning.jobs.cancel( @@ -105,10 +150,25 @@ def test_raw_response_cancel(self, client: OpenAI) -> None: response = client.fine_tuning.jobs.with_raw_response.cancel( "ft-AF1WoRqd3aJAHsqc9NY7iL8F", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(FineTuningJob, job, path=["response"]) + @parametrize + def test_streaming_response_cancel(self, client: OpenAI) -> None: + with client.fine_tuning.jobs.with_streaming_response.cancel( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list_events(self, client: OpenAI) -> None: job = client.fine_tuning.jobs.list_events( @@ -130,10 +190,25 @@ def test_raw_response_list_events(self, client: OpenAI) -> None: response = client.fine_tuning.jobs.with_raw_response.list_events( "ft-AF1WoRqd3aJAHsqc9NY7iL8F", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(SyncCursorPage[FineTuningJobEvent], job, path=["response"]) + @parametrize + def test_streaming_response_list_events(self, client: OpenAI) -> None: + with client.fine_tuning.jobs.with_streaming_response.list_events( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = response.parse() + assert_matches_type(SyncCursorPage[FineTuningJobEvent], job, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncJobs: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -169,10 +244,26 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: model="gpt-3.5-turbo", training_file="file-abc123", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(FineTuningJob, job, path=["response"]) + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.fine_tuning.jobs.with_streaming_response.create( + model="gpt-3.5-turbo", + training_file="file-abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_retrieve(self, client: AsyncOpenAI) -> None: job = await client.fine_tuning.jobs.retrieve( @@ -185,10 +276,25 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: response = await client.fine_tuning.jobs.with_raw_response.retrieve( "ft-AF1WoRqd3aJAHsqc9NY7iL8F", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(FineTuningJob, job, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.fine_tuning.jobs.with_streaming_response.retrieve( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: job = await client.fine_tuning.jobs.list() @@ -205,10 +311,23 @@ async def test_method_list_with_all_params(self, client: AsyncOpenAI) -> None: @parametrize async def test_raw_response_list(self, client: AsyncOpenAI) -> None: response = await client.fine_tuning.jobs.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(AsyncCursorPage[FineTuningJob], job, path=["response"]) + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.fine_tuning.jobs.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(AsyncCursorPage[FineTuningJob], job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_cancel(self, client: AsyncOpenAI) -> None: job = await client.fine_tuning.jobs.cancel( @@ -221,10 +340,25 @@ async def test_raw_response_cancel(self, client: AsyncOpenAI) -> None: response = await client.fine_tuning.jobs.with_raw_response.cancel( "ft-AF1WoRqd3aJAHsqc9NY7iL8F", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(FineTuningJob, job, path=["response"]) + @parametrize + async def test_streaming_response_cancel(self, client: AsyncOpenAI) -> None: + async with client.fine_tuning.jobs.with_streaming_response.cancel( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(FineTuningJob, job, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list_events(self, client: AsyncOpenAI) -> None: job = await client.fine_tuning.jobs.list_events( @@ -246,6 +380,21 @@ async def test_raw_response_list_events(self, client: AsyncOpenAI) -> None: response = await client.fine_tuning.jobs.with_raw_response.list_events( "ft-AF1WoRqd3aJAHsqc9NY7iL8F", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" job = response.parse() assert_matches_type(AsyncCursorPage[FineTuningJobEvent], job, path=["response"]) + + @parametrize + async def test_streaming_response_list_events(self, client: AsyncOpenAI) -> None: + async with client.fine_tuning.jobs.with_streaming_response.list_events( + "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + job = await response.parse() + assert_matches_type(AsyncCursorPage[FineTuningJobEvent], job, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_completions.py b/tests/api_resources/test_completions.py index b12fd6401..a5e8dc809 100644 --- a/tests/api_resources/test_completions.py +++ b/tests/api_resources/test_completions.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -57,21 +58,38 @@ def test_raw_response_create_overload_1(self, client: OpenAI) -> None: model="string", prompt="This is a test.", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = response.parse() assert_matches_type(Completion, completion, path=["response"]) + @parametrize + def test_streaming_response_create_overload_1(self, client: OpenAI) -> None: + with client.completions.with_streaming_response.create( + model="string", + prompt="This is a test.", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = response.parse() + assert_matches_type(Completion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_create_overload_2(self, client: OpenAI) -> None: - client.completions.create( + completion_stream = client.completions.create( model="string", prompt="This is a test.", stream=True, ) + completion_stream.response.close() @parametrize def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: - client.completions.create( + completion_stream = client.completions.create( model="string", prompt="This is a test.", stream=True, @@ -90,6 +108,7 @@ def test_method_create_with_all_params_overload_2(self, client: OpenAI) -> None: top_p=1, user="user-1234", ) + completion_stream.response.close() @parametrize def test_raw_response_create_overload_2(self, client: OpenAI) -> None: @@ -98,8 +117,25 @@ def test_raw_response_create_overload_2(self, client: OpenAI) -> None: prompt="This is a test.", stream=True, ) + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - response.parse() + stream = response.parse() + stream.close() + + @parametrize + def test_streaming_response_create_overload_2(self, client: OpenAI) -> None: + with client.completions.with_streaming_response.create( + model="string", + prompt="This is a test.", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = response.parse() + stream.close() + + assert cast(Any, response.is_closed) is True class TestAsyncCompletions: @@ -144,21 +180,38 @@ async def test_raw_response_create_overload_1(self, client: AsyncOpenAI) -> None model="string", prompt="This is a test.", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = response.parse() assert_matches_type(Completion, completion, path=["response"]) + @parametrize + async def test_streaming_response_create_overload_1(self, client: AsyncOpenAI) -> None: + async with client.completions.with_streaming_response.create( + model="string", + prompt="This is a test.", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + completion = await response.parse() + assert_matches_type(Completion, completion, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_create_overload_2(self, client: AsyncOpenAI) -> None: - await client.completions.create( + completion_stream = await client.completions.create( model="string", prompt="This is a test.", stream=True, ) + await completion_stream.response.aclose() @parametrize async def test_method_create_with_all_params_overload_2(self, client: AsyncOpenAI) -> None: - await client.completions.create( + completion_stream = await client.completions.create( model="string", prompt="This is a test.", stream=True, @@ -177,6 +230,7 @@ async def test_method_create_with_all_params_overload_2(self, client: AsyncOpenA top_p=1, user="user-1234", ) + await completion_stream.response.aclose() @parametrize async def test_raw_response_create_overload_2(self, client: AsyncOpenAI) -> None: @@ -185,5 +239,22 @@ async def test_raw_response_create_overload_2(self, client: AsyncOpenAI) -> None prompt="This is a test.", stream=True, ) + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - response.parse() + stream = response.parse() + await stream.close() + + @parametrize + async def test_streaming_response_create_overload_2(self, client: AsyncOpenAI) -> None: + async with client.completions.with_streaming_response.create( + model="string", + prompt="This is a test.", + stream=True, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stream = await response.parse() + await stream.close() + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_embeddings.py b/tests/api_resources/test_embeddings.py index faf07ffb7..77875fc46 100644 --- a/tests/api_resources/test_embeddings.py +++ b/tests/api_resources/test_embeddings.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -44,10 +45,26 @@ def test_raw_response_create(self, client: OpenAI) -> None: input="The quick brown fox jumped over the lazy dog", model="text-embedding-ada-002", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() assert_matches_type(CreateEmbeddingResponse, embedding, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.embeddings.with_streaming_response.create( + input="The quick brown fox jumped over the lazy dog", + model="text-embedding-ada-002", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + embedding = response.parse() + assert_matches_type(CreateEmbeddingResponse, embedding, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncEmbeddings: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -78,6 +95,22 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: input="The quick brown fox jumped over the lazy dog", model="text-embedding-ada-002", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" embedding = response.parse() assert_matches_type(CreateEmbeddingResponse, embedding, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.embeddings.with_streaming_response.create( + input="The quick brown fox jumped over the lazy dog", + model="text-embedding-ada-002", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + embedding = await response.parse() + assert_matches_type(CreateEmbeddingResponse, embedding, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index 13ffca977..e36a7839f 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -3,15 +3,16 @@ from __future__ import annotations import os +from typing import Any, cast import httpx import pytest from respx import MockRouter +import openai._legacy_response as _legacy_response from openai import OpenAI, AsyncOpenAI from tests.utils import assert_matches_type from openai.types import FileObject, FileDeleted -from openai._types import BinaryResponseContent from openai._client import OpenAI, AsyncOpenAI from openai.pagination import SyncPage, AsyncPage @@ -40,10 +41,26 @@ def test_raw_response_create(self, client: OpenAI) -> None: file=b"raw file contents", purpose="fine-tune", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileObject, file, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.files.with_streaming_response.create( + file=b"raw file contents", + purpose="fine-tune", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileObject, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: OpenAI) -> None: file = client.files.retrieve( @@ -56,10 +73,25 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: response = client.files.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileObject, file, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.files.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileObject, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: file = client.files.list() @@ -75,10 +107,23 @@ def test_method_list_with_all_params(self, client: OpenAI) -> None: @parametrize def test_raw_response_list(self, client: OpenAI) -> None: response = client.files.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(SyncPage[FileObject], file, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.files.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(SyncPage[FileObject], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_delete(self, client: OpenAI) -> None: file = client.files.delete( @@ -91,10 +136,25 @@ def test_raw_response_delete(self, client: OpenAI) -> None: response = client.files.with_raw_response.delete( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileDeleted, file, path=["response"]) + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.files.with_streaming_response.delete( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileDeleted, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize @pytest.mark.respx(base_url=base_url) def test_method_content(self, client: OpenAI, respx_mock: MockRouter) -> None: @@ -102,20 +162,37 @@ def test_method_content(self, client: OpenAI, respx_mock: MockRouter) -> None: file = client.files.content( "string", ) - assert isinstance(file, BinaryResponseContent) + assert isinstance(file, _legacy_response.HttpxBinaryResponseContent) assert file.json() == {"foo": "bar"} @parametrize @pytest.mark.respx(base_url=base_url) def test_raw_response_content(self, client: OpenAI, respx_mock: MockRouter) -> None: respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + response = client.files.with_raw_response.content( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert isinstance(file, BinaryResponseContent) - assert file.json() == {"foo": "bar"} + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, file, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_content(self, client: OpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.files.with_streaming_response.content( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(bytes, file, path=["response"]) + + assert cast(Any, response.is_closed) is True @parametrize def test_method_retrieve_content(self, client: OpenAI) -> None: @@ -123,6 +200,7 @@ def test_method_retrieve_content(self, client: OpenAI) -> None: file = client.files.retrieve_content( "string", ) + assert_matches_type(str, file, path=["response"]) @parametrize @@ -131,10 +209,26 @@ def test_raw_response_retrieve_content(self, client: OpenAI) -> None: response = client.files.with_raw_response.retrieve_content( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(str, file, path=["response"]) + @parametrize + def test_streaming_response_retrieve_content(self, client: OpenAI) -> None: + with pytest.warns(DeprecationWarning): + with client.files.with_streaming_response.retrieve_content( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(str, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncFiles: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -155,10 +249,26 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: file=b"raw file contents", purpose="fine-tune", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileObject, file, path=["response"]) + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.files.with_streaming_response.create( + file=b"raw file contents", + purpose="fine-tune", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileObject, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_retrieve(self, client: AsyncOpenAI) -> None: file = await client.files.retrieve( @@ -171,10 +281,25 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: response = await client.files.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileObject, file, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.files.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileObject, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: file = await client.files.list() @@ -190,10 +315,23 @@ async def test_method_list_with_all_params(self, client: AsyncOpenAI) -> None: @parametrize async def test_raw_response_list(self, client: AsyncOpenAI) -> None: response = await client.files.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(AsyncPage[FileObject], file, path=["response"]) + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.files.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(AsyncPage[FileObject], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_delete(self, client: AsyncOpenAI) -> None: file = await client.files.delete( @@ -206,10 +344,25 @@ async def test_raw_response_delete(self, client: AsyncOpenAI) -> None: response = await client.files.with_raw_response.delete( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(FileDeleted, file, path=["response"]) + @parametrize + async def test_streaming_response_delete(self, client: AsyncOpenAI) -> None: + async with client.files.with_streaming_response.delete( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileDeleted, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize @pytest.mark.respx(base_url=base_url) async def test_method_content(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: @@ -217,20 +370,37 @@ async def test_method_content(self, client: AsyncOpenAI, respx_mock: MockRouter) file = await client.files.content( "string", ) - assert isinstance(file, BinaryResponseContent) + assert isinstance(file, _legacy_response.HttpxBinaryResponseContent) assert file.json() == {"foo": "bar"} @parametrize @pytest.mark.respx(base_url=base_url) async def test_raw_response_content(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + response = await client.files.with_raw_response.content( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() - assert isinstance(file, BinaryResponseContent) - assert file.json() == {"foo": "bar"} + assert_matches_type(_legacy_response.HttpxBinaryResponseContent, file, path=["response"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_content(self, client: AsyncOpenAI, respx_mock: MockRouter) -> None: + respx_mock.get("/files/string/content").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with client.files.with_streaming_response.content( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(bytes, file, path=["response"]) + + assert cast(Any, response.is_closed) is True @parametrize async def test_method_retrieve_content(self, client: AsyncOpenAI) -> None: @@ -238,6 +408,7 @@ async def test_method_retrieve_content(self, client: AsyncOpenAI) -> None: file = await client.files.retrieve_content( "string", ) + assert_matches_type(str, file, path=["response"]) @parametrize @@ -246,6 +417,22 @@ async def test_raw_response_retrieve_content(self, client: AsyncOpenAI) -> None: response = await client.files.with_raw_response.retrieve_content( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" file = response.parse() assert_matches_type(str, file, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_content(self, client: AsyncOpenAI) -> None: + with pytest.warns(DeprecationWarning): + async with client.files.with_streaming_response.retrieve_content( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(str, file, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_images.py b/tests/api_resources/test_images.py index c7f5e5bcd..553bd018e 100644 --- a/tests/api_resources/test_images.py +++ b/tests/api_resources/test_images.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -44,10 +45,25 @@ def test_raw_response_create_variation(self, client: OpenAI) -> None: response = client.images.with_raw_response.create_variation( image=b"raw file contents", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() assert_matches_type(ImagesResponse, image, path=["response"]) + @parametrize + def test_streaming_response_create_variation(self, client: OpenAI) -> None: + with client.images.with_streaming_response.create_variation( + image=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + image = response.parse() + assert_matches_type(ImagesResponse, image, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_edit(self, client: OpenAI) -> None: image = client.images.edit( @@ -76,10 +92,26 @@ def test_raw_response_edit(self, client: OpenAI) -> None: image=b"raw file contents", prompt="A cute baby sea otter wearing a beret", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() assert_matches_type(ImagesResponse, image, path=["response"]) + @parametrize + def test_streaming_response_edit(self, client: OpenAI) -> None: + with client.images.with_streaming_response.edit( + image=b"raw file contents", + prompt="A cute baby sea otter wearing a beret", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + image = response.parse() + assert_matches_type(ImagesResponse, image, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_generate(self, client: OpenAI) -> None: image = client.images.generate( @@ -106,10 +138,25 @@ def test_raw_response_generate(self, client: OpenAI) -> None: response = client.images.with_raw_response.generate( prompt="A cute baby sea otter", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() assert_matches_type(ImagesResponse, image, path=["response"]) + @parametrize + def test_streaming_response_generate(self, client: OpenAI) -> None: + with client.images.with_streaming_response.generate( + prompt="A cute baby sea otter", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + image = response.parse() + assert_matches_type(ImagesResponse, image, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncImages: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -140,10 +187,25 @@ async def test_raw_response_create_variation(self, client: AsyncOpenAI) -> None: response = await client.images.with_raw_response.create_variation( image=b"raw file contents", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() assert_matches_type(ImagesResponse, image, path=["response"]) + @parametrize + async def test_streaming_response_create_variation(self, client: AsyncOpenAI) -> None: + async with client.images.with_streaming_response.create_variation( + image=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + image = await response.parse() + assert_matches_type(ImagesResponse, image, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_edit(self, client: AsyncOpenAI) -> None: image = await client.images.edit( @@ -172,10 +234,26 @@ async def test_raw_response_edit(self, client: AsyncOpenAI) -> None: image=b"raw file contents", prompt="A cute baby sea otter wearing a beret", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() assert_matches_type(ImagesResponse, image, path=["response"]) + @parametrize + async def test_streaming_response_edit(self, client: AsyncOpenAI) -> None: + async with client.images.with_streaming_response.edit( + image=b"raw file contents", + prompt="A cute baby sea otter wearing a beret", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + image = await response.parse() + assert_matches_type(ImagesResponse, image, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_generate(self, client: AsyncOpenAI) -> None: image = await client.images.generate( @@ -202,6 +280,21 @@ async def test_raw_response_generate(self, client: AsyncOpenAI) -> None: response = await client.images.with_raw_response.generate( prompt="A cute baby sea otter", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() assert_matches_type(ImagesResponse, image, path=["response"]) + + @parametrize + async def test_streaming_response_generate(self, client: AsyncOpenAI) -> None: + async with client.images.with_streaming_response.generate( + prompt="A cute baby sea otter", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + image = await response.parse() + assert_matches_type(ImagesResponse, image, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_models.py b/tests/api_resources/test_models.py index 399880961..5afda86a7 100644 --- a/tests/api_resources/test_models.py +++ b/tests/api_resources/test_models.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -33,10 +34,25 @@ def test_raw_response_retrieve(self, client: OpenAI) -> None: response = client.models.with_raw_response.retrieve( "gpt-3.5-turbo", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" model = response.parse() assert_matches_type(Model, model, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.models.with_streaming_response.retrieve( + "gpt-3.5-turbo", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model = response.parse() + assert_matches_type(Model, model, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list(self, client: OpenAI) -> None: model = client.models.list() @@ -45,10 +61,23 @@ def test_method_list(self, client: OpenAI) -> None: @parametrize def test_raw_response_list(self, client: OpenAI) -> None: response = client.models.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" model = response.parse() assert_matches_type(SyncPage[Model], model, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: OpenAI) -> None: + with client.models.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model = response.parse() + assert_matches_type(SyncPage[Model], model, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_delete(self, client: OpenAI) -> None: model = client.models.delete( @@ -61,10 +90,25 @@ def test_raw_response_delete(self, client: OpenAI) -> None: response = client.models.with_raw_response.delete( "ft:gpt-3.5-turbo:acemeco:suffix:abc123", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" model = response.parse() assert_matches_type(ModelDeleted, model, path=["response"]) + @parametrize + def test_streaming_response_delete(self, client: OpenAI) -> None: + with client.models.with_streaming_response.delete( + "ft:gpt-3.5-turbo:acemeco:suffix:abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model = response.parse() + assert_matches_type(ModelDeleted, model, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncModels: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -83,10 +127,25 @@ async def test_raw_response_retrieve(self, client: AsyncOpenAI) -> None: response = await client.models.with_raw_response.retrieve( "gpt-3.5-turbo", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" model = response.parse() assert_matches_type(Model, model, path=["response"]) + @parametrize + async def test_streaming_response_retrieve(self, client: AsyncOpenAI) -> None: + async with client.models.with_streaming_response.retrieve( + "gpt-3.5-turbo", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model = await response.parse() + assert_matches_type(Model, model, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_list(self, client: AsyncOpenAI) -> None: model = await client.models.list() @@ -95,10 +154,23 @@ async def test_method_list(self, client: AsyncOpenAI) -> None: @parametrize async def test_raw_response_list(self, client: AsyncOpenAI) -> None: response = await client.models.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" model = response.parse() assert_matches_type(AsyncPage[Model], model, path=["response"]) + @parametrize + async def test_streaming_response_list(self, client: AsyncOpenAI) -> None: + async with client.models.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model = await response.parse() + assert_matches_type(AsyncPage[Model], model, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize async def test_method_delete(self, client: AsyncOpenAI) -> None: model = await client.models.delete( @@ -111,6 +183,21 @@ async def test_raw_response_delete(self, client: AsyncOpenAI) -> None: response = await client.models.with_raw_response.delete( "ft:gpt-3.5-turbo:acemeco:suffix:abc123", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" model = response.parse() assert_matches_type(ModelDeleted, model, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, client: AsyncOpenAI) -> None: + async with client.models.with_streaming_response.delete( + "ft:gpt-3.5-turbo:acemeco:suffix:abc123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + model = await response.parse() + assert_matches_type(ModelDeleted, model, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_moderations.py b/tests/api_resources/test_moderations.py index 502030d61..88d35f003 100644 --- a/tests/api_resources/test_moderations.py +++ b/tests/api_resources/test_moderations.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +from typing import Any, cast import pytest @@ -40,10 +41,25 @@ def test_raw_response_create(self, client: OpenAI) -> None: response = client.moderations.with_raw_response.create( input="I want to kill them.", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" moderation = response.parse() assert_matches_type(ModerationCreateResponse, moderation, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.moderations.with_streaming_response.create( + input="I want to kill them.", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + moderation = response.parse() + assert_matches_type(ModerationCreateResponse, moderation, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncModerations: strict_client = AsyncOpenAI(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -70,6 +86,21 @@ async def test_raw_response_create(self, client: AsyncOpenAI) -> None: response = await client.moderations.with_raw_response.create( input="I want to kill them.", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" moderation = response.parse() assert_matches_type(ModerationCreateResponse, moderation, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, client: AsyncOpenAI) -> None: + async with client.moderations.with_streaming_response.create( + input="I want to kill them.", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + moderation = await response.parse() + assert_matches_type(ModerationCreateResponse, moderation, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index c49e4d629..7aa473fe9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -19,6 +19,8 @@ from openai import OpenAI, AsyncOpenAI, APIResponseValidationError from openai._client import OpenAI, AsyncOpenAI from openai._models import BaseModel, FinalRequestOptions +from openai._response import APIResponse, AsyncAPIResponse +from openai._constants import RAW_RESPONSE_HEADER from openai._streaming import Stream, AsyncStream from openai._exceptions import OpenAIError, APIStatusError, APITimeoutError, APIResponseValidationError from openai._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options @@ -220,6 +222,7 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic # to_raw_response_wrapper leaks through the @functools.wraps() decorator. # # removing the decorator fixes the leak for reasons we don't understand. + "openai/_legacy_response.py", "openai/_response.py", # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. "openai/_compat.py", @@ -612,8 +615,9 @@ class Model(BaseModel): respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = self.client.post("/foo", cast_to=Model, stream=True) - assert isinstance(response, Stream) + stream = self.client.post("/foo", cast_to=Model, stream=True, stream_cls=Stream[Model]) + assert isinstance(stream, Stream) + stream.response.close() @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -661,6 +665,33 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str calculated = client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + def test_streaming_response(self) -> None: + response = self.client.post( + "/chat/completions", + body=dict( + messages=[ + { + "role": "user", + "content": "Say this is a test", + } + ], + model="gpt-3.5-turbo", + ), + cast_to=APIResponse[bytes], + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + ) + + assert not cast(Any, response.is_closed) + assert _get_open_connections(self.client) == 1 + + for _ in response.iter_bytes(): + ... + + assert cast(Any, response.is_closed) + assert _get_open_connections(self.client) == 0 + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: @@ -679,7 +710,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No model="gpt-3.5-turbo", ), cast_to=httpx.Response, - options={"headers": {"X-Stainless-Streamed-Raw-Response": "true"}}, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 @@ -702,7 +733,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non model="gpt-3.5-turbo", ), cast_to=httpx.Response, - options={"headers": {"X-Stainless-Streamed-Raw-Response": "true"}}, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 @@ -883,6 +914,7 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic # to_raw_response_wrapper leaks through the @functools.wraps() decorator. # # removing the decorator fixes the leak for reasons we don't understand. + "openai/_legacy_response.py", "openai/_response.py", # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. "openai/_compat.py", @@ -1288,8 +1320,9 @@ class Model(BaseModel): respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - response = await self.client.post("/foo", cast_to=Model, stream=True) - assert isinstance(response, AsyncStream) + stream = await self.client.post("/foo", cast_to=Model, stream=True, stream_cls=AsyncStream[Model]) + assert isinstance(stream, AsyncStream) + await stream.response.aclose() @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -1339,6 +1372,33 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte calculated = client._calculate_retry_timeout(remaining_retries, options, headers) assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response(self) -> None: + response = await self.client.post( + "/chat/completions", + body=dict( + messages=[ + { + "role": "user", + "content": "Say this is a test", + } + ], + model="gpt-3.5-turbo", + ), + cast_to=AsyncAPIResponse[bytes], + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + ) + + assert not cast(Any, response.is_closed) + assert _get_open_connections(self.client) == 1 + + async for _ in response.iter_bytes(): + ... + + assert cast(Any, response.is_closed) + assert _get_open_connections(self.client) == 0 + @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: @@ -1357,7 +1417,7 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) model="gpt-3.5-turbo", ), cast_to=httpx.Response, - options={"headers": {"X-Stainless-Streamed-Raw-Response": "true"}}, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 @@ -1380,7 +1440,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) model="gpt-3.5-turbo", ), cast_to=httpx.Response, - options={"headers": {"X-Stainless-Streamed-Raw-Response": "true"}}, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 diff --git a/tests/test_response.py b/tests/test_response.py new file mode 100644 index 000000000..335ca7922 --- /dev/null +++ b/tests/test_response.py @@ -0,0 +1,50 @@ +from typing import List + +import httpx +import pytest + +from openai._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + BinaryAPIResponse, + AsyncBinaryAPIResponse, + extract_response_type, +) + + +class ConcreteBaseAPIResponse(APIResponse[bytes]): + ... + + +class ConcreteAPIResponse(APIResponse[List[str]]): + ... + + +class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): + ... + + +def test_extract_response_type_direct_classes() -> None: + assert extract_response_type(BaseAPIResponse[str]) == str + assert extract_response_type(APIResponse[str]) == str + assert extract_response_type(AsyncAPIResponse[str]) == str + + +def test_extract_response_type_direct_class_missing_type_arg() -> None: + with pytest.raises( + RuntimeError, + match="Expected type to have a type argument at index 0 but it did not", + ): + extract_response_type(AsyncAPIResponse) + + +def test_extract_response_type_concrete_subclasses() -> None: + assert extract_response_type(ConcreteBaseAPIResponse) == bytes + assert extract_response_type(ConcreteAPIResponse) == List[str] + assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response + + +def test_extract_response_type_binary_response() -> None: + assert extract_response_type(BinaryAPIResponse) == bytes + assert extract_response_type(AsyncBinaryAPIResponse) == bytes diff --git a/tests/utils.py b/tests/utils.py index 02dd9c0ac..216b33355 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import inspect import traceback import contextlib from typing import Any, TypeVar, Iterator, cast @@ -68,6 +69,8 @@ def assert_matches_type( assert isinstance(value, bool) elif origin == float: assert isinstance(value, float) + elif origin == bytes: + assert isinstance(value, bytes) elif origin == datetime: assert isinstance(value, datetime) elif origin == date: @@ -100,6 +103,8 @@ def assert_matches_type( elif issubclass(origin, BaseModel): assert isinstance(value, type_) assert assert_matches_model(type_, cast(Any, value), path=path) + elif inspect.isclass(origin) and origin.__name__ == "HttpxBinaryResponseContent": + assert value.__class__.__name__ == "HttpxBinaryResponseContent" else: assert None, f"Unhandled field type: {type_}"