diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ba231b07..9e1cdeda 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.21.0" + ".": "1.21.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db87fef..1c10e20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 1.21.1 (2025-04-11) + +Full Changelog: [v1.21.0...v1.21.1](https://github.com/Finch-API/finch-api-python/compare/v1.21.0...v1.21.1) + +### Bug Fixes + +* **perf:** optimize some hot paths ([4fe846e](https://github.com/Finch-API/finch-api-python/commit/4fe846ea14d90fb142eb1d2b3d7fa259326604f8)) +* **perf:** skip traversing types for NotGiven values ([c6df147](https://github.com/Finch-API/finch-api-python/commit/c6df1471d194dcad32a5ab771f8a7fe19e904f9f)) + ## 1.21.0 (2025-04-09) Full Changelog: [v1.20.1...v1.21.0](https://github.com/Finch-API/finch-api-python/compare/v1.20.1...v1.21.0) diff --git a/pyproject.toml b/pyproject.toml index 0080f7bf..241689ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "finch-api" -version = "1.21.0" +version = "1.21.1" description = "The official Python library for the Finch API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/finch/_utils/_transform.py b/src/finch/_utils/_transform.py index 3ec62081..b0cc20a7 100644 --- a/src/finch/_utils/_transform.py +++ b/src/finch/_utils/_transform.py @@ -5,13 +5,15 @@ import pathlib from typing import Any, Mapping, TypeVar, cast from datetime import date, datetime -from typing_extensions import Literal, get_args, override, get_type_hints +from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints import anyio import pydantic from ._utils import ( is_list, + is_given, + lru_cache, is_mapping, is_iterable, ) @@ -108,6 +110,7 @@ class Params(TypedDict, total=False): return cast(_T, transformed) +@lru_cache(maxsize=8096) def _get_annotated_type(type_: type) -> type | None: """If the given type is an `Annotated` type then it is returned, if not `None` is returned. @@ -258,6 +261,11 @@ def _transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include `NotGiven` values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -415,6 +423,11 @@ async def _async_transform_typeddict( result: dict[str, object] = {} annotations = get_type_hints(expected_type, include_extras=True) for key, value in data.items(): + if not is_given(value): + # we don't need to include `NotGiven` values here as they'll + # be stripped out before the request is sent anyway + continue + type_ = annotations.get(key) if type_ is None: # we do not have a type annotation for this field, leave it as is @@ -422,3 +435,13 @@ async def _async_transform_typeddict( else: result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_) return result + + +@lru_cache(maxsize=8096) +def get_type_hints( + obj: Any, + globalns: dict[str, Any] | None = None, + localns: Mapping[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) diff --git a/src/finch/_utils/_typing.py b/src/finch/_utils/_typing.py index 278749b1..1958820f 100644 --- a/src/finch/_utils/_typing.py +++ b/src/finch/_utils/_typing.py @@ -13,6 +13,7 @@ get_origin, ) +from ._utils import lru_cache from .._types import InheritsGeneric from .._compat import is_union as _is_union @@ -66,6 +67,7 @@ def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] +@lru_cache(maxsize=8096) def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): return strip_annotated_type(cast(type, get_args(typ)[0])) diff --git a/src/finch/_version.py b/src/finch/_version.py index 4bdbe1a5..525c4d96 100644 --- a/src/finch/_version.py +++ b/src/finch/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "finch" -__version__ = "1.21.0" # x-release-please-version +__version__ = "1.21.1" # x-release-please-version diff --git a/tests/test_transform.py b/tests/test_transform.py index 80271b12..6ea64018 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -8,7 +8,7 @@ import pytest -from finch._types import Base64FileInput +from finch._types import NOT_GIVEN, Base64FileInput from finch._utils import ( PropertyInfo, transform as _transform, @@ -444,3 +444,10 @@ async def test_transform_skipping(use_async: bool) -> None: # iterables of ints are converted to a list data = iter([1, 2, 3]) assert await transform(data, Iterable[int], use_async) == [1, 2, 3] + + +@parametrize +@pytest.mark.asyncio +async def test_strips_notgiven(use_async: bool) -> None: + assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"} + assert await transform({"foo_bar": NOT_GIVEN}, Foo1, use_async) == {}