From c36bf615ac2d8ebd91d8452c5b7440541e66f1d6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 18:11:45 +0000 Subject: [PATCH 1/5] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0ecc23f..84c354b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/replicate%2Freplicate-client-2088d647e7b0fd37dcf58ef48b8f01ed1a82ff797b9697ad10a7b6a5105e9e0f.yml -openapi_spec_hash: 718f540e7c44501e1a8c7156ee45d595 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/replicate%2Freplicate-client-efbc8cc2d74644b213e161d3e11e0589d1cef181fb318ea02c8eb6b00f245713.yml +openapi_spec_hash: 13da0c06c900b61cd98ab678e024987a config_hash: 927b6ebc00ee115763ad69483bbf5566 From 3173e5f61edd89ffe0b64b53fc8e8e9905e145e4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 18:16:15 +0000 Subject: [PATCH 2/5] feat(api): add Files API methods ...and mark them as undocumented. --- .stats.yml | 4 +- README.md | 28 +- api.md | 8 + src/replicate/__init__.py | 17 +- src/replicate/_client.py | 87 ++- src/replicate/_files.py | 2 +- src/replicate/_module_client.py | 8 + src/replicate/resources/__init__.py | 14 + src/replicate/resources/files.py | 678 ++++++++++++++++++++ src/replicate/types/__init__.py | 5 + src/replicate/types/file_create_params.py | 23 + src/replicate/types/file_create_response.py | 44 ++ src/replicate/types/file_download_params.py | 23 + src/replicate/types/file_get_response.py | 44 ++ src/replicate/types/file_list_response.py | 44 ++ tests/api_resources/test_files.py | 487 ++++++++++++++ tests/conftest.py | 8 +- tests/test_client.py | 176 ++--- tests/test_module_client.py | 2 +- 19 files changed, 1544 insertions(+), 158 deletions(-) create mode 100644 src/replicate/resources/files.py create mode 100644 src/replicate/types/file_create_params.py create mode 100644 src/replicate/types/file_create_response.py create mode 100644 src/replicate/types/file_download_params.py create mode 100644 src/replicate/types/file_get_response.py create mode 100644 src/replicate/types/file_list_response.py create mode 100644 tests/api_resources/test_files.py diff --git a/.stats.yml b/.stats.yml index 84c354b..d97e266 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 30 +configured_endpoints: 35 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/replicate%2Freplicate-client-efbc8cc2d74644b213e161d3e11e0589d1cef181fb318ea02c8eb6b00f245713.yml openapi_spec_hash: 13da0c06c900b61cd98ab678e024987a -config_hash: 927b6ebc00ee115763ad69483bbf5566 +config_hash: e25b98bb6202d27e54fc6e08c7de80d8 diff --git a/README.md b/README.md index 8cfc0e7..a1ab424 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,17 @@ import os from replicate import Replicate client = Replicate( - bearer_token=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted + api_key=os.environ.get("REPLICATE_CLIENT_API_KEY"), # This is the default and can be omitted ) account = client.account.get() print(account.type) ``` -While you can provide a `bearer_token` keyword argument, +While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `REPLICATE_API_TOKEN="My Bearer Token"` to your `.env` file -so that your Bearer Token is not stored in source control. +to add `REPLICATE_CLIENT_API_KEY="My API Key"` to your `.env` file +so that your API Key is not stored in source control. ## Async usage @@ -50,7 +50,7 @@ import asyncio from replicate import AsyncReplicate client = AsyncReplicate( - bearer_token=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted + api_key=os.environ.get("REPLICATE_CLIENT_API_KEY"), # This is the default and can be omitted ) @@ -136,6 +136,24 @@ for prediction in first_page.results: # Remove `await` for non-async usage. ``` +## File uploads + +Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. + +```python +from pathlib import Path +from replicate import Replicate + +client = Replicate() + +client.files.create( + content=Path("/path/to/file"), + filename="filename", +) +``` + +The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `replicate.APIConnectionError` is raised. diff --git a/api.md b/api.md index 0afde25..408f49a 100644 --- a/api.md +++ b/api.md @@ -154,3 +154,11 @@ from replicate.types.webhooks.default import SecretGetResponse Methods: - client.webhooks.default.secret.get() -> SecretGetResponse + +# Files + +Types: + +```python +from replicate.types import FileCreateResponse, FileListResponse, FileGetResponse +``` diff --git a/src/replicate/__init__.py b/src/replicate/__init__.py index ad8bd3f..aee7ed5 100644 --- a/src/replicate/__init__.py +++ b/src/replicate/__init__.py @@ -104,7 +104,7 @@ from ._base_client import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES -bearer_token: str | None = None +api_key: str | None = None base_url: str | _httpx.URL | None = None @@ -125,14 +125,14 @@ class _ModuleClient(Replicate): @property # type: ignore @override - def bearer_token(self) -> str | None: - return bearer_token + def api_key(self) -> str | None: + return api_key - @bearer_token.setter # type: ignore - def bearer_token(self, value: str | None) -> None: # type: ignore - global bearer_token + @api_key.setter # type: ignore + def api_key(self, value: str | None) -> None: # type: ignore + global api_key - bearer_token = value + api_key = value @property @override @@ -210,7 +210,7 @@ def _load_client() -> Replicate: # type: ignore[reportUnusedFunction] if _client is None: _client = _ModuleClient( - bearer_token=bearer_token, + api_key=api_key, base_url=base_url, timeout=timeout, max_retries=max_retries, @@ -230,6 +230,7 @@ def _reset_client() -> None: # type: ignore[reportUnusedFunction] from ._module_client import ( + files as files, models as models, account as account, hardware as hardware, diff --git a/src/replicate/_client.py b/src/replicate/_client.py index 88fc0d9..055388f 100644 --- a/src/replicate/_client.py +++ b/src/replicate/_client.py @@ -31,7 +31,8 @@ ) if TYPE_CHECKING: - from .resources import models, account, hardware, webhooks, trainings, collections, deployments, predictions + from .resources import files, models, account, hardware, webhooks, trainings, collections, deployments, predictions + from .resources.files import FilesResource, AsyncFilesResource from .resources.account import AccountResource, AsyncAccountResource from .resources.hardware import HardwareResource, AsyncHardwareResource from .resources.trainings import TrainingsResource, AsyncTrainingsResource @@ -55,12 +56,12 @@ class Replicate(SyncAPIClient): # client options - bearer_token: str + api_key: str def __init__( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, @@ -82,15 +83,15 @@ def __init__( ) -> None: """Construct a new synchronous Replicate client instance. - This automatically infers the `bearer_token` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `REPLICATE_CLIENT_API_KEY` environment variable if it is not provided. """ - if bearer_token is None: - bearer_token = os.environ.get("REPLICATE_API_TOKEN") - if bearer_token is None: + if api_key is None: + api_key = os.environ.get("REPLICATE_CLIENT_API_KEY") + if api_key is None: raise ReplicateError( - "The bearer_token client option must be set either by passing bearer_token to the client or by setting the REPLICATE_API_TOKEN environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_CLIENT_API_KEY environment variable" ) - self.bearer_token = bearer_token + self.api_key = api_key if base_url is None: base_url = os.environ.get("REPLICATE_BASE_URL") @@ -156,6 +157,12 @@ def webhooks(self) -> WebhooksResource: return WebhooksResource(self) + @cached_property + def files(self) -> FilesResource: + from .resources.files import FilesResource + + return FilesResource(self) + @cached_property def with_raw_response(self) -> ReplicateWithRawResponse: return ReplicateWithRawResponse(self) @@ -172,8 +179,8 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - bearer_token = self.bearer_token - return {"Authorization": f"Bearer {bearer_token}"} + api_key = self.api_key + return {"Authorization": f"Bearer {api_key}"} @property @override @@ -187,7 +194,7 @@ def default_headers(self) -> dict[str, str | Omit]: def copy( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.Client | None = None, @@ -221,7 +228,7 @@ def copy( http_client = http_client or self._client return self.__class__( - bearer_token=bearer_token or self.bearer_token, + api_key=api_key or self.api_key, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -271,12 +278,12 @@ def _make_status_error( class AsyncReplicate(AsyncAPIClient): # client options - bearer_token: str + api_key: str def __init__( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, @@ -298,15 +305,15 @@ def __init__( ) -> None: """Construct a new async AsyncReplicate client instance. - This automatically infers the `bearer_token` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `REPLICATE_CLIENT_API_KEY` environment variable if it is not provided. """ - if bearer_token is None: - bearer_token = os.environ.get("REPLICATE_API_TOKEN") - if bearer_token is None: + if api_key is None: + api_key = os.environ.get("REPLICATE_CLIENT_API_KEY") + if api_key is None: raise ReplicateError( - "The bearer_token client option must be set either by passing bearer_token to the client or by setting the REPLICATE_API_TOKEN environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_CLIENT_API_KEY environment variable" ) - self.bearer_token = bearer_token + self.api_key = api_key if base_url is None: base_url = os.environ.get("REPLICATE_BASE_URL") @@ -372,6 +379,12 @@ def webhooks(self) -> AsyncWebhooksResource: return AsyncWebhooksResource(self) + @cached_property + def files(self) -> AsyncFilesResource: + from .resources.files import AsyncFilesResource + + return AsyncFilesResource(self) + @cached_property def with_raw_response(self) -> AsyncReplicateWithRawResponse: return AsyncReplicateWithRawResponse(self) @@ -388,8 +401,8 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - bearer_token = self.bearer_token - return {"Authorization": f"Bearer {bearer_token}"} + api_key = self.api_key + return {"Authorization": f"Bearer {api_key}"} @property @override @@ -403,7 +416,7 @@ def default_headers(self) -> dict[str, str | Omit]: def copy( self, *, - bearer_token: str | None = None, + api_key: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.AsyncClient | None = None, @@ -437,7 +450,7 @@ def copy( http_client = http_client or self._client return self.__class__( - bearer_token=bearer_token or self.bearer_token, + api_key=api_key or self.api_key, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -539,6 +552,12 @@ def webhooks(self) -> webhooks.WebhooksResourceWithRawResponse: return WebhooksResourceWithRawResponse(self._client.webhooks) + @cached_property + def files(self) -> files.FilesResourceWithRawResponse: + from .resources.files import FilesResourceWithRawResponse + + return FilesResourceWithRawResponse(self._client.files) + class AsyncReplicateWithRawResponse: _client: AsyncReplicate @@ -594,6 +613,12 @@ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithRawResponse: return AsyncWebhooksResourceWithRawResponse(self._client.webhooks) + @cached_property + def files(self) -> files.AsyncFilesResourceWithRawResponse: + from .resources.files import AsyncFilesResourceWithRawResponse + + return AsyncFilesResourceWithRawResponse(self._client.files) + class ReplicateWithStreamedResponse: _client: Replicate @@ -649,6 +674,12 @@ def webhooks(self) -> webhooks.WebhooksResourceWithStreamingResponse: return WebhooksResourceWithStreamingResponse(self._client.webhooks) + @cached_property + def files(self) -> files.FilesResourceWithStreamingResponse: + from .resources.files import FilesResourceWithStreamingResponse + + return FilesResourceWithStreamingResponse(self._client.files) + class AsyncReplicateWithStreamedResponse: _client: AsyncReplicate @@ -704,6 +735,12 @@ def webhooks(self) -> webhooks.AsyncWebhooksResourceWithStreamingResponse: return AsyncWebhooksResourceWithStreamingResponse(self._client.webhooks) + @cached_property + def files(self) -> files.AsyncFilesResourceWithStreamingResponse: + from .resources.files import AsyncFilesResourceWithStreamingResponse + + return AsyncFilesResourceWithStreamingResponse(self._client.files) + Client = Replicate diff --git a/src/replicate/_files.py b/src/replicate/_files.py index 715cc20..d563518 100644 --- a/src/replicate/_files.py +++ b/src/replicate/_files.py @@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/replicate/replicate-python-stainless/tree/main#file-uploads" ) from None diff --git a/src/replicate/_module_client.py b/src/replicate/_module_client.py index c80ea25..1d90cf0 100644 --- a/src/replicate/_module_client.py +++ b/src/replicate/_module_client.py @@ -6,6 +6,7 @@ from typing_extensions import override if TYPE_CHECKING: + from .resources.files import FilesResource from .resources.account import AccountResource from .resources.hardware import HardwareResource from .resources.trainings import TrainingsResource @@ -19,6 +20,12 @@ from ._utils import LazyProxy +class FilesResourceProxy(LazyProxy["FilesResource"]): + @override + def __load__(self) -> FilesResource: + return _load_client().files + + class ModelsResourceProxy(LazyProxy["ModelsResource"]): @override def __load__(self) -> ModelsResource: @@ -67,6 +74,7 @@ def __load__(self) -> PredictionsResource: return _load_client().predictions +files: FilesResource = FilesResourceProxy().__as_proxied__() models: ModelsResource = ModelsResourceProxy().__as_proxied__() account: AccountResource = AccountResourceProxy().__as_proxied__() hardware: HardwareResource = HardwareResourceProxy().__as_proxied__() diff --git a/src/replicate/resources/__init__.py b/src/replicate/resources/__init__.py index 7982afa..4786942 100644 --- a/src/replicate/resources/__init__.py +++ b/src/replicate/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .files import ( + FilesResource, + AsyncFilesResource, + FilesResourceWithRawResponse, + AsyncFilesResourceWithRawResponse, + FilesResourceWithStreamingResponse, + AsyncFilesResourceWithStreamingResponse, +) from .models import ( ModelsResource, AsyncModelsResource, @@ -114,4 +122,10 @@ "AsyncWebhooksResourceWithRawResponse", "WebhooksResourceWithStreamingResponse", "AsyncWebhooksResourceWithStreamingResponse", + "FilesResource", + "AsyncFilesResource", + "FilesResourceWithRawResponse", + "AsyncFilesResourceWithRawResponse", + "FilesResourceWithStreamingResponse", + "AsyncFilesResourceWithStreamingResponse", ] diff --git a/src/replicate/resources/files.py b/src/replicate/resources/files.py new file mode 100644 index 0000000..fdb90c0 --- /dev/null +++ b/src/replicate/resources/files.py @@ -0,0 +1,678 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Mapping, cast + +import httpx + +from ..types import file_create_params, file_download_params +from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven, FileTypes +from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + to_custom_raw_response_wrapper, + async_to_streamed_response_wrapper, + to_custom_streamed_response_wrapper, + async_to_custom_raw_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ..pagination import SyncCursorURLPage, AsyncCursorURLPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.file_get_response import FileGetResponse +from ..types.file_list_response import FileListResponse +from ..types.file_create_response import FileCreateResponse + +__all__ = ["FilesResource", "AsyncFilesResource"] + + +class FilesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> FilesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/replicate/replicate-python-stainless#accessing-raw-response-data-eg-headers + """ + return FilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> FilesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/replicate/replicate-python-stainless#with_streaming_response + """ + return FilesResourceWithStreamingResponse(self) + + def create( + self, + *, + content: FileTypes, + filename: str, + metadata: object | NotGiven = NOT_GIVEN, + type: 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, + ) -> FileCreateResponse: + """ + Create a file by uploading its content and optional metadata. + + Example cURL request: + + ```console + curl -X POST https://api.replicate.com/v1/files \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + -H 'Content-Type: multipart/form-data' \\ + -F 'content=@/path/to/archive.zip;type=application/zip;filename=example.zip' \\ + -F 'metadata={"customer_reference_id": 123};type=application/json' + ``` + + The request must include: + + - `content`: The file content (required) + - `type`: The content / MIME type for the file (defaults to + `application/octet-stream`) + - `filename`: The filename (required, ≤ 255 bytes, valid UTF-8) + - `metadata`: User-provided metadata associated with the file (defaults to `{}`, + must be valid JSON) + + Args: + content: The file content + + filename: The filename + + metadata: User-provided metadata associated with the file + + type: The content / MIME type for the file + + 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 + """ + body = deepcopy_minimal( + { + "content": content, + "filename": filename, + "metadata": metadata, + "type": type, + } + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["content"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + "/files", + body=maybe_transform(body, file_create_params.FileCreateParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileCreateResponse, + ) + + def list( + self, + *, + # 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, + ) -> SyncCursorURLPage[FileListResponse]: + """ + Get a paginated list of all files created by the user or organization associated + with the provided API token. + + Example cURL request: + + ```console + curl -s \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + https://api.replicate.com/v1/files + ``` + + The response will be a paginated JSON array of file objects, sorted with the + most recent file first. + """ + return self._get_api_list( + "/files", + page=SyncCursorURLPage[FileListResponse], + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + model=FileListResponse, + ) + + def delete( + self, + *, + file_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, + ) -> None: + """Delete a file. + + Once a file has been deleted, subsequent requests to the file + resource return 404 Not found. + + Example cURL request: + + ```console + curl -X DELETE \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + https://api.replicate.com/v1/files/cneqzikepnug6xezperrr4z55o + ``` + + 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( + f"/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def download( + self, + *, + file_id: str, + expiry: int, + owner: str, + signature: 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, + ) -> BinaryAPIResponse: + """ + Download a file by providing the file owner, access expiry, and a valid + signature. + + Example cURL request: + + ```console + curl -X GET "https://api.replicate.com/v1/files/cneqzikepnug6xezperrr4z55o/download?expiry=1708515345&owner=mattt&signature=zuoghqlrcnw8YHywkpaXQlHsVhWen%2FDZ4aal76dLiOo%3D" + ``` + + Args: + expiry: A Unix timestamp with expiration date of this download URL + + owner: The username of the user or organization that uploaded the file + + signature: A base64-encoded HMAC-SHA256 checksum of the string '{owner} {id} {expiry}' + generated with the Files API signing secret + + 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return self._get( + f"/files/{file_id}/download", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "expiry": expiry, + "owner": owner, + "signature": signature, + }, + file_download_params.FileDownloadParams, + ), + ), + cast_to=BinaryAPIResponse, + ) + + def get( + self, + *, + file_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, + ) -> FileGetResponse: + """ + Get the details of a file. + + Example cURL request: + + ```console + curl -s \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + https://api.replicate.com/v1/files/cneqzikepnug6xezperrr4z55o + ``` + + 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return self._get( + f"/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileGetResponse, + ) + + +class AsyncFilesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncFilesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/replicate/replicate-python-stainless#accessing-raw-response-data-eg-headers + """ + return AsyncFilesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFilesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/replicate/replicate-python-stainless#with_streaming_response + """ + return AsyncFilesResourceWithStreamingResponse(self) + + async def create( + self, + *, + content: FileTypes, + filename: str, + metadata: object | NotGiven = NOT_GIVEN, + type: 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, + ) -> FileCreateResponse: + """ + Create a file by uploading its content and optional metadata. + + Example cURL request: + + ```console + curl -X POST https://api.replicate.com/v1/files \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + -H 'Content-Type: multipart/form-data' \\ + -F 'content=@/path/to/archive.zip;type=application/zip;filename=example.zip' \\ + -F 'metadata={"customer_reference_id": 123};type=application/json' + ``` + + The request must include: + + - `content`: The file content (required) + - `type`: The content / MIME type for the file (defaults to + `application/octet-stream`) + - `filename`: The filename (required, ≤ 255 bytes, valid UTF-8) + - `metadata`: User-provided metadata associated with the file (defaults to `{}`, + must be valid JSON) + + Args: + content: The file content + + filename: The filename + + metadata: User-provided metadata associated with the file + + type: The content / MIME type for the file + + 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 + """ + body = deepcopy_minimal( + { + "content": content, + "filename": filename, + "metadata": metadata, + "type": type, + } + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["content"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/files", + body=await async_maybe_transform(body, file_create_params.FileCreateParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileCreateResponse, + ) + + def list( + self, + *, + # 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, + ) -> AsyncPaginator[FileListResponse, AsyncCursorURLPage[FileListResponse]]: + """ + Get a paginated list of all files created by the user or organization associated + with the provided API token. + + Example cURL request: + + ```console + curl -s \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + https://api.replicate.com/v1/files + ``` + + The response will be a paginated JSON array of file objects, sorted with the + most recent file first. + """ + return self._get_api_list( + "/files", + page=AsyncCursorURLPage[FileListResponse], + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + model=FileListResponse, + ) + + async def delete( + self, + *, + file_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, + ) -> None: + """Delete a file. + + Once a file has been deleted, subsequent requests to the file + resource return 404 Not found. + + Example cURL request: + + ```console + curl -X DELETE \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + https://api.replicate.com/v1/files/cneqzikepnug6xezperrr4z55o + ``` + + 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._delete( + f"/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def download( + self, + *, + file_id: str, + expiry: int, + owner: str, + signature: 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, + ) -> AsyncBinaryAPIResponse: + """ + Download a file by providing the file owner, access expiry, and a valid + signature. + + Example cURL request: + + ```console + curl -X GET "https://api.replicate.com/v1/files/cneqzikepnug6xezperrr4z55o/download?expiry=1708515345&owner=mattt&signature=zuoghqlrcnw8YHywkpaXQlHsVhWen%2FDZ4aal76dLiOo%3D" + ``` + + Args: + expiry: A Unix timestamp with expiration date of this download URL + + owner: The username of the user or organization that uploaded the file + + signature: A base64-encoded HMAC-SHA256 checksum of the string '{owner} {id} {expiry}' + generated with the Files API signing secret + + 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return await self._get( + f"/files/{file_id}/download", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "expiry": expiry, + "owner": owner, + "signature": signature, + }, + file_download_params.FileDownloadParams, + ), + ), + cast_to=AsyncBinaryAPIResponse, + ) + + async def get( + self, + *, + file_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, + ) -> FileGetResponse: + """ + Get the details of a file. + + Example cURL request: + + ```console + curl -s \\ + -H "Authorization: Token $REPLICATE_API_TOKEN" \\ + https://api.replicate.com/v1/files/cneqzikepnug6xezperrr4z55o + ``` + + 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 file_id: + raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") + return await self._get( + f"/files/{file_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FileGetResponse, + ) + + +class FilesResourceWithRawResponse: + def __init__(self, files: FilesResource) -> None: + self._files = files + + self.create = to_raw_response_wrapper( + files.create, + ) + self.list = to_raw_response_wrapper( + files.list, + ) + self.delete = to_raw_response_wrapper( + files.delete, + ) + self.download = to_custom_raw_response_wrapper( + files.download, + BinaryAPIResponse, + ) + self.get = to_raw_response_wrapper( + files.get, + ) + + +class AsyncFilesResourceWithRawResponse: + def __init__(self, files: AsyncFilesResource) -> None: + self._files = files + + self.create = async_to_raw_response_wrapper( + files.create, + ) + self.list = async_to_raw_response_wrapper( + files.list, + ) + self.delete = async_to_raw_response_wrapper( + files.delete, + ) + self.download = async_to_custom_raw_response_wrapper( + files.download, + AsyncBinaryAPIResponse, + ) + self.get = async_to_raw_response_wrapper( + files.get, + ) + + +class FilesResourceWithStreamingResponse: + def __init__(self, files: FilesResource) -> None: + self._files = files + + self.create = to_streamed_response_wrapper( + files.create, + ) + self.list = to_streamed_response_wrapper( + files.list, + ) + self.delete = to_streamed_response_wrapper( + files.delete, + ) + self.download = to_custom_streamed_response_wrapper( + files.download, + StreamedBinaryAPIResponse, + ) + self.get = to_streamed_response_wrapper( + files.get, + ) + + +class AsyncFilesResourceWithStreamingResponse: + def __init__(self, files: AsyncFilesResource) -> None: + self._files = files + + self.create = async_to_streamed_response_wrapper( + files.create, + ) + self.list = async_to_streamed_response_wrapper( + files.list, + ) + self.delete = async_to_streamed_response_wrapper( + files.delete, + ) + self.download = async_to_custom_streamed_response_wrapper( + files.download, + AsyncStreamedBinaryAPIResponse, + ) + self.get = async_to_streamed_response_wrapper( + files.get, + ) diff --git a/src/replicate/types/__init__.py b/src/replicate/types/__init__.py index 5edacdb..aa49593 100644 --- a/src/replicate/types/__init__.py +++ b/src/replicate/types/__init__.py @@ -3,10 +3,15 @@ from __future__ import annotations from .prediction import Prediction as Prediction +from .file_get_response import FileGetResponse as FileGetResponse +from .file_create_params import FileCreateParams as FileCreateParams +from .file_list_response import FileListResponse as FileListResponse from .model_create_params import ModelCreateParams as ModelCreateParams from .model_list_response import ModelListResponse as ModelListResponse from .model_search_params import ModelSearchParams as ModelSearchParams from .account_get_response import AccountGetResponse as AccountGetResponse +from .file_create_response import FileCreateResponse as FileCreateResponse +from .file_download_params import FileDownloadParams as FileDownloadParams from .training_get_response import TrainingGetResponse as TrainingGetResponse from .hardware_list_response import HardwareListResponse as HardwareListResponse from .prediction_list_params import PredictionListParams as PredictionListParams diff --git a/src/replicate/types/file_create_params.py b/src/replicate/types/file_create_params.py new file mode 100644 index 0000000..e7c2186 --- /dev/null +++ b/src/replicate/types/file_create_params.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .._types import FileTypes + +__all__ = ["FileCreateParams"] + + +class FileCreateParams(TypedDict, total=False): + content: Required[FileTypes] + """The file content""" + + filename: Required[str] + """The filename""" + + metadata: object + """User-provided metadata associated with the file""" + + type: str + """The content / MIME type for the file""" diff --git a/src/replicate/types/file_create_response.py b/src/replicate/types/file_create_response.py new file mode 100644 index 0000000..118c73c --- /dev/null +++ b/src/replicate/types/file_create_response.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["FileCreateResponse", "Checksums", "URLs"] + + +class Checksums(BaseModel): + sha256: Optional[str] = None + """SHA256 checksum of the file""" + + +class URLs(BaseModel): + get: Optional[str] = None + """A URL to the file resource""" + + +class FileCreateResponse(BaseModel): + id: str + """A unique, randomly-generated identifier for the file resource""" + + checksums: Checksums + """A dictionary of checksums for the file keyed by the algorithm name""" + + content_type: str + """The content / MIME type of the file""" + + created_at: datetime + """When the file was created""" + + expires_at: datetime + """When the file expires""" + + metadata: object + """Metadata provided by user when the file was created""" + + size: int + """The length of the file in bytes""" + + urls: URLs + """A dictionary of URLs associated with the file resource""" diff --git a/src/replicate/types/file_download_params.py b/src/replicate/types/file_download_params.py new file mode 100644 index 0000000..c13cd5f --- /dev/null +++ b/src/replicate/types/file_download_params.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["FileDownloadParams"] + + +class FileDownloadParams(TypedDict, total=False): + file_id: Required[str] + + expiry: Required[int] + """A Unix timestamp with expiration date of this download URL""" + + owner: Required[str] + """The username of the user or organization that uploaded the file""" + + signature: Required[str] + """ + A base64-encoded HMAC-SHA256 checksum of the string '{owner} {id} {expiry}' + generated with the Files API signing secret + """ diff --git a/src/replicate/types/file_get_response.py b/src/replicate/types/file_get_response.py new file mode 100644 index 0000000..e8abbf9 --- /dev/null +++ b/src/replicate/types/file_get_response.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["FileGetResponse", "Checksums", "URLs"] + + +class Checksums(BaseModel): + sha256: Optional[str] = None + """SHA256 checksum of the file""" + + +class URLs(BaseModel): + get: Optional[str] = None + """A URL to the file resource""" + + +class FileGetResponse(BaseModel): + id: str + """A unique, randomly-generated identifier for the file resource""" + + checksums: Checksums + """A dictionary of checksums for the file keyed by the algorithm name""" + + content_type: str + """The content / MIME type of the file""" + + created_at: datetime + """When the file was created""" + + expires_at: datetime + """When the file expires""" + + metadata: object + """Metadata provided by user when the file was created""" + + size: int + """The length of the file in bytes""" + + urls: URLs + """A dictionary of URLs associated with the file resource""" diff --git a/src/replicate/types/file_list_response.py b/src/replicate/types/file_list_response.py new file mode 100644 index 0000000..76ac052 --- /dev/null +++ b/src/replicate/types/file_list_response.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from .._models import BaseModel + +__all__ = ["FileListResponse", "Checksums", "URLs"] + + +class Checksums(BaseModel): + sha256: Optional[str] = None + """SHA256 checksum of the file""" + + +class URLs(BaseModel): + get: Optional[str] = None + """A URL to the file resource""" + + +class FileListResponse(BaseModel): + id: str + """A unique, randomly-generated identifier for the file resource""" + + checksums: Checksums + """A dictionary of checksums for the file keyed by the algorithm name""" + + content_type: str + """The content / MIME type of the file""" + + created_at: datetime + """When the file was created""" + + expires_at: datetime + """When the file expires""" + + metadata: object + """Metadata provided by user when the file was created""" + + size: int + """The length of the file in bytes""" + + urls: URLs + """A dictionary of URLs associated with the file resource""" diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py new file mode 100644 index 0000000..2a773fe --- /dev/null +++ b/tests/api_resources/test_files.py @@ -0,0 +1,487 @@ +# 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 httpx +import pytest +from respx import MockRouter + +from replicate import Replicate, AsyncReplicate +from tests.utils import assert_matches_type +from replicate.types import ( + FileGetResponse, + FileListResponse, + FileCreateResponse, +) +from replicate._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, +) +from replicate.pagination import SyncCursorURLPage, AsyncCursorURLPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestFiles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_create(self, client: Replicate) -> None: + file = client.files.create( + content=b"raw file contents", + filename="filename", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_create_with_all_params(self, client: Replicate) -> None: + file = client.files.create( + content=b"raw file contents", + filename="filename", + metadata={}, + type="type", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_create(self, client: Replicate) -> None: + response = client.files.with_raw_response.create( + content=b"raw file contents", + filename="filename", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileCreateResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_create(self, client: Replicate) -> None: + with client.files.with_streaming_response.create( + content=b"raw file contents", + filename="filename", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileCreateResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_list(self, client: Replicate) -> None: + file = client.files.list() + assert_matches_type(SyncCursorURLPage[FileListResponse], file, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list(self, client: Replicate) -> 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(SyncCursorURLPage[FileListResponse], file, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list(self, client: Replicate) -> 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(SyncCursorURLPage[FileListResponse], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_delete(self, client: Replicate) -> None: + file = client.files.delete( + file_id="file_id", + ) + assert file is None + + @pytest.mark.skip() + @parametrize + def test_raw_response_delete(self, client: Replicate) -> None: + response = client.files.with_raw_response.delete( + file_id="file_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert file is None + + @pytest.mark.skip() + @parametrize + def test_streaming_response_delete(self, client: Replicate) -> None: + with client.files.with_streaming_response.delete( + file_id="file_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert file is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_delete(self, client: Replicate) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.files.with_raw_response.delete( + file_id="", + ) + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_download(self, client: Replicate, respx_mock: MockRouter) -> None: + respx_mock.get("/files/file_id/download").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + file = client.files.download( + file_id="file_id", + expiry=0, + owner="owner", + signature="signature", + ) + assert file.is_closed + assert file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, BinaryAPIResponse) + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_download(self, client: Replicate, respx_mock: MockRouter) -> None: + respx_mock.get("/files/file_id/download").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + file = client.files.with_raw_response.download( + file_id="file_id", + expiry=0, + owner="owner", + signature="signature", + ) + + assert file.is_closed is True + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + assert file.json() == {"foo": "bar"} + assert isinstance(file, BinaryAPIResponse) + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_download(self, client: Replicate, respx_mock: MockRouter) -> None: + respx_mock.get("/files/file_id/download").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.files.with_streaming_response.download( + file_id="file_id", + expiry=0, + owner="owner", + signature="signature", + ) as file: + assert not file.is_closed + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + + assert file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, StreamedBinaryAPIResponse) + + assert cast(Any, file.is_closed) is True + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_download(self, client: Replicate) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.files.with_raw_response.download( + file_id="", + expiry=0, + owner="owner", + signature="signature", + ) + + @pytest.mark.skip() + @parametrize + def test_method_get(self, client: Replicate) -> None: + file = client.files.get( + file_id="file_id", + ) + assert_matches_type(FileGetResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_get(self, client: Replicate) -> None: + response = client.files.with_raw_response.get( + file_id="file_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = response.parse() + assert_matches_type(FileGetResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_get(self, client: Replicate) -> None: + with client.files.with_streaming_response.get( + file_id="file_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = response.parse() + assert_matches_type(FileGetResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_get(self, client: Replicate) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + client.files.with_raw_response.get( + file_id="", + ) + + +class TestAsyncFiles: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create(self, async_client: AsyncReplicate) -> None: + file = await async_client.files.create( + content=b"raw file contents", + filename="filename", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncReplicate) -> None: + file = await async_client.files.create( + content=b"raw file contents", + filename="filename", + metadata={}, + type="type", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_create(self, async_client: AsyncReplicate) -> None: + response = await async_client.files.with_raw_response.create( + content=b"raw file contents", + filename="filename", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileCreateResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_create(self, async_client: AsyncReplicate) -> None: + async with async_client.files.with_streaming_response.create( + content=b"raw file contents", + filename="filename", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileCreateResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_list(self, async_client: AsyncReplicate) -> None: + file = await async_client.files.list() + assert_matches_type(AsyncCursorURLPage[FileListResponse], file, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list(self, async_client: AsyncReplicate) -> None: + response = await async_client.files.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(AsyncCursorURLPage[FileListResponse], file, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list(self, async_client: AsyncReplicate) -> None: + async with async_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(AsyncCursorURLPage[FileListResponse], file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_delete(self, async_client: AsyncReplicate) -> None: + file = await async_client.files.delete( + file_id="file_id", + ) + assert file is None + + @pytest.mark.skip() + @parametrize + async def test_raw_response_delete(self, async_client: AsyncReplicate) -> None: + response = await async_client.files.with_raw_response.delete( + file_id="file_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert file is None + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncReplicate) -> None: + async with async_client.files.with_streaming_response.delete( + file_id="file_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert file is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_delete(self, async_client: AsyncReplicate) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.files.with_raw_response.delete( + file_id="", + ) + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_download(self, async_client: AsyncReplicate, respx_mock: MockRouter) -> None: + respx_mock.get("/files/file_id/download").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + file = await async_client.files.download( + file_id="file_id", + expiry=0, + owner="owner", + signature="signature", + ) + assert file.is_closed + assert await file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, AsyncBinaryAPIResponse) + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_download(self, async_client: AsyncReplicate, respx_mock: MockRouter) -> None: + respx_mock.get("/files/file_id/download").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + file = await async_client.files.with_raw_response.download( + file_id="file_id", + expiry=0, + owner="owner", + signature="signature", + ) + + assert file.is_closed is True + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + assert await file.json() == {"foo": "bar"} + assert isinstance(file, AsyncBinaryAPIResponse) + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_download(self, async_client: AsyncReplicate, respx_mock: MockRouter) -> None: + respx_mock.get("/files/file_id/download").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.files.with_streaming_response.download( + file_id="file_id", + expiry=0, + owner="owner", + signature="signature", + ) as file: + assert not file.is_closed + assert file.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await file.json() == {"foo": "bar"} + assert cast(Any, file.is_closed) is True + assert isinstance(file, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, file.is_closed) is True + + @pytest.mark.skip() + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_download(self, async_client: AsyncReplicate) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.files.with_raw_response.download( + file_id="", + expiry=0, + owner="owner", + signature="signature", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_get(self, async_client: AsyncReplicate) -> None: + file = await async_client.files.get( + file_id="file_id", + ) + assert_matches_type(FileGetResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_get(self, async_client: AsyncReplicate) -> None: + response = await async_client.files.with_raw_response.get( + file_id="file_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + file = await response.parse() + assert_matches_type(FileGetResponse, file, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_get(self, async_client: AsyncReplicate) -> None: + async with async_client.files.with_streaming_response.get( + file_id="file_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + file = await response.parse() + assert_matches_type(FileGetResponse, file, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_get(self, async_client: AsyncReplicate) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"): + await async_client.files.with_raw_response.get( + file_id="", + ) diff --git a/tests/conftest.py b/tests/conftest.py index 79342b0..f6ec554 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,7 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -bearer_token = "My Bearer Token" +api_key = "My API Key" @pytest.fixture(scope="session") @@ -37,7 +37,7 @@ def client(request: FixtureRequest) -> Iterator[Replicate]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict) as client: + with Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: yield client @@ -47,7 +47,5 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncReplicate] if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - async with AsyncReplicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict - ) as client: + async with AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 3ffb5ab..cb1cdd1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -36,7 +36,7 @@ from .utils import update_env base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -bearer_token = "My Bearer Token" +api_key = "My API Key" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -58,7 +58,7 @@ def _get_open_connections(client: Replicate | AsyncReplicate) -> int: class TestReplicate: - client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -84,9 +84,9 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(bearer_token="another My Bearer Token") - assert copied.bearer_token == "another My Bearer Token" - assert self.client.bearer_token == "My Bearer Token" + copied = self.client.copy(api_key="another My API Key") + assert copied.api_key == "another My API Key" + assert self.client.api_key == "My API Key" def test_copy_default_options(self) -> None: # options that have a default are overridden correctly @@ -106,10 +106,7 @@ def test_copy_default_options(self) -> None: def test_copy_default_headers(self) -> None: client = Replicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) assert client.default_headers["X-Foo"] == "bar" @@ -143,7 +140,7 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = Replicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -268,7 +265,7 @@ def test_request_timeout(self) -> None: def test_client_timeout_option(self) -> None: client = Replicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0) ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -279,7 +276,7 @@ def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: client = Replicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -289,7 +286,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = Replicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -299,7 +296,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = Replicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -311,17 +308,14 @@ async def test_invalid_http_client(self) -> None: async with httpx.AsyncClient() as http_client: Replicate( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=cast(Any, http_client), ) def test_default_headers_option(self) -> None: client = Replicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" @@ -329,7 +323,7 @@ def test_default_headers_option(self) -> None: client2 = Replicate( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -341,21 +335,18 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_validate_headers(self) -> None: - client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {bearer_token}" + assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(ReplicateError): - with update_env(**{"REPLICATE_API_TOKEN": Omit()}): - client2 = Replicate(base_url=base_url, bearer_token=None, _strict_response_validation=True) + with update_env(**{"REPLICATE_CLIENT_API_KEY": Omit()}): + client2 = Replicate(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: client = Replicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_query={"query_param": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -555,9 +546,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Replicate( - base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True - ) + client = Replicate(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -566,20 +555,16 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(REPLICATE_BASE_URL="http://localhost:5000/from/env"): - client = Replicate(bearer_token=bearer_token, _strict_response_validation=True) + client = Replicate(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ + Replicate(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Replicate( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, - ), - Replicate( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -599,14 +584,10 @@ def test_base_url_trailing_slash(self, client: Replicate) -> None: @pytest.mark.parametrize( "client", [ + Replicate(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Replicate( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, - ), - Replicate( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -626,14 +607,10 @@ def test_base_url_no_trailing_slash(self, client: Replicate) -> None: @pytest.mark.parametrize( "client", [ + Replicate(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Replicate( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, - ), - Replicate( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -651,7 +628,7 @@ def test_absolute_request_url(self, client: Replicate) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -662,7 +639,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -683,12 +660,7 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Replicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - max_retries=cast(Any, None), - ) + Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -697,12 +669,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + strict_client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False) + client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -730,7 +702,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -836,7 +808,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: class TestAsyncReplicate: - client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -864,9 +836,9 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(bearer_token="another My Bearer Token") - assert copied.bearer_token == "another My Bearer Token" - assert self.client.bearer_token == "My Bearer Token" + copied = self.client.copy(api_key="another My API Key") + assert copied.api_key == "another My API Key" + assert self.client.api_key == "My API Key" def test_copy_default_options(self) -> None: # options that have a default are overridden correctly @@ -886,10 +858,7 @@ def test_copy_default_options(self) -> None: def test_copy_default_headers(self) -> None: client = AsyncReplicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) assert client.default_headers["X-Foo"] == "bar" @@ -923,7 +892,7 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = AsyncReplicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -1048,7 +1017,7 @@ async def test_request_timeout(self) -> None: async def test_client_timeout_option(self) -> None: client = AsyncReplicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0) ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1059,7 +1028,7 @@ async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: client = AsyncReplicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1069,7 +1038,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncReplicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1079,7 +1048,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncReplicate( - base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1091,17 +1060,14 @@ def test_invalid_http_client(self) -> None: with httpx.Client() as http_client: AsyncReplicate( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=cast(Any, http_client), ) def test_default_headers_option(self) -> None: client = AsyncReplicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_headers={"X-Foo": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" @@ -1109,7 +1075,7 @@ def test_default_headers_option(self) -> None: client2 = AsyncReplicate( base_url=base_url, - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1121,21 +1087,18 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_validate_headers(self) -> None: - client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {bearer_token}" + assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(ReplicateError): - with update_env(**{"REPLICATE_API_TOKEN": Omit()}): - client2 = AsyncReplicate(base_url=base_url, bearer_token=None, _strict_response_validation=True) + with update_env(**{"REPLICATE_CLIENT_API_KEY": Omit()}): + client2 = AsyncReplicate(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: client = AsyncReplicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - default_query={"query_param": "bar"}, + base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -1336,7 +1299,7 @@ class Model(BaseModel): def test_base_url_setter(self) -> None: client = AsyncReplicate( - base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True + base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True ) assert client.base_url == "https://example.com/from_init/" @@ -1346,20 +1309,18 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(REPLICATE_BASE_URL="http://localhost:5000/from/env"): - client = AsyncReplicate(bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncReplicate(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ AsyncReplicate( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), AsyncReplicate( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1380,13 +1341,11 @@ def test_base_url_trailing_slash(self, client: AsyncReplicate) -> None: "client", [ AsyncReplicate( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), AsyncReplicate( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1407,13 +1366,11 @@ def test_base_url_no_trailing_slash(self, client: AsyncReplicate) -> None: "client", [ AsyncReplicate( - base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, - _strict_response_validation=True, + base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), AsyncReplicate( base_url="http://localhost:5000/custom/path/", - bearer_token=bearer_token, + api_key=api_key, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1431,7 +1388,7 @@ def test_absolute_request_url(self, client: AsyncReplicate) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1443,7 +1400,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1466,10 +1423,7 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): AsyncReplicate( - base_url=base_url, - bearer_token=bearer_token, - _strict_response_validation=True, - max_retries=cast(Any, None), + base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None) ) @pytest.mark.respx(base_url=base_url) @@ -1480,12 +1434,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + strict_client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False) + client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1514,7 +1468,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) diff --git a/tests/test_module_client.py b/tests/test_module_client.py index 327a9a0..2f00654 100644 --- a/tests/test_module_client.py +++ b/tests/test_module_client.py @@ -12,7 +12,7 @@ def reset_state() -> None: replicate._reset_client() - replicate.bearer_token = None or "My Bearer Token" + replicate.api_key = None or "My API Key" replicate.base_url = None replicate.timeout = DEFAULT_TIMEOUT replicate.max_retries = DEFAULT_MAX_RETRIES From 5a9b95ce89e536b539eefe0864a47784fdb0ec08 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 18:24:56 +0000 Subject: [PATCH 3/5] fix(api): fix client_settings.opts.api_key.read_env This regressed when using the "Guess with AI" feature. See https://replicatehq.slack.com/archives/C08H2L2E0KT/p1746642117876339 --- .stats.yml | 2 +- README.md | 6 +++--- src/replicate/_client.py | 12 ++++++------ tests/test_client.py | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.stats.yml b/.stats.yml index d97e266..ba6ef75 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 35 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/replicate%2Freplicate-client-efbc8cc2d74644b213e161d3e11e0589d1cef181fb318ea02c8eb6b00f245713.yml openapi_spec_hash: 13da0c06c900b61cd98ab678e024987a -config_hash: e25b98bb6202d27e54fc6e08c7de80d8 +config_hash: 8e80c2bf93f71823213f5773dd297921 diff --git a/README.md b/README.md index a1ab424..f7cc3b9 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ import os from replicate import Replicate client = Replicate( - api_key=os.environ.get("REPLICATE_CLIENT_API_KEY"), # This is the default and can be omitted + api_key=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted ) account = client.account.get() @@ -37,7 +37,7 @@ print(account.type) While you can provide an `api_key` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `REPLICATE_CLIENT_API_KEY="My API Key"` to your `.env` file +to add `REPLICATE_API_TOKEN="My API Key"` to your `.env` file so that your API Key is not stored in source control. ## Async usage @@ -50,7 +50,7 @@ import asyncio from replicate import AsyncReplicate client = AsyncReplicate( - api_key=os.environ.get("REPLICATE_CLIENT_API_KEY"), # This is the default and can be omitted + api_key=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted ) diff --git a/src/replicate/_client.py b/src/replicate/_client.py index 055388f..ce0d6d8 100644 --- a/src/replicate/_client.py +++ b/src/replicate/_client.py @@ -83,13 +83,13 @@ def __init__( ) -> None: """Construct a new synchronous Replicate client instance. - This automatically infers the `api_key` argument from the `REPLICATE_CLIENT_API_KEY` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. """ if api_key is None: - api_key = os.environ.get("REPLICATE_CLIENT_API_KEY") + api_key = os.environ.get("REPLICATE_API_TOKEN") if api_key is None: raise ReplicateError( - "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_CLIENT_API_KEY environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_API_TOKEN environment variable" ) self.api_key = api_key @@ -305,13 +305,13 @@ def __init__( ) -> None: """Construct a new async AsyncReplicate client instance. - This automatically infers the `api_key` argument from the `REPLICATE_CLIENT_API_KEY` environment variable if it is not provided. + This automatically infers the `api_key` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. """ if api_key is None: - api_key = os.environ.get("REPLICATE_CLIENT_API_KEY") + api_key = os.environ.get("REPLICATE_API_TOKEN") if api_key is None: raise ReplicateError( - "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_CLIENT_API_KEY environment variable" + "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_API_TOKEN environment variable" ) self.api_key = api_key diff --git a/tests/test_client.py b/tests/test_client.py index cb1cdd1..c181768 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -340,7 +340,7 @@ def test_validate_headers(self) -> None: assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(ReplicateError): - with update_env(**{"REPLICATE_CLIENT_API_KEY": Omit()}): + with update_env(**{"REPLICATE_API_TOKEN": Omit()}): client2 = Replicate(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 @@ -1092,7 +1092,7 @@ def test_validate_headers(self) -> None: assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(ReplicateError): - with update_env(**{"REPLICATE_CLIENT_API_KEY": Omit()}): + with update_env(**{"REPLICATE_API_TOKEN": Omit()}): client2 = AsyncReplicate(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 From 13162be9d367de29d222b86506fa921a10800665 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 18:38:56 +0000 Subject: [PATCH 4/5] feat(api): fix bearer token which also regressed when guessing with AI --- .stats.yml | 2 +- README.md | 10 +-- src/replicate/__init__.py | 16 ++-- src/replicate/_client.py | 48 +++++----- tests/conftest.py | 8 +- tests/test_client.py | 172 +++++++++++++++++++++++------------- tests/test_module_client.py | 2 +- 7 files changed, 153 insertions(+), 105 deletions(-) diff --git a/.stats.yml b/.stats.yml index ba6ef75..7349579 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 35 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/replicate%2Freplicate-client-efbc8cc2d74644b213e161d3e11e0589d1cef181fb318ea02c8eb6b00f245713.yml openapi_spec_hash: 13da0c06c900b61cd98ab678e024987a -config_hash: 8e80c2bf93f71823213f5773dd297921 +config_hash: 8ef6787524fd12bfeb27f8c6acef3dca diff --git a/README.md b/README.md index f7cc3b9..23b8b71 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,17 @@ import os from replicate import Replicate client = Replicate( - api_key=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted + bearer_token=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted ) account = client.account.get() print(account.type) ``` -While you can provide an `api_key` keyword argument, +While you can provide a `bearer_token` keyword argument, we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `REPLICATE_API_TOKEN="My API Key"` to your `.env` file -so that your API Key is not stored in source control. +to add `REPLICATE_API_TOKEN="My Bearer Token"` to your `.env` file +so that your Bearer Token is not stored in source control. ## Async usage @@ -50,7 +50,7 @@ import asyncio from replicate import AsyncReplicate client = AsyncReplicate( - api_key=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted + bearer_token=os.environ.get("REPLICATE_API_TOKEN"), # This is the default and can be omitted ) diff --git a/src/replicate/__init__.py b/src/replicate/__init__.py index aee7ed5..7a38cc0 100644 --- a/src/replicate/__init__.py +++ b/src/replicate/__init__.py @@ -104,7 +104,7 @@ from ._base_client import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES -api_key: str | None = None +bearer_token: str | None = None base_url: str | _httpx.URL | None = None @@ -125,14 +125,14 @@ class _ModuleClient(Replicate): @property # type: ignore @override - def api_key(self) -> str | None: - return api_key + def bearer_token(self) -> str | None: + return bearer_token - @api_key.setter # type: ignore - def api_key(self, value: str | None) -> None: # type: ignore - global api_key + @bearer_token.setter # type: ignore + def bearer_token(self, value: str | None) -> None: # type: ignore + global bearer_token - api_key = value + bearer_token = value @property @override @@ -210,7 +210,7 @@ def _load_client() -> Replicate: # type: ignore[reportUnusedFunction] if _client is None: _client = _ModuleClient( - api_key=api_key, + bearer_token=bearer_token, base_url=base_url, timeout=timeout, max_retries=max_retries, diff --git a/src/replicate/_client.py b/src/replicate/_client.py index ce0d6d8..0fe93c6 100644 --- a/src/replicate/_client.py +++ b/src/replicate/_client.py @@ -56,12 +56,12 @@ class Replicate(SyncAPIClient): # client options - api_key: str + bearer_token: str def __init__( self, *, - api_key: str | None = None, + bearer_token: str | None = None, base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, @@ -83,15 +83,15 @@ def __init__( ) -> None: """Construct a new synchronous Replicate client instance. - This automatically infers the `api_key` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. + This automatically infers the `bearer_token` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. """ - if api_key is None: - api_key = os.environ.get("REPLICATE_API_TOKEN") - if api_key is None: + if bearer_token is None: + bearer_token = os.environ.get("REPLICATE_API_TOKEN") + if bearer_token is None: raise ReplicateError( - "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_API_TOKEN environment variable" + "The bearer_token client option must be set either by passing bearer_token to the client or by setting the REPLICATE_API_TOKEN environment variable" ) - self.api_key = api_key + self.bearer_token = bearer_token if base_url is None: base_url = os.environ.get("REPLICATE_BASE_URL") @@ -179,8 +179,8 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - api_key = self.api_key - return {"Authorization": f"Bearer {api_key}"} + bearer_token = self.bearer_token + return {"Authorization": f"Bearer {bearer_token}"} @property @override @@ -194,7 +194,7 @@ def default_headers(self) -> dict[str, str | Omit]: def copy( self, *, - api_key: str | None = None, + bearer_token: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.Client | None = None, @@ -228,7 +228,7 @@ def copy( http_client = http_client or self._client return self.__class__( - api_key=api_key or self.api_key, + bearer_token=bearer_token or self.bearer_token, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, @@ -278,12 +278,12 @@ def _make_status_error( class AsyncReplicate(AsyncAPIClient): # client options - api_key: str + bearer_token: str def __init__( self, *, - api_key: str | None = None, + bearer_token: str | None = None, base_url: str | httpx.URL | None = None, timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, max_retries: int = DEFAULT_MAX_RETRIES, @@ -305,15 +305,15 @@ def __init__( ) -> None: """Construct a new async AsyncReplicate client instance. - This automatically infers the `api_key` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. + This automatically infers the `bearer_token` argument from the `REPLICATE_API_TOKEN` environment variable if it is not provided. """ - if api_key is None: - api_key = os.environ.get("REPLICATE_API_TOKEN") - if api_key is None: + if bearer_token is None: + bearer_token = os.environ.get("REPLICATE_API_TOKEN") + if bearer_token is None: raise ReplicateError( - "The api_key client option must be set either by passing api_key to the client or by setting the REPLICATE_API_TOKEN environment variable" + "The bearer_token client option must be set either by passing bearer_token to the client or by setting the REPLICATE_API_TOKEN environment variable" ) - self.api_key = api_key + self.bearer_token = bearer_token if base_url is None: base_url = os.environ.get("REPLICATE_BASE_URL") @@ -401,8 +401,8 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: - api_key = self.api_key - return {"Authorization": f"Bearer {api_key}"} + bearer_token = self.bearer_token + return {"Authorization": f"Bearer {bearer_token}"} @property @override @@ -416,7 +416,7 @@ def default_headers(self) -> dict[str, str | Omit]: def copy( self, *, - api_key: str | None = None, + bearer_token: str | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.AsyncClient | None = None, @@ -450,7 +450,7 @@ def copy( http_client = http_client or self._client return self.__class__( - api_key=api_key or self.api_key, + bearer_token=bearer_token or self.bearer_token, base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, diff --git a/tests/conftest.py b/tests/conftest.py index f6ec554..79342b0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,7 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -api_key = "My API Key" +bearer_token = "My Bearer Token" @pytest.fixture(scope="session") @@ -37,7 +37,7 @@ def client(request: FixtureRequest) -> Iterator[Replicate]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + with Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict) as client: yield client @@ -47,5 +47,7 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncReplicate] if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - async with AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + async with AsyncReplicate( + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=strict + ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index c181768..3ffb5ab 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -36,7 +36,7 @@ from .utils import update_env base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -api_key = "My API Key" +bearer_token = "My Bearer Token" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -58,7 +58,7 @@ def _get_open_connections(client: Replicate | AsyncReplicate) -> int: class TestReplicate: - client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) def test_raw_response(self, respx_mock: MockRouter) -> None: @@ -84,9 +84,9 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(api_key="another My API Key") - assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + copied = self.client.copy(bearer_token="another My Bearer Token") + assert copied.bearer_token == "another My Bearer Token" + assert self.client.bearer_token == "My Bearer Token" def test_copy_default_options(self) -> None: # options that have a default are overridden correctly @@ -106,7 +106,10 @@ def test_copy_default_options(self) -> None: def test_copy_default_headers(self) -> None: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) assert client.default_headers["X-Foo"] == "bar" @@ -140,7 +143,7 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -265,7 +268,7 @@ def test_request_timeout(self) -> None: def test_client_timeout_option(self) -> None: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0) ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -276,7 +279,7 @@ def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -286,7 +289,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -296,7 +299,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -308,14 +311,17 @@ async def test_invalid_http_client(self) -> None: async with httpx.AsyncClient() as http_client: Replicate( base_url=base_url, - api_key=api_key, + bearer_token=bearer_token, _strict_response_validation=True, http_client=cast(Any, http_client), ) def test_default_headers_option(self) -> None: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" @@ -323,7 +329,7 @@ def test_default_headers_option(self) -> None: client2 = Replicate( base_url=base_url, - api_key=api_key, + bearer_token=bearer_token, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -335,18 +341,21 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_validate_headers(self) -> None: - client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {api_key}" + assert request.headers.get("Authorization") == f"Bearer {bearer_token}" with pytest.raises(ReplicateError): with update_env(**{"REPLICATE_API_TOKEN": Omit()}): - client2 = Replicate(base_url=base_url, api_key=None, _strict_response_validation=True) + client2 = Replicate(base_url=base_url, bearer_token=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: client = Replicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + default_query={"query_param": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -546,7 +555,9 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Replicate(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True) + client = Replicate( + base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True + ) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -555,16 +566,20 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(REPLICATE_BASE_URL="http://localhost:5000/from/env"): - client = Replicate(api_key=api_key, _strict_response_validation=True) + client = Replicate(bearer_token=bearer_token, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ - Replicate(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Replicate( base_url="http://localhost:5000/custom/path/", - api_key=api_key, + bearer_token=bearer_token, + _strict_response_validation=True, + ), + Replicate( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -584,10 +599,14 @@ def test_base_url_trailing_slash(self, client: Replicate) -> None: @pytest.mark.parametrize( "client", [ - Replicate(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Replicate( base_url="http://localhost:5000/custom/path/", - api_key=api_key, + bearer_token=bearer_token, + _strict_response_validation=True, + ), + Replicate( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -607,10 +626,14 @@ def test_base_url_no_trailing_slash(self, client: Replicate) -> None: @pytest.mark.parametrize( "client", [ - Replicate(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), Replicate( base_url="http://localhost:5000/custom/path/", - api_key=api_key, + bearer_token=bearer_token, + _strict_response_validation=True, + ), + Replicate( + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -628,7 +651,7 @@ def test_absolute_request_url(self, client: Replicate) -> None: assert request.url == "https://myapi.com/foo" def test_copied_client_does_not_close_http(self) -> None: - client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -639,7 +662,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() def test_client_context_manager(self) -> None: - client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) with client as c2: assert c2 is client assert not c2.is_closed() @@ -660,7 +683,12 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)) + Replicate( + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + max_retries=cast(Any, None), + ) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -669,12 +697,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + strict_client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=False) + client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False) response = client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -702,7 +730,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = Replicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = Replicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -808,7 +836,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: class TestAsyncReplicate: - client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) @pytest.mark.respx(base_url=base_url) @pytest.mark.asyncio @@ -836,9 +864,9 @@ def test_copy(self) -> None: copied = self.client.copy() assert id(copied) != id(self.client) - copied = self.client.copy(api_key="another My API Key") - assert copied.api_key == "another My API Key" - assert self.client.api_key == "My API Key" + copied = self.client.copy(bearer_token="another My Bearer Token") + assert copied.bearer_token == "another My Bearer Token" + assert self.client.bearer_token == "My Bearer Token" def test_copy_default_options(self) -> None: # options that have a default are overridden correctly @@ -858,7 +886,10 @@ def test_copy_default_options(self) -> None: def test_copy_default_headers(self) -> None: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) assert client.default_headers["X-Foo"] == "bar" @@ -892,7 +923,7 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -1017,7 +1048,7 @@ async def test_request_timeout(self) -> None: async def test_client_timeout_option(self) -> None: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, timeout=httpx.Timeout(0) ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1028,7 +1059,7 @@ async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1038,7 +1069,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1048,7 +1079,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client + base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True, http_client=http_client ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1060,14 +1091,17 @@ def test_invalid_http_client(self) -> None: with httpx.Client() as http_client: AsyncReplicate( base_url=base_url, - api_key=api_key, + bearer_token=bearer_token, _strict_response_validation=True, http_client=cast(Any, http_client), ) def test_default_headers_option(self) -> None: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + default_headers={"X-Foo": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" @@ -1075,7 +1109,7 @@ def test_default_headers_option(self) -> None: client2 = AsyncReplicate( base_url=base_url, - api_key=api_key, + bearer_token=bearer_token, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1087,18 +1121,21 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-stainless-lang") == "my-overriding-header" def test_validate_headers(self) -> None: - client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("Authorization") == f"Bearer {api_key}" + assert request.headers.get("Authorization") == f"Bearer {bearer_token}" with pytest.raises(ReplicateError): with update_env(**{"REPLICATE_API_TOKEN": Omit()}): - client2 = AsyncReplicate(base_url=base_url, api_key=None, _strict_response_validation=True) + client2 = AsyncReplicate(base_url=base_url, bearer_token=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: client = AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + default_query={"query_param": "bar"}, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) url = httpx.URL(request.url) @@ -1299,7 +1336,7 @@ class Model(BaseModel): def test_base_url_setter(self) -> None: client = AsyncReplicate( - base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True + base_url="https://example.com/from_init", bearer_token=bearer_token, _strict_response_validation=True ) assert client.base_url == "https://example.com/from_init/" @@ -1309,18 +1346,20 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(REPLICATE_BASE_URL="http://localhost:5000/from/env"): - client = AsyncReplicate(api_key=api_key, _strict_response_validation=True) + client = AsyncReplicate(bearer_token=bearer_token, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( "client", [ AsyncReplicate( - base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + _strict_response_validation=True, ), AsyncReplicate( base_url="http://localhost:5000/custom/path/", - api_key=api_key, + bearer_token=bearer_token, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1341,11 +1380,13 @@ def test_base_url_trailing_slash(self, client: AsyncReplicate) -> None: "client", [ AsyncReplicate( - base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + _strict_response_validation=True, ), AsyncReplicate( base_url="http://localhost:5000/custom/path/", - api_key=api_key, + bearer_token=bearer_token, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1366,11 +1407,13 @@ def test_base_url_no_trailing_slash(self, client: AsyncReplicate) -> None: "client", [ AsyncReplicate( - base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True + base_url="http://localhost:5000/custom/path/", + bearer_token=bearer_token, + _strict_response_validation=True, ), AsyncReplicate( base_url="http://localhost:5000/custom/path/", - api_key=api_key, + bearer_token=bearer_token, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1388,7 +1431,7 @@ def test_absolute_request_url(self, client: AsyncReplicate) -> None: assert request.url == "https://myapi.com/foo" async def test_copied_client_does_not_close_http(self) -> None: - client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) assert not client.is_closed() copied = client.copy() @@ -1400,7 +1443,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not client.is_closed() async def test_client_context_manager(self) -> None: - client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) async with client as c2: assert c2 is client assert not c2.is_closed() @@ -1423,7 +1466,10 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): AsyncReplicate( - base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None) + base_url=base_url, + bearer_token=bearer_token, + _strict_response_validation=True, + max_retries=cast(Any, None), ) @pytest.mark.respx(base_url=base_url) @@ -1434,12 +1480,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + strict_client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=False) + client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=False) response = await client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1468,7 +1514,7 @@ class Model(BaseModel): @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @pytest.mark.asyncio async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None: - client = AsyncReplicate(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) diff --git a/tests/test_module_client.py b/tests/test_module_client.py index 2f00654..327a9a0 100644 --- a/tests/test_module_client.py +++ b/tests/test_module_client.py @@ -12,7 +12,7 @@ def reset_state() -> None: replicate._reset_client() - replicate.api_key = None or "My API Key" + replicate.bearer_token = None or "My Bearer Token" replicate.base_url = None replicate.timeout = DEFAULT_TIMEOUT replicate.max_retries = DEFAULT_MAX_RETRIES From 94d17eeca0f7ee812a4bf00aa31fe66411d8df51 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 May 2025 18:39:12 +0000 Subject: [PATCH 5/5] release: 0.2.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ pyproject.toml | 2 +- src/replicate/_version.py | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3d2ac0b..10f3091 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0" + ".": "0.2.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 654c222..94d8543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.2.0 (2025-05-07) + +Full Changelog: [v0.1.0...v0.2.0](https://github.com/replicate/replicate-python-stainless/compare/v0.1.0...v0.2.0) + +### Features + +* **api:** add Files API methods ([3173e5f](https://github.com/replicate/replicate-python-stainless/commit/3173e5f61edd89ffe0b64b53fc8e8e9905e145e4)) +* **api:** fix bearer token which also regressed when guessing with AI ([13162be](https://github.com/replicate/replicate-python-stainless/commit/13162be9d367de29d222b86506fa921a10800665)) + + +### Bug Fixes + +* **api:** fix client_settings.opts.api_key.read_env ([5a9b95c](https://github.com/replicate/replicate-python-stainless/commit/5a9b95ce89e536b539eefe0864a47784fdb0ec08)) + ## 0.1.0 (2025-05-07) Full Changelog: [v0.1.0-alpha.10...v0.1.0](https://github.com/replicate/replicate-python-stainless/compare/v0.1.0-alpha.10...v0.1.0) diff --git a/pyproject.toml b/pyproject.toml index 46f2b41..aee2e9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "replicate-stainless" -version = "0.1.0" +version = "0.2.0" description = "The official Python library for the replicate API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/replicate/_version.py b/src/replicate/_version.py index ba344a8..1f94a83 100644 --- a/src/replicate/_version.py +++ b/src/replicate/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "replicate" -__version__ = "0.1.0" # x-release-please-version +__version__ = "0.2.0" # x-release-please-version