diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 521804d..19cfee5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.0.0-alpha.10" + ".": "2.0.0-alpha.11" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index f09ad10..8301e7f 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-d174b63c516640a0987bbb0879d1cb435ecf6cbcfe7c9f6465bfcc5416eff8e1.yml openapi_spec_hash: 46f69b0827b955f2adf93928fcfb2c57 -config_hash: 71c9e54dad5d546e9a98afe5edc6742b +config_hash: 218f95f3af761e55867d28b8d75464c8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 865971d..6095bb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 2.0.0-alpha.11 (2025-07-12) + +Full Changelog: [v2.0.0-alpha.10...v2.0.0-alpha.11](https://github.com/replicate/replicate-python-stainless/compare/v2.0.0-alpha.10...v2.0.0-alpha.11) + +### Bug Fixes + +* **client:** don't send Content-Type header on GET requests ([32d3401](https://github.com/replicate/replicate-python-stainless/commit/32d3401e0180ab91d7e8aba5959d15dae8640814)) +* **parsing:** correctly handle nested discriminated unions ([2aaaab1](https://github.com/replicate/replicate-python-stainless/commit/2aaaab1999f44d7ee8581cc7466e3be04360f4c7)) + + +### Chores + +* **internal:** bump pinned h11 dep ([c3101fc](https://github.com/replicate/replicate-python-stainless/commit/c3101fc57487b08ea55344b8eff1308bb92290cf)) +* **package:** mark python 3.13 as supported ([1263862](https://github.com/replicate/replicate-python-stainless/commit/1263862868ee1a2750eb8b66d6b2f76c0b6f753d)) +* **readme:** fix version rendering on pypi ([626518b](https://github.com/replicate/replicate-python-stainless/commit/626518b846fd2dcb02ca21e1054a8fd4f0849130)) + ## 2.0.0-alpha.10 (2025-07-08) Full Changelog: [v2.0.0-alpha.9...v2.0.0-alpha.10](https://github.com/replicate/replicate-python-stainless/compare/v2.0.0-alpha.9...v2.0.0-alpha.10) diff --git a/README.md b/README.md index f21ffc3..99756fc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Replicate Python API library -[![PyPI version]()](https://pypi.org/project/replicate/) + +[![PyPI version](https://img.shields.io/pypi/v/replicate.svg?label=pypi%20(stable))](https://pypi.org/project/replicate/) The Replicate Python library provides convenient access to the Replicate REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, diff --git a/pyproject.toml b/pyproject.toml index 47bca44..8ba0b24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "replicate" -version = "2.0.0-alpha.10" +version = "2.0.0-alpha.11" description = "The official Python library for the replicate API" dynamic = ["readme"] license = "Apache-2.0" @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", @@ -38,7 +39,7 @@ Homepage = "https://github.com/replicate/replicate-python-stainless" Repository = "https://github.com/replicate/replicate-python-stainless" [project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index 2468db9..b9364b9 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,9 +48,9 @@ filelock==3.12.4 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via httpx-aiohttp diff --git a/requirements.lock b/requirements.lock index e419018..d76f084 100644 --- a/requirements.lock +++ b/requirements.lock @@ -36,9 +36,9 @@ exceptiongroup==1.2.2 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via httpx-aiohttp diff --git a/src/replicate/_base_client.py b/src/replicate/_base_client.py index 43f54b6..3031e5c 100644 --- a/src/replicate/_base_client.py +++ b/src/replicate/_base_client.py @@ -529,6 +529,15 @@ def _build_request( # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + kwargs["json"] = json_data if is_given(json_data) else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, @@ -540,8 +549,6 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data if is_given(json_data) else None, - files=files, **kwargs, ) diff --git a/src/replicate/_models.py b/src/replicate/_models.py index 4f21498..528d568 100644 --- a/src/replicate/_models.py +++ b/src/replicate/_models.py @@ -2,9 +2,10 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, @@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) def is_basemodel(type_: type) -> bool: @@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. @@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object: type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() diff --git a/src/replicate/_version.py b/src/replicate/_version.py index 2b85993..203297c 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__ = "2.0.0-alpha.10" # x-release-please-version +__version__ = "2.0.0-alpha.11" # x-release-please-version diff --git a/tests/test_client.py b/tests/test_client.py index 3af3ff8..607533a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -473,7 +473,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: Replicate) -> None: request = client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -1319,7 +1319,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncReplicate) -> None: request = async_client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, diff --git a/tests/test_models.py b/tests/test_models.py index cf8173c..e39dc96 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -889,3 +889,48 @@ class ModelB(BaseModel): ) assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2)