From 8c4eff51f2ce6acdfea3e88f1dfd2993d03b8bb3 Mon Sep 17 00:00:00 2001 From: Stainless Bot Date: Mon, 15 Apr 2024 20:10:08 +0000 Subject: [PATCH] feat(api): add batch API https://platform.openai.com/docs/api-reference/batch/create --- .stats.yml | 2 +- api.md | 14 + src/openai/__init__.py | 1 + src/openai/_client.py | 8 + src/openai/_module_client.py | 7 + src/openai/resources/__init__.py | 14 + src/openai/resources/batches.py | 354 +++++++++++++++++++++++ src/openai/types/__init__.py | 4 + src/openai/types/batch.py | 85 ++++++ src/openai/types/batch_create_params.py | 35 +++ src/openai/types/batch_error.py | 21 ++ src/openai/types/batch_request_counts.py | 16 + tests/api_resources/test_batches.py | 268 +++++++++++++++++ 13 files changed, 828 insertions(+), 1 deletion(-) create mode 100644 src/openai/resources/batches.py create mode 100644 src/openai/types/batch.py create mode 100644 src/openai/types/batch_create_params.py create mode 100644 src/openai/types/batch_error.py create mode 100644 src/openai/types/batch_request_counts.py create mode 100644 tests/api_resources/test_batches.py diff --git a/.stats.yml b/.stats.yml index 284caebf44..47c2bce1cc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 52 +configured_endpoints: 55 diff --git a/api.md b/api.md index cc3c91a8d5..38f77592e8 100644 --- a/api.md +++ b/api.md @@ -361,3 +361,17 @@ Methods: - client.beta.threads.messages.files.retrieve(file_id, \*, thread_id, message_id) -> MessageFile - client.beta.threads.messages.files.list(message_id, \*, thread_id, \*\*params) -> SyncCursorPage[MessageFile] + +# Batches + +Types: + +```python +from openai.types import Batch, BatchError, BatchRequestCounts +``` + +Methods: + +- client.batches.create(\*\*params) -> Batch +- client.batches.retrieve(batch_id) -> Batch +- client.batches.cancel(batch_id) -> Batch diff --git a/src/openai/__init__.py b/src/openai/__init__.py index 1daa26f7b7..490ba017f0 100644 --- a/src/openai/__init__.py +++ b/src/openai/__init__.py @@ -335,6 +335,7 @@ def _reset_client() -> None: # type: ignore[reportUnusedFunction] files as files, images as images, models as models, + batches as batches, embeddings as embeddings, completions as completions, fine_tuning as fine_tuning, diff --git a/src/openai/_client.py b/src/openai/_client.py index e9169df72a..5a6852e571 100644 --- a/src/openai/_client.py +++ b/src/openai/_client.py @@ -57,6 +57,7 @@ class OpenAI(SyncAPIClient): models: resources.Models fine_tuning: resources.FineTuning beta: resources.Beta + batches: resources.Batches with_raw_response: OpenAIWithRawResponse with_streaming_response: OpenAIWithStreamedResponse @@ -134,6 +135,7 @@ def __init__( self.models = resources.Models(self) self.fine_tuning = resources.FineTuning(self) self.beta = resources.Beta(self) + self.batches = resources.Batches(self) self.with_raw_response = OpenAIWithRawResponse(self) self.with_streaming_response = OpenAIWithStreamedResponse(self) @@ -257,6 +259,7 @@ class AsyncOpenAI(AsyncAPIClient): models: resources.AsyncModels fine_tuning: resources.AsyncFineTuning beta: resources.AsyncBeta + batches: resources.AsyncBatches with_raw_response: AsyncOpenAIWithRawResponse with_streaming_response: AsyncOpenAIWithStreamedResponse @@ -334,6 +337,7 @@ def __init__( self.models = resources.AsyncModels(self) self.fine_tuning = resources.AsyncFineTuning(self) self.beta = resources.AsyncBeta(self) + self.batches = resources.AsyncBatches(self) self.with_raw_response = AsyncOpenAIWithRawResponse(self) self.with_streaming_response = AsyncOpenAIWithStreamedResponse(self) @@ -458,6 +462,7 @@ def __init__(self, client: OpenAI) -> None: self.models = resources.ModelsWithRawResponse(client.models) self.fine_tuning = resources.FineTuningWithRawResponse(client.fine_tuning) self.beta = resources.BetaWithRawResponse(client.beta) + self.batches = resources.BatchesWithRawResponse(client.batches) class AsyncOpenAIWithRawResponse: @@ -472,6 +477,7 @@ def __init__(self, client: AsyncOpenAI) -> None: self.models = resources.AsyncModelsWithRawResponse(client.models) self.fine_tuning = resources.AsyncFineTuningWithRawResponse(client.fine_tuning) self.beta = resources.AsyncBetaWithRawResponse(client.beta) + self.batches = resources.AsyncBatchesWithRawResponse(client.batches) class OpenAIWithStreamedResponse: @@ -486,6 +492,7 @@ def __init__(self, client: OpenAI) -> None: self.models = resources.ModelsWithStreamingResponse(client.models) self.fine_tuning = resources.FineTuningWithStreamingResponse(client.fine_tuning) self.beta = resources.BetaWithStreamingResponse(client.beta) + self.batches = resources.BatchesWithStreamingResponse(client.batches) class AsyncOpenAIWithStreamedResponse: @@ -500,6 +507,7 @@ def __init__(self, client: AsyncOpenAI) -> None: self.models = resources.AsyncModelsWithStreamingResponse(client.models) self.fine_tuning = resources.AsyncFineTuningWithStreamingResponse(client.fine_tuning) self.beta = resources.AsyncBetaWithStreamingResponse(client.beta) + self.batches = resources.AsyncBatchesWithStreamingResponse(client.batches) Client = OpenAI diff --git a/src/openai/_module_client.py b/src/openai/_module_client.py index 9227f5e2b4..6f7356eb3c 100644 --- a/src/openai/_module_client.py +++ b/src/openai/_module_client.py @@ -42,6 +42,12 @@ def __load__(self) -> resources.Models: return _load_client().models +class BatchesProxy(LazyProxy[resources.Batches]): + @override + def __load__(self) -> resources.Batches: + return _load_client().batches + + class EmbeddingsProxy(LazyProxy[resources.Embeddings]): @override def __load__(self) -> resources.Embeddings: @@ -72,6 +78,7 @@ def __load__(self) -> resources.FineTuning: audio: resources.Audio = AudioProxy().__as_proxied__() images: resources.Images = ImagesProxy().__as_proxied__() models: resources.Models = ModelsProxy().__as_proxied__() +batches: resources.Batches = BatchesProxy().__as_proxied__() embeddings: resources.Embeddings = EmbeddingsProxy().__as_proxied__() completions: resources.Completions = CompletionsProxy().__as_proxied__() moderations: resources.Moderations = ModerationsProxy().__as_proxied__() diff --git a/src/openai/resources/__init__.py b/src/openai/resources/__init__.py index 64aa12d260..ecae4243fc 100644 --- a/src/openai/resources/__init__.py +++ b/src/openai/resources/__init__.py @@ -48,6 +48,14 @@ ModelsWithStreamingResponse, AsyncModelsWithStreamingResponse, ) +from .batches import ( + Batches, + AsyncBatches, + BatchesWithRawResponse, + AsyncBatchesWithRawResponse, + BatchesWithStreamingResponse, + AsyncBatchesWithStreamingResponse, +) from .embeddings import ( Embeddings, AsyncEmbeddings, @@ -142,4 +150,10 @@ "AsyncBetaWithRawResponse", "BetaWithStreamingResponse", "AsyncBetaWithStreamingResponse", + "Batches", + "AsyncBatches", + "BatchesWithRawResponse", + "AsyncBatchesWithRawResponse", + "BatchesWithStreamingResponse", + "AsyncBatchesWithStreamingResponse", ] diff --git a/src/openai/resources/batches.py b/src/openai/resources/batches.py new file mode 100644 index 0000000000..0921ccb194 --- /dev/null +++ b/src/openai/resources/batches.py @@ -0,0 +1,354 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Literal + +import httpx + +from .. import _legacy_response +from ..types import Batch, batch_create_params +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._utils import ( + maybe_transform, + async_maybe_transform, +) +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from .._base_client import ( + make_request_options, +) + +__all__ = ["Batches", "AsyncBatches"] + + +class Batches(SyncAPIResource): + @cached_property + def with_raw_response(self) -> BatchesWithRawResponse: + return BatchesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BatchesWithStreamingResponse: + return BatchesWithStreamingResponse(self) + + def create( + self, + *, + completion_window: Literal["24h"], + endpoint: Literal["/v1/chat/completions"], + input_file_id: str, + metadata: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Batch: + """ + Creates and executes a batch from an uploaded file of requests + + Args: + completion_window: The time frame within which the batch should be processed. Currently only `24h` + is supported. + + endpoint: The endpoint to be used for all requests in the batch. Currently only + `/v1/chat/completions` is supported. + + input_file_id: The ID of an uploaded file that contains requests for the new batch. + + See [upload file](https://platform.openai.com/docs/api-reference/files/create) + for how to upload a file. + + Your input file must be formatted as a JSONL file, and must be uploaded with the + purpose `batch`. + + metadata: Optional custom metadata for the batch. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/batches", + body=maybe_transform( + { + "completion_window": completion_window, + "endpoint": endpoint, + "input_file_id": input_file_id, + "metadata": metadata, + }, + batch_create_params.BatchCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Batch, + ) + + def retrieve( + self, + batch_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Batch: + """ + Retrieves a batch. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return self._get( + f"/batches/{batch_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Batch, + ) + + def cancel( + self, + batch_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Batch: + """ + Cancels an in-progress batch. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return self._post( + f"/batches/{batch_id}/cancel", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Batch, + ) + + +class AsyncBatches(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncBatchesWithRawResponse: + return AsyncBatchesWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBatchesWithStreamingResponse: + return AsyncBatchesWithStreamingResponse(self) + + async def create( + self, + *, + completion_window: Literal["24h"], + endpoint: Literal["/v1/chat/completions"], + input_file_id: str, + metadata: Optional[Dict[str, str]] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Batch: + """ + Creates and executes a batch from an uploaded file of requests + + Args: + completion_window: The time frame within which the batch should be processed. Currently only `24h` + is supported. + + endpoint: The endpoint to be used for all requests in the batch. Currently only + `/v1/chat/completions` is supported. + + input_file_id: The ID of an uploaded file that contains requests for the new batch. + + See [upload file](https://platform.openai.com/docs/api-reference/files/create) + for how to upload a file. + + Your input file must be formatted as a JSONL file, and must be uploaded with the + purpose `batch`. + + metadata: Optional custom metadata for the batch. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/batches", + body=await async_maybe_transform( + { + "completion_window": completion_window, + "endpoint": endpoint, + "input_file_id": input_file_id, + "metadata": metadata, + }, + batch_create_params.BatchCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Batch, + ) + + async def retrieve( + self, + batch_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Batch: + """ + Retrieves a batch. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return await self._get( + f"/batches/{batch_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Batch, + ) + + async def cancel( + self, + batch_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Batch: + """ + Cancels an in-progress batch. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not batch_id: + raise ValueError(f"Expected a non-empty value for `batch_id` but received {batch_id!r}") + return await self._post( + f"/batches/{batch_id}/cancel", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Batch, + ) + + +class BatchesWithRawResponse: + def __init__(self, batches: Batches) -> None: + self._batches = batches + + self.create = _legacy_response.to_raw_response_wrapper( + batches.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + batches.retrieve, + ) + self.cancel = _legacy_response.to_raw_response_wrapper( + batches.cancel, + ) + + +class AsyncBatchesWithRawResponse: + def __init__(self, batches: AsyncBatches) -> None: + self._batches = batches + + self.create = _legacy_response.async_to_raw_response_wrapper( + batches.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + batches.retrieve, + ) + self.cancel = _legacy_response.async_to_raw_response_wrapper( + batches.cancel, + ) + + +class BatchesWithStreamingResponse: + def __init__(self, batches: Batches) -> None: + self._batches = batches + + self.create = to_streamed_response_wrapper( + batches.create, + ) + self.retrieve = to_streamed_response_wrapper( + batches.retrieve, + ) + self.cancel = to_streamed_response_wrapper( + batches.cancel, + ) + + +class AsyncBatchesWithStreamingResponse: + def __init__(self, batches: AsyncBatches) -> None: + self._batches = batches + + self.create = async_to_streamed_response_wrapper( + batches.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + batches.retrieve, + ) + self.cancel = async_to_streamed_response_wrapper( + batches.cancel, + ) diff --git a/src/openai/types/__init__.py b/src/openai/types/__init__.py index 0917e22a8f..4bbcdddc2a 100644 --- a/src/openai/types/__init__.py +++ b/src/openai/types/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from .batch import Batch as Batch from .image import Image as Image from .model import Model as Model from .shared import ( @@ -12,6 +13,7 @@ from .embedding import Embedding as Embedding from .completion import Completion as Completion from .moderation import Moderation as Moderation +from .batch_error import BatchError as BatchError from .file_object import FileObject as FileObject from .file_content import FileContent as FileContent from .file_deleted import FileDeleted as FileDeleted @@ -22,6 +24,8 @@ from .completion_choice import CompletionChoice as CompletionChoice from .image_edit_params import ImageEditParams as ImageEditParams from .file_create_params import FileCreateParams as FileCreateParams +from .batch_create_params import BatchCreateParams as BatchCreateParams +from .batch_request_counts import BatchRequestCounts as BatchRequestCounts from .image_generate_params import ImageGenerateParams as ImageGenerateParams from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .completion_create_params import CompletionCreateParams as CompletionCreateParams diff --git a/src/openai/types/batch.py b/src/openai/types/batch.py new file mode 100644 index 0000000000..bde04d1a24 --- /dev/null +++ b/src/openai/types/batch.py @@ -0,0 +1,85 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import builtins +from typing import List, Optional +from typing_extensions import Literal + +from .._models import BaseModel +from .batch_error import BatchError +from .batch_request_counts import BatchRequestCounts + +__all__ = ["Batch", "Errors"] + + +class Errors(BaseModel): + data: Optional[List[BatchError]] = None + + object: Optional[str] = None + """The object type, which is always `list`.""" + + +class Batch(BaseModel): + id: str + + completion_window: str + """The time frame within which the batch should be processed.""" + + created_at: str + """The Unix timestamp (in seconds) for when the batch was created.""" + + endpoint: str + """The OpenAI API endpoint used by the batch.""" + + input_file_id: str + """The ID of the input file for the batch.""" + + object: Literal["batch"] + """The object type, which is always `batch`.""" + + status: Literal[ + "validating", "failed", "in_progress", "finalizing", "completed", "expired", "cancelling", "cancelled" + ] + """The current status of the batch.""" + + cancelled_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch was cancelled.""" + + cancelling_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch started cancelling.""" + + completed_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch was completed.""" + + error_file_id: Optional[str] = None + """The ID of the file containing the outputs of requests with errors.""" + + errors: Optional[Errors] = None + + expired_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch expired.""" + + expires_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch will expire.""" + + failed_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch failed.""" + + finalizing_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch started finalizing.""" + + in_progress_at: Optional[str] = None + """The Unix timestamp (in seconds) for when the batch started processing.""" + + metadata: Optional[builtins.object] = None + """Set of 16 key-value pairs that can be attached to an object. + + This can be useful for storing additional information about the object in a + structured format. Keys can be a maximum of 64 characters long and values can be + a maxium of 512 characters long. + """ + + output_file_id: Optional[str] = None + """The ID of the file containing the outputs of successfully executed requests.""" + + request_counts: Optional[BatchRequestCounts] = None + """The request counts for different statuses within the batch.""" diff --git a/src/openai/types/batch_create_params.py b/src/openai/types/batch_create_params.py new file mode 100644 index 0000000000..6a22be8626 --- /dev/null +++ b/src/openai/types/batch_create_params.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["BatchCreateParams"] + + +class BatchCreateParams(TypedDict, total=False): + completion_window: Required[Literal["24h"]] + """The time frame within which the batch should be processed. + + Currently only `24h` is supported. + """ + + endpoint: Required[Literal["/v1/chat/completions"]] + """The endpoint to be used for all requests in the batch. + + Currently only `/v1/chat/completions` is supported. + """ + + input_file_id: Required[str] + """The ID of an uploaded file that contains requests for the new batch. + + See [upload file](https://platform.openai.com/docs/api-reference/files/create) + for how to upload a file. + + Your input file must be formatted as a JSONL file, and must be uploaded with the + purpose `batch`. + """ + + metadata: Optional[Dict[str, str]] + """Optional custom metadata for the batch.""" diff --git a/src/openai/types/batch_error.py b/src/openai/types/batch_error.py new file mode 100644 index 0000000000..1cdd808dbd --- /dev/null +++ b/src/openai/types/batch_error.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["BatchError"] + + +class BatchError(BaseModel): + code: Optional[str] = None + """An error code identifying the error type.""" + + line: Optional[int] = None + """The line number of the input file where the error occurred, if applicable.""" + + message: Optional[str] = None + """A human-readable message providing more details about the error.""" + + param: Optional[str] = None + """The name of the parameter that caused the error, if applicable.""" diff --git a/src/openai/types/batch_request_counts.py b/src/openai/types/batch_request_counts.py new file mode 100644 index 0000000000..068b071af1 --- /dev/null +++ b/src/openai/types/batch_request_counts.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["BatchRequestCounts"] + + +class BatchRequestCounts(BaseModel): + completed: int + """Number of requests that have been completed successfully.""" + + failed: int + """Number of requests that have failed.""" + + total: int + """Total number of requests in the batch.""" diff --git a/tests/api_resources/test_batches.py b/tests/api_resources/test_batches.py new file mode 100644 index 0000000000..aafeff8116 --- /dev/null +++ b/tests/api_resources/test_batches.py @@ -0,0 +1,268 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from openai import OpenAI, AsyncOpenAI +from tests.utils import assert_matches_type +from openai.types import Batch + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBatches: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: OpenAI) -> None: + batch = client.batches.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: OpenAI) -> None: + batch = client.batches.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + metadata={"foo": "string"}, + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: OpenAI) -> None: + response = client.batches.with_raw_response.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: OpenAI) -> None: + with client.batches.with_streaming_response.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: OpenAI) -> None: + batch = client.batches.retrieve( + "string", + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: OpenAI) -> None: + response = client.batches.with_raw_response.retrieve( + "string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: OpenAI) -> None: + with client.batches.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + client.batches.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_cancel(self, client: OpenAI) -> None: + batch = client.batches.cancel( + "string", + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + def test_raw_response_cancel(self, client: OpenAI) -> None: + response = client.batches.with_raw_response.cancel( + "string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + def test_streaming_response_cancel(self, client: OpenAI) -> None: + with client.batches.with_streaming_response.cancel( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_cancel(self, client: OpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + client.batches.with_raw_response.cancel( + "", + ) + + +class TestAsyncBatches: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_create(self, async_client: AsyncOpenAI) -> None: + batch = await async_client.batches.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOpenAI) -> None: + batch = await async_client.batches.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + metadata={"foo": "string"}, + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOpenAI) -> None: + response = await async_client.batches.with_raw_response.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOpenAI) -> None: + async with async_client.batches.with_streaming_response.create( + completion_window="24h", + endpoint="/v1/chat/completions", + input_file_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOpenAI) -> None: + batch = await async_client.batches.retrieve( + "string", + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOpenAI) -> None: + response = await async_client.batches.with_raw_response.retrieve( + "string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOpenAI) -> None: + async with async_client.batches.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + await async_client.batches.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_cancel(self, async_client: AsyncOpenAI) -> None: + batch = await async_client.batches.cancel( + "string", + ) + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncOpenAI) -> None: + response = await async_client.batches.with_raw_response.cancel( + "string", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + batch = response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncOpenAI) -> None: + async with async_client.batches.with_streaming_response.cancel( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + batch = await response.parse() + assert_matches_type(Batch, batch, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_cancel(self, async_client: AsyncOpenAI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `batch_id` but received ''"): + await async_client.batches.with_raw_response.cancel( + "", + )