diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8d0e94f..5a0c69c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: - name: Get GitHub OIDC Token if: github.repository == 'stainless-sdks/finch-python' id: github-oidc - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1ee5dee6..6d2723c7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.44.1" + ".": "1.45.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index b15bfab0..abcde50d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-46f433f34d440aa1dfcc48cc8d822c598571b68be2f723ec99e1b4fba6c13b1e.yml -openapi_spec_hash: 5b5cd728776723ac773900f7e8a32c05 -config_hash: 0892e2e0eeb0343a022afa62e9080dd1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-093ade6f1d3115b654a73b97855fbe334c9f9c5d906081dad2ec973ab0c0b24d.yml +openapi_spec_hash: 7cc27b8e483d9db9c411875289c42eb9 +config_hash: d21a244fc073152c8dbecb8ece970209 diff --git a/CHANGELOG.md b/CHANGELOG.md index 809425a0..c16e5113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 1.45.0 (2026-03-10) + +Full Changelog: [v1.44.1...v1.45.0](https://github.com/Finch-API/finch-api-python/compare/v1.44.1...v1.45.0) + +### Features + +* **api:** add per endpoint security ([686b27e](https://github.com/Finch-API/finch-api-python/commit/686b27e6d19cc5858b420e2b1bb265f50791c7ea)) +* **api:** api update ([c6631fc](https://github.com/Finch-API/finch-api-python/commit/c6631fc8d74f89931be556746c6ce67e4cb62867)) +* **client:** add custom JSON encoder for extended type support ([02cb5a0](https://github.com/Finch-API/finch-api-python/commit/02cb5a0bdcdbd8b4652937928ca7d0b336f6c53c)) + + +### Bug Fixes + +* **api:** remove invalid transform config ([565de84](https://github.com/Finch-API/finch-api-python/commit/565de8480f61bf07d5dbd0fb39fbb8b4f5ea9671)) +* **docs:** fix mcp installation instructions for remote servers ([298ddec](https://github.com/Finch-API/finch-api-python/commit/298ddec8fded9a72146800253d644cb96227cbde)) +* **tests:** skip broken date validation test ([e22eb0f](https://github.com/Finch-API/finch-api-python/commit/e22eb0ffed3e42143600c931f2ab5b342160230c)) + + +### Chores + +* **ci:** upgrade `actions/github-script` ([f342729](https://github.com/Finch-API/finch-api-python/commit/f3427295a99f7aa4a45412f699ec6b3cc41b16c2)) +* format all `api.md` files ([8555964](https://github.com/Finch-API/finch-api-python/commit/8555964275d168e400ef190422baee5a1983eeb1)) +* **internal:** add request options to SSE classes ([977dcfa](https://github.com/Finch-API/finch-api-python/commit/977dcfa5e7da78f0d4b465843e52a4fd2999799b)) +* **internal:** bump dependencies ([0b67a64](https://github.com/Finch-API/finch-api-python/commit/0b67a64b36f6824c1000d126e5ed707e495c3c59)) +* **internal:** codegen related update ([b746d12](https://github.com/Finch-API/finch-api-python/commit/b746d12f98b1fcb287bf1b7844fc8a2d864a7d86)) +* **internal:** fix lint error on Python 3.14 ([b0f5ba3](https://github.com/Finch-API/finch-api-python/commit/b0f5ba3c117a989524c31e6c62ab9215f98fccd2)) +* **internal:** make `test_proxy_environment_variables` more resilient ([e33584f](https://github.com/Finch-API/finch-api-python/commit/e33584fc7bc4aaaa73377f8436f9858190266236)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([9f092c7](https://github.com/Finch-API/finch-api-python/commit/9f092c7d989fbe863c8d8624238694c564a871f0)) +* update mock server docs ([7891a91](https://github.com/Finch-API/finch-api-python/commit/7891a91c042ae55b9aeaec5980b81197b8692039)) + ## 1.44.1 (2026-01-16) Full Changelog: [v1.44.0...v1.44.1](https://github.com/Finch-API/finch-api-python/compare/v1.44.0...v1.44.1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21f44701..3eb5807c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,8 +88,7 @@ $ pip install ./path-to-wheel-file.whl Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. ```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml +$ ./scripts/mock ``` ```sh diff --git a/README.md b/README.md index 70cc1a8b..72f61d2a 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Finch MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40tryfinch%2Ffinch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl19) -[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40tryfinch%2Ffinch-api-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40tryfinch%2Ffinch-api-mcp%22%5D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40tryfinch%2Ffinch-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0cnlmaW5jaC9maW5jaC1hcGktbWNwIl0sImVudiI6eyJGSU5DSF9BQ0NFU1NfVE9LRU4iOiJNeSBBY2Nlc3MgVG9rZW4iLCJGSU5DSF9DTElFTlRfSUQiOiI0YWIxNWU1MS0xMWFkLTQ5ZjQtYWNhZS1mMzQzYjc3OTQzNzUiLCJGSU5DSF9DTElFTlRfU0VDUkVUIjoiTXkgQ2xpZW50IFNlY3JldCIsIkZJTkNIX1dFQkhPT0tfU0VDUkVUIjoiTXkgV2ViaG9vayBTZWNyZXQifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40tryfinch%2Ffinch-api-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40tryfinch%2Ffinch-api-mcp%22%5D%2C%22env%22%3A%7B%22FINCH_ACCESS_TOKEN%22%3A%22My%20Access%20Token%22%2C%22FINCH_CLIENT_ID%22%3A%224ab15e51-11ad-49f4-acae-f343b7794375%22%2C%22FINCH_CLIENT_SECRET%22%3A%22My%20Client%20Secret%22%2C%22FINCH_WEBHOOK_SECRET%22%3A%22My%20Webhook%20Secret%22%7D%7D) > Note: You may need to set environment variables in your MCP client. diff --git a/pyproject.toml b/pyproject.toml index 5907bdab..ba656654 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "finch-api" -version = "1.44.1" +version = "1.45.0" description = "The official Python library for the Finch API" dynamic = ["readme"] license = "Apache-2.0" @@ -69,7 +69,7 @@ format = { chain = [ # run formatting again to fix any inconsistencies when imports are stripped "format:ruff", ]} -"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" +"format:docs" = "bash -c 'python scripts/utils/ruffen-docs.py README.md $(find . -type f -name api.md)'" "format:ruff" = "ruff format" "lint" = { chain = [ diff --git a/requirements-dev.lock b/requirements-dev.lock index 96c9c174..75467572 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,14 +12,14 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.2 +aiohttp==3.13.3 # via finch-api # via httpx-aiohttp aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.12.0 +anyio==4.12.1 # via finch-api # via httpx argcomplete==3.6.3 @@ -31,7 +31,7 @@ attrs==25.4.0 # via nox backports-asyncio-runner==1.2.0 # via pytest-asyncio -certifi==2025.11.12 +certifi==2026.1.4 # via httpcore # via httpx colorlog==6.10.1 @@ -61,7 +61,7 @@ httpx==0.28.1 # via finch-api # via httpx-aiohttp # via respx -httpx-aiohttp==0.1.9 +httpx-aiohttp==0.1.12 # via finch-api humanize==4.13.0 # via nox @@ -69,7 +69,7 @@ idna==3.11 # via anyio # via httpx # via yarl -importlib-metadata==8.7.0 +importlib-metadata==8.7.1 iniconfig==2.1.0 # via pytest markdown-it-py==3.0.0 @@ -82,14 +82,14 @@ multidict==6.7.0 mypy==1.17.0 mypy-extensions==1.1.0 # via mypy -nodeenv==1.9.1 +nodeenv==1.10.0 # via pyright nox==2025.11.12 packaging==25.0 # via dependency-groups # via nox # via pytest -pathspec==0.12.1 +pathspec==1.0.3 # via mypy platformdirs==4.4.0 # via virtualenv @@ -115,13 +115,13 @@ python-dateutil==2.9.0.post0 # via time-machine respx==0.22.0 rich==14.2.0 -ruff==0.14.7 +ruff==0.14.13 six==1.17.0 # via python-dateutil sniffio==1.3.1 # via finch-api time-machine==2.19.0 -tomli==2.3.0 +tomli==2.4.0 # via dependency-groups # via mypy # via nox @@ -141,7 +141,7 @@ typing-extensions==4.15.0 # via virtualenv typing-inspection==0.4.2 # via pydantic -virtualenv==20.35.4 +virtualenv==20.36.1 # via nox yarl==1.22.0 # via aiohttp diff --git a/requirements.lock b/requirements.lock index dbf05384..a3f1ad70 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,21 +12,21 @@ -e file:. aiohappyeyeballs==2.6.1 # via aiohttp -aiohttp==3.13.2 +aiohttp==3.13.3 # via finch-api # via httpx-aiohttp aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.12.0 +anyio==4.12.1 # via finch-api # via httpx async-timeout==5.0.1 # via aiohttp attrs==25.4.0 # via aiohttp -certifi==2025.11.12 +certifi==2026.1.4 # via httpcore # via httpx distro==1.9.0 @@ -43,7 +43,7 @@ httpcore==1.0.9 httpx==0.28.1 # via finch-api # via httpx-aiohttp -httpx-aiohttp==0.1.9 +httpx-aiohttp==0.1.12 # via finch-api idna==3.11 # via anyio diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py index 63e36f00..d338a4ce 100644 --- a/src/finch/_base_client.py +++ b/src/finch/_base_client.py @@ -86,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps from ._legacy_response import LegacyAPIResponse log: logging.Logger = logging.getLogger(__name__) @@ -555,8 +556,10 @@ def _build_request( kwargs["content"] = options.content elif isinstance(json_data, bytes): kwargs["content"] = json_data - else: - kwargs["json"] = json_data if is_given(json_data) else None + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/finch/_compat.py b/src/finch/_compat.py index bdef67f0..786ff42a 100644 --- a/src/finch/_compat.py +++ b/src/finch/_compat.py @@ -139,6 +139,7 @@ def model_dump( exclude_defaults: bool = False, warnings: bool = True, mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( @@ -148,13 +149,12 @@ def model_dump( exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 warnings=True if PYDANTIC_V1 else warnings, + by_alias=by_alias, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) diff --git a/src/finch/_legacy_response.py b/src/finch/_legacy_response.py index 24d12a21..a2241252 100644 --- a/src/finch/_legacy_response.py +++ b/src/finch/_legacy_response.py @@ -214,6 +214,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -224,6 +225,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -237,6 +239,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/finch/_response.py b/src/finch/_response.py index 3828f2a2..40ced75f 100644 --- a/src/finch/_response.py +++ b/src/finch/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/finch/_streaming.py b/src/finch/_streaming.py index facc5e66..f4c3580e 100644 --- a/src/finch/_streaming.py +++ b/src/finch/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import Finch, AsyncFinch + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,10 +32,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Finch, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -85,7 +88,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -94,10 +97,12 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncFinch, + options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response self._cast_to = cast_to self._client = client + self._options = options self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() diff --git a/src/finch/_utils/_compat.py b/src/finch/_utils/_compat.py index dd703233..2c70b299 100644 --- a/src/finch/_utils/_compat.py +++ b/src/finch/_utils/_compat.py @@ -26,7 +26,7 @@ def is_union(tp: Optional[Type[Any]]) -> bool: else: import types - return tp is Union or tp is types.UnionType + return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap] def is_typeddict(tp: Type[Any]) -> bool: diff --git a/src/finch/_utils/_json.py b/src/finch/_utils/_json.py new file mode 100644 index 00000000..60584214 --- /dev/null +++ b/src/finch/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/src/finch/_version.py b/src/finch/_version.py index 337c1dbd..d8308dd6 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.44.1" # x-release-please-version +__version__ = "1.45.0" # x-release-please-version diff --git a/src/finch/resources/access_tokens.py b/src/finch/resources/access_tokens.py index 39599bc9..f1f46034 100644 --- a/src/finch/resources/access_tokens.py +++ b/src/finch/resources/access_tokens.py @@ -71,6 +71,8 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {"Authorization": omit, **(extra_headers or {})} + if not is_given(client_id): if self._client.client_id is None: raise ValueError( @@ -157,6 +159,8 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {"Authorization": omit, **(extra_headers or {})} + if not is_given(client_id): if self._client.client_id is None: raise ValueError( diff --git a/src/finch/resources/account.py b/src/finch/resources/account.py index 394a29e8..ac8583a2 100644 --- a/src/finch/resources/account.py +++ b/src/finch/resources/account.py @@ -47,6 +47,7 @@ def disconnect( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisconnectResponse: """Disconnect one or more `access_token`s from your application.""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/disconnect", options=make_request_options( @@ -66,6 +67,7 @@ def introspect( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Introspection: """Read account information associated with an `access_token`""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( "/introspect", options=make_request_options( @@ -106,6 +108,7 @@ async def disconnect( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DisconnectResponse: """Disconnect one or more `access_token`s from your application.""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/disconnect", options=make_request_options( @@ -125,6 +128,7 @@ async def introspect( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> Introspection: """Read account information associated with an `access_token`""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( "/introspect", options=make_request_options( diff --git a/src/finch/resources/connect/sessions.py b/src/finch/resources/connect/sessions.py index f402a565..648deb68 100644 --- a/src/finch/resources/connect/sessions.py +++ b/src/finch/resources/connect/sessions.py @@ -104,6 +104,7 @@ def new( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return self._post( "/connect/sessions", body=maybe_transform( @@ -177,6 +178,7 @@ def reauthenticate( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return self._post( "/connect/sessions/reauthenticate", body=maybe_transform( @@ -278,6 +280,7 @@ async def new( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return await self._post( "/connect/sessions", body=await async_maybe_transform( @@ -351,6 +354,7 @@ async def reauthenticate( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return await self._post( "/connect/sessions/reauthenticate", body=await async_maybe_transform( diff --git a/src/finch/resources/hris/benefits/benefits.py b/src/finch/resources/hris/benefits/benefits.py index 9d5d0dde..91236b33 100644 --- a/src/finch/resources/hris/benefits/benefits.py +++ b/src/finch/resources/hris/benefits/benefits.py @@ -106,6 +106,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/employer/benefits", body=maybe_transform( @@ -155,6 +156,7 @@ def retrieve( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( f"/employer/benefits/{benefit_id}", options=make_request_options( @@ -198,6 +200,7 @@ def update( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( f"/employer/benefits/{benefit_id}", body=maybe_transform({"description": description}, benefit_update_params.BenefitUpdateParams), @@ -236,6 +239,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/benefits", page=SyncSinglePage[CompanyBenefit], @@ -274,6 +278,7 @@ def list_supported_benefits( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/benefits/meta", page=SyncSinglePage[SupportedBenefit], @@ -356,6 +361,7 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/employer/benefits", body=await async_maybe_transform( @@ -407,6 +413,7 @@ async def retrieve( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( f"/employer/benefits/{benefit_id}", options=make_request_options( @@ -452,6 +459,7 @@ async def update( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( f"/employer/benefits/{benefit_id}", body=await async_maybe_transform({"description": description}, benefit_update_params.BenefitUpdateParams), @@ -492,6 +500,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/benefits", page=AsyncSinglePage[CompanyBenefit], @@ -530,6 +539,7 @@ def list_supported_benefits( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/benefits/meta", page=AsyncSinglePage[SupportedBenefit], diff --git a/src/finch/resources/hris/benefits/individuals.py b/src/finch/resources/hris/benefits/individuals.py index cc8d67d3..c03f86fb 100644 --- a/src/finch/resources/hris/benefits/individuals.py +++ b/src/finch/resources/hris/benefits/individuals.py @@ -83,6 +83,7 @@ def enroll_many( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( f"/employer/benefits/{benefit_id}/individuals", body=maybe_transform(individuals, Iterable[individual_enroll_many_params.Individual]), @@ -126,6 +127,7 @@ def enrolled_ids( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( f"/employer/benefits/{benefit_id}/enrolled", options=make_request_options( @@ -172,6 +174,7 @@ def retrieve_many_benefits( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=SyncSinglePage[IndividualBenefit], @@ -222,6 +225,7 @@ def unenroll_many( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._delete( f"/employer/benefits/{benefit_id}/individuals", body=maybe_transform( @@ -295,6 +299,7 @@ async def enroll_many( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( f"/employer/benefits/{benefit_id}/individuals", body=await async_maybe_transform(individuals, Iterable[individual_enroll_many_params.Individual]), @@ -338,6 +343,7 @@ async def enrolled_ids( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( f"/employer/benefits/{benefit_id}/enrolled", options=make_request_options( @@ -384,6 +390,7 @@ def retrieve_many_benefits( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=AsyncSinglePage[IndividualBenefit], @@ -434,6 +441,7 @@ async def unenroll_many( """ if not benefit_id: raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._delete( f"/employer/benefits/{benefit_id}/individuals", body=await async_maybe_transform( diff --git a/src/finch/resources/hris/company/company.py b/src/finch/resources/hris/company/company.py index 159b6e64..1bc042b7 100644 --- a/src/finch/resources/hris/company/company.py +++ b/src/finch/resources/hris/company/company.py @@ -74,6 +74,7 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( "/employer/company", options=make_request_options( @@ -136,6 +137,7 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( "/employer/company", options=make_request_options( diff --git a/src/finch/resources/hris/company/pay_statement_item/pay_statement_item.py b/src/finch/resources/hris/company/pay_statement_item/pay_statement_item.py index d2d83911..5254872d 100644 --- a/src/finch/resources/hris/company/pay_statement_item/pay_statement_item.py +++ b/src/finch/resources/hris/company/pay_statement_item/pay_statement_item.py @@ -98,6 +98,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-statement-item", page=SyncResponsesPage[PayStatementItemListResponse], @@ -190,6 +191,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-statement-item", page=AsyncResponsesPage[PayStatementItemListResponse], diff --git a/src/finch/resources/hris/company/pay_statement_item/rules.py b/src/finch/resources/hris/company/pay_statement_item/rules.py index 64071a73..7fc7d9ad 100644 --- a/src/finch/resources/hris/company/pay_statement_item/rules.py +++ b/src/finch/resources/hris/company/pay_statement_item/rules.py @@ -90,6 +90,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/employer/pay-statement-item/rule", body=maybe_transform( @@ -141,6 +142,7 @@ def update( """ if not rule_id: raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._put( f"/employer/pay-statement-item/rule/{rule_id}", body=maybe_transform({"optional_property": optional_property}, rule_update_params.RuleUpdateParams), @@ -179,6 +181,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-statement-item/rule", page=SyncResponsesPage[RuleListResponse], @@ -220,6 +223,7 @@ def delete( """ if not rule_id: raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._delete( f"/employer/pay-statement-item/rule/{rule_id}", options=make_request_options( @@ -294,6 +298,7 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/employer/pay-statement-item/rule", body=await async_maybe_transform( @@ -345,6 +350,7 @@ async def update( """ if not rule_id: raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._put( f"/employer/pay-statement-item/rule/{rule_id}", body=await async_maybe_transform( @@ -385,6 +391,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-statement-item/rule", page=AsyncResponsesPage[RuleListResponse], @@ -426,6 +433,7 @@ async def delete( """ if not rule_id: raise ValueError(f"Expected a non-empty value for `rule_id` but received {rule_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._delete( f"/employer/pay-statement-item/rule/{rule_id}", options=make_request_options( diff --git a/src/finch/resources/hris/directory.py b/src/finch/resources/hris/directory.py index 32068b06..50cbae78 100644 --- a/src/finch/resources/hris/directory.py +++ b/src/finch/resources/hris/directory.py @@ -71,6 +71,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/directory", page=SyncIndividualsPage[IndividualInDirectory], @@ -185,6 +186,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/directory", page=AsyncIndividualsPage[IndividualInDirectory], diff --git a/src/finch/resources/hris/documents.py b/src/finch/resources/hris/documents.py index e7dc7c73..5c56765a 100644 --- a/src/finch/resources/hris/documents.py +++ b/src/finch/resources/hris/documents.py @@ -82,6 +82,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( "/employer/documents", options=make_request_options( @@ -133,6 +134,7 @@ def retreive( """ if not document_id: raise ValueError(f"Expected a non-empty value for `document_id` but received {document_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return cast( DocumentRetreiveResponse, self._get( @@ -212,6 +214,7 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( "/employer/documents", options=make_request_options( @@ -263,6 +266,7 @@ async def retreive( """ if not document_id: raise ValueError(f"Expected a non-empty value for `document_id` but received {document_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return cast( DocumentRetreiveResponse, await self._get( diff --git a/src/finch/resources/hris/employments.py b/src/finch/resources/hris/employments.py index 6374b0b7..07d3087b 100644 --- a/src/finch/resources/hris/employments.py +++ b/src/finch/resources/hris/employments.py @@ -68,6 +68,7 @@ def retrieve_many( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/employment", page=SyncResponsesPage[EmploymentDataResponse], @@ -134,6 +135,7 @@ def retrieve_many( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/employment", page=AsyncResponsesPage[EmploymentDataResponse], diff --git a/src/finch/resources/hris/individuals.py b/src/finch/resources/hris/individuals.py index b4a966c0..64f04179 100644 --- a/src/finch/resources/hris/individuals.py +++ b/src/finch/resources/hris/individuals.py @@ -67,6 +67,7 @@ def retrieve_many( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/individual", page=SyncResponsesPage[IndividualResponse], @@ -138,6 +139,7 @@ def retrieve_many( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/individual", page=AsyncResponsesPage[IndividualResponse], diff --git a/src/finch/resources/hris/pay_statements.py b/src/finch/resources/hris/pay_statements.py index bcae95c8..64312286 100644 --- a/src/finch/resources/hris/pay_statements.py +++ b/src/finch/resources/hris/pay_statements.py @@ -71,6 +71,7 @@ def retrieve_many( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-statement", page=SyncResponsesPage[PayStatementResponse], @@ -142,6 +143,7 @@ def retrieve_many( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-statement", page=AsyncResponsesPage[PayStatementResponse], diff --git a/src/finch/resources/hris/payments.py b/src/finch/resources/hris/payments.py index a795090c..2b226949 100644 --- a/src/finch/resources/hris/payments.py +++ b/src/finch/resources/hris/payments.py @@ -74,6 +74,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/payment", page=SyncSinglePage[Payment], @@ -148,6 +149,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/payment", page=AsyncSinglePage[Payment], diff --git a/src/finch/resources/jobs/automated.py b/src/finch/resources/jobs/automated.py index 687e1389..fa266ac7 100644 --- a/src/finch/resources/jobs/automated.py +++ b/src/finch/resources/jobs/automated.py @@ -137,6 +137,7 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AutomatedCreateResponse: + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/jobs/automated", body=maybe_transform( @@ -177,6 +178,7 @@ def retrieve( """ if not job_id: raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( f"/jobs/automated/{job_id}", options=make_request_options( @@ -216,6 +218,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( "/jobs/automated", options=make_request_options( @@ -351,6 +354,7 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AutomatedCreateResponse: + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/jobs/automated", body=await async_maybe_transform( @@ -391,6 +395,7 @@ async def retrieve( """ if not job_id: raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( f"/jobs/automated/{job_id}", options=make_request_options( @@ -430,6 +435,7 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( "/jobs/automated", options=make_request_options( diff --git a/src/finch/resources/jobs/manual.py b/src/finch/resources/jobs/manual.py index 9e99c9d7..d391d137 100644 --- a/src/finch/resources/jobs/manual.py +++ b/src/finch/resources/jobs/manual.py @@ -62,6 +62,7 @@ def retrieve( """ if not job_id: raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( f"/jobs/manual/{job_id}", options=make_request_options( @@ -118,6 +119,7 @@ async def retrieve( """ if not job_id: raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( f"/jobs/manual/{job_id}", options=make_request_options( diff --git a/src/finch/resources/payroll/pay_groups.py b/src/finch/resources/payroll/pay_groups.py index 0202884e..91c5edbf 100644 --- a/src/finch/resources/payroll/pay_groups.py +++ b/src/finch/resources/payroll/pay_groups.py @@ -67,6 +67,7 @@ def retrieve( """ if not pay_group_id: raise ValueError(f"Expected a non-empty value for `pay_group_id` but received {pay_group_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( f"/employer/pay-groups/{pay_group_id}", options=make_request_options( @@ -106,6 +107,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-groups", page=SyncSinglePage[PayGroupListResponse], @@ -175,6 +177,7 @@ async def retrieve( """ if not pay_group_id: raise ValueError(f"Expected a non-empty value for `pay_group_id` but received {pay_group_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( f"/employer/pay-groups/{pay_group_id}", options=make_request_options( @@ -216,6 +219,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/employer/pay-groups", page=AsyncSinglePage[PayGroupListResponse], diff --git a/src/finch/resources/providers.py b/src/finch/resources/providers.py index 9fc6fad3..2e37ff56 100644 --- a/src/finch/resources/providers.py +++ b/src/finch/resources/providers.py @@ -47,6 +47,7 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SyncSinglePage[ProviderListResponse]: """Return details on all available payroll and HR systems.""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/providers", page=SyncSinglePage[ProviderListResponse], @@ -88,6 +89,7 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AsyncPaginator[ProviderListResponse, AsyncSinglePage[ProviderListResponse]]: """Return details on all available payroll and HR systems.""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get_api_list( "/providers", page=AsyncSinglePage[ProviderListResponse], diff --git a/src/finch/resources/request_forwarding.py b/src/finch/resources/request_forwarding.py index dacb93fd..01bea5a8 100644 --- a/src/finch/resources/request_forwarding.py +++ b/src/finch/resources/request_forwarding.py @@ -87,6 +87,7 @@ def forward( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/forward", body=maybe_transform( @@ -174,6 +175,7 @@ async def forward( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/forward", body=await async_maybe_transform( diff --git a/src/finch/resources/sandbox/company.py b/src/finch/resources/sandbox/company.py index 45c87dc9..714bc7b1 100644 --- a/src/finch/resources/sandbox/company.py +++ b/src/finch/resources/sandbox/company.py @@ -85,6 +85,7 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._put( "/sandbox/company", body=maybe_transform( @@ -172,6 +173,7 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._put( "/sandbox/company", body=await async_maybe_transform( diff --git a/src/finch/resources/sandbox/connections/accounts.py b/src/finch/resources/sandbox/connections/accounts.py index e38e2a2d..e8d168b9 100644 --- a/src/finch/resources/sandbox/connections/accounts.py +++ b/src/finch/resources/sandbox/connections/accounts.py @@ -72,6 +72,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return self._post( "/sandbox/connections/accounts", body=maybe_transform( @@ -114,6 +115,7 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._put( "/sandbox/connections/accounts", body=maybe_transform({"connection_status": connection_status}, account_update_params.AccountUpdateParams), @@ -175,6 +177,7 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return await self._post( "/sandbox/connections/accounts", body=await async_maybe_transform( @@ -217,6 +220,7 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._put( "/sandbox/connections/accounts", body=await async_maybe_transform( diff --git a/src/finch/resources/sandbox/connections/connections.py b/src/finch/resources/sandbox/connections/connections.py index c4c35dc3..a3f0c4b1 100644 --- a/src/finch/resources/sandbox/connections/connections.py +++ b/src/finch/resources/sandbox/connections/connections.py @@ -83,6 +83,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return self._post( "/sandbox/connections", body=maybe_transform( @@ -157,6 +158,7 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._basic_auth, **(extra_headers or {})} return await self._post( "/sandbox/connections", body=await async_maybe_transform( diff --git a/src/finch/resources/sandbox/directory.py b/src/finch/resources/sandbox/directory.py index 2afba6a5..6096fdc0 100644 --- a/src/finch/resources/sandbox/directory.py +++ b/src/finch/resources/sandbox/directory.py @@ -65,6 +65,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/sandbox/directory", body=maybe_transform(body, Iterable[directory_create_params.Body]), @@ -121,6 +122,7 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/sandbox/directory", body=await async_maybe_transform(body, Iterable[directory_create_params.Body]), diff --git a/src/finch/resources/sandbox/employment.py b/src/finch/resources/sandbox/employment.py index ede9a473..386ac281 100644 --- a/src/finch/resources/sandbox/employment.py +++ b/src/finch/resources/sandbox/employment.py @@ -56,6 +56,7 @@ def update( | Omit = omit, end_date: Optional[str] | Omit = omit, first_name: Optional[str] | Omit = omit, + flsa_status: Optional[Literal["exempt", "non_exempt", "unknown"]] | Omit = omit, income: Optional[IncomeParam] | Omit = omit, income_history: Optional[Iterable[Optional[IncomeParam]]] | Omit = omit, is_active: Optional[bool] | Omit = omit, @@ -92,6 +93,9 @@ def update( first_name: The legal first name of the individual. + flsa_status: The FLSA status of the individual. Available options: `exempt`, `non_exempt`, + `unknown`. + income: The employee's income as reported by the provider. This may not always be annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, depending on what information the provider returns. @@ -122,6 +126,7 @@ def update( """ if not individual_id: raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._put( f"/sandbox/employment/{individual_id}", body=maybe_transform( @@ -133,6 +138,7 @@ def update( "employment_status": employment_status, "end_date": end_date, "first_name": first_name, + "flsa_status": flsa_status, "income": income, "income_history": income_history, "is_active": is_active, @@ -188,6 +194,7 @@ async def update( | Omit = omit, end_date: Optional[str] | Omit = omit, first_name: Optional[str] | Omit = omit, + flsa_status: Optional[Literal["exempt", "non_exempt", "unknown"]] | Omit = omit, income: Optional[IncomeParam] | Omit = omit, income_history: Optional[Iterable[Optional[IncomeParam]]] | Omit = omit, is_active: Optional[bool] | Omit = omit, @@ -224,6 +231,9 @@ async def update( first_name: The legal first name of the individual. + flsa_status: The FLSA status of the individual. Available options: `exempt`, `non_exempt`, + `unknown`. + income: The employee's income as reported by the provider. This may not always be annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, depending on what information the provider returns. @@ -254,6 +264,7 @@ async def update( """ if not individual_id: raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._put( f"/sandbox/employment/{individual_id}", body=await async_maybe_transform( @@ -265,6 +276,7 @@ async def update( "employment_status": employment_status, "end_date": end_date, "first_name": first_name, + "flsa_status": flsa_status, "income": income, "income_history": income_history, "is_active": is_active, diff --git a/src/finch/resources/sandbox/individual.py b/src/finch/resources/sandbox/individual.py index 5b9041c0..399a88c2 100644 --- a/src/finch/resources/sandbox/individual.py +++ b/src/finch/resources/sandbox/individual.py @@ -113,6 +113,7 @@ def update( """ if not individual_id: raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._put( f"/sandbox/individual/{individual_id}", body=maybe_transform( @@ -231,6 +232,7 @@ async def update( """ if not individual_id: raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._put( f"/sandbox/individual/{individual_id}", body=await async_maybe_transform( diff --git a/src/finch/resources/sandbox/jobs/configuration.py b/src/finch/resources/sandbox/jobs/configuration.py index f8839411..e3239cc6 100644 --- a/src/finch/resources/sandbox/jobs/configuration.py +++ b/src/finch/resources/sandbox/jobs/configuration.py @@ -51,6 +51,7 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ConfigurationRetrieveResponse: """Get configurations for sandbox jobs""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._get( "/sandbox/jobs/configuration", options=make_request_options( @@ -83,6 +84,7 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._put( "/sandbox/jobs/configuration", body=maybe_transform( @@ -130,6 +132,7 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> ConfigurationRetrieveResponse: """Get configurations for sandbox jobs""" + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._get( "/sandbox/jobs/configuration", options=make_request_options( @@ -162,6 +165,7 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._put( "/sandbox/jobs/configuration", body=await async_maybe_transform( diff --git a/src/finch/resources/sandbox/jobs/jobs.py b/src/finch/resources/sandbox/jobs/jobs.py index 070bd293..4ac88a37 100644 --- a/src/finch/resources/sandbox/jobs/jobs.py +++ b/src/finch/resources/sandbox/jobs/jobs.py @@ -77,6 +77,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/sandbox/jobs", body=maybe_transform({"type": type}, job_create_params.JobCreateParams), @@ -137,6 +138,7 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/sandbox/jobs", body=await async_maybe_transform({"type": type}, job_create_params.JobCreateParams), diff --git a/src/finch/resources/sandbox/payment.py b/src/finch/resources/sandbox/payment.py index 2506f49d..f08880a7 100644 --- a/src/finch/resources/sandbox/payment.py +++ b/src/finch/resources/sandbox/payment.py @@ -67,6 +67,7 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return self._post( "/sandbox/payment", body=maybe_transform( @@ -131,6 +132,7 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {**self._client._bearer_auth, **(extra_headers or {})} return await self._post( "/sandbox/payment", body=await async_maybe_transform( diff --git a/src/finch/types/create_access_token_response.py b/src/finch/types/create_access_token_response.py index 0aacdf48..0edd76d4 100644 --- a/src/finch/types/create_access_token_response.py +++ b/src/finch/types/create_access_token_response.py @@ -54,3 +54,9 @@ class CreateAccessTokenResponse(BaseModel): The ID of your customer you provided to Finch when a connect session was created for this connection """ + + customer_name: Optional[str] = None + """ + The name of your customer you provided to Finch when a connect session was + created for this connection + """ diff --git a/src/finch/types/hris/employment_data.py b/src/finch/types/hris/employment_data.py index 385eeded..2a9409d6 100644 --- a/src/finch/types/hris/employment_data.py +++ b/src/finch/types/hris/employment_data.py @@ -75,6 +75,12 @@ class UnionMember0(BaseModel): first_name: Optional[str] = None """The legal first name of the individual.""" + flsa_status: Optional[Literal["exempt", "non_exempt", "unknown"]] = None + """The FLSA status of the individual. + + Available options: `exempt`, `non_exempt`, `unknown`. + """ + is_active: Optional[bool] = None """`true` if the individual an an active employee or contractor at the company.""" diff --git a/src/finch/types/sandbox/directory_create_params.py b/src/finch/types/sandbox/directory_create_params.py index 3c142a74..ce26a93f 100644 --- a/src/finch/types/sandbox/directory_create_params.py +++ b/src/finch/types/sandbox/directory_create_params.py @@ -127,6 +127,12 @@ class Body(TypedDict, total=False): first_name: Optional[str] """The legal first name of the individual.""" + flsa_status: Optional[Literal["exempt", "non_exempt", "unknown"]] + """The FLSA status of the individual. + + Available options: `exempt`, `non_exempt`, `unknown`. + """ + gender: Optional[Literal["female", "male", "other", "decline_to_specify"]] """The gender of the individual.""" diff --git a/src/finch/types/sandbox/employment_update_params.py b/src/finch/types/sandbox/employment_update_params.py index 1e3469de..21dca764 100644 --- a/src/finch/types/sandbox/employment_update_params.py +++ b/src/finch/types/sandbox/employment_update_params.py @@ -38,6 +38,12 @@ class EmploymentUpdateParams(TypedDict, total=False): first_name: Optional[str] """The legal first name of the individual.""" + flsa_status: Optional[Literal["exempt", "non_exempt", "unknown"]] + """The FLSA status of the individual. + + Available options: `exempt`, `non_exempt`, `unknown`. + """ + income: Optional[IncomeParam] """The employee's income as reported by the provider. diff --git a/src/finch/types/sandbox/employment_update_response.py b/src/finch/types/sandbox/employment_update_response.py index fb44c946..c349b08d 100644 --- a/src/finch/types/sandbox/employment_update_response.py +++ b/src/finch/types/sandbox/employment_update_response.py @@ -74,6 +74,12 @@ class EmploymentUpdateResponse(BaseModel): first_name: Optional[str] = None """The legal first name of the individual.""" + flsa_status: Optional[Literal["exempt", "non_exempt", "unknown"]] = None + """The FLSA status of the individual. + + Available options: `exempt`, `non_exempt`, `unknown`. + """ + income_history: Optional[List[Optional[Income]]] = None """The array of income history.""" diff --git a/tests/api_resources/hris/test_directory.py b/tests/api_resources/hris/test_directory.py index fdeca307..dd01b8b9 100644 --- a/tests/api_resources/hris/test_directory.py +++ b/tests/api_resources/hris/test_directory.py @@ -11,6 +11,7 @@ from tests.utils import assert_matches_type from finch.pagination import SyncIndividualsPage, AsyncIndividualsPage from finch.types.hris import IndividualInDirectory +from finch.types.hris.directory_list_individuals_params import UnnamedTypeWithNoPropertyInfoOrParent0 # pyright: reportDeprecated=false @@ -59,7 +60,7 @@ def test_method_list_individuals(self, client: Finch) -> None: with pytest.warns(DeprecationWarning): directory = client.hris.directory.list_individuals() - assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) @parametrize def test_method_list_individuals_with_all_params(self, client: Finch) -> None: @@ -70,7 +71,7 @@ def test_method_list_individuals_with_all_params(self, client: Finch) -> None: offset=0, ) - assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) @parametrize def test_raw_response_list_individuals(self, client: Finch) -> None: @@ -80,7 +81,7 @@ def test_raw_response_list_individuals(self, client: Finch) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() - assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) @parametrize def test_streaming_response_list_individuals(self, client: Finch) -> None: @@ -90,7 +91,7 @@ def test_streaming_response_list_individuals(self, client: Finch) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() - assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -139,7 +140,7 @@ async def test_method_list_individuals(self, async_client: AsyncFinch) -> None: with pytest.warns(DeprecationWarning): directory = await async_client.hris.directory.list_individuals() - assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) @parametrize async def test_method_list_individuals_with_all_params(self, async_client: AsyncFinch) -> None: @@ -150,7 +151,7 @@ async def test_method_list_individuals_with_all_params(self, async_client: Async offset=0, ) - assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) @parametrize async def test_raw_response_list_individuals(self, async_client: AsyncFinch) -> None: @@ -160,7 +161,7 @@ async def test_raw_response_list_individuals(self, async_client: AsyncFinch) -> assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() - assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) @parametrize async def test_streaming_response_list_individuals(self, async_client: AsyncFinch) -> None: @@ -170,6 +171,6 @@ async def test_streaming_response_list_individuals(self, async_client: AsyncFinc assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = await response.parse() - assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + assert_matches_type(UnnamedTypeWithNoPropertyInfoOrParent0, directory, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/sandbox/test_directory.py b/tests/api_resources/sandbox/test_directory.py index ffc78bec..fcba8f6d 100644 --- a/tests/api_resources/sandbox/test_directory.py +++ b/tests/api_resources/sandbox/test_directory.py @@ -52,6 +52,7 @@ def test_method_create_with_all_params(self, client: Finch) -> None: "end_date": "end_date", "ethnicity": "asian", "first_name": "first_name", + "flsa_status": "exempt", "gender": "female", "income": { "amount": 0, @@ -168,6 +169,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> "end_date": "end_date", "ethnicity": "asian", "first_name": "first_name", + "flsa_status": "exempt", "gender": "female", "income": { "amount": 0, diff --git a/tests/api_resources/sandbox/test_employment.py b/tests/api_resources/sandbox/test_employment.py index c9aecbc0..33595a7f 100644 --- a/tests/api_resources/sandbox/test_employment.py +++ b/tests/api_resources/sandbox/test_employment.py @@ -44,6 +44,7 @@ def test_method_update_with_all_params(self, client: Finch) -> None: employment_status="active", end_date="end_date", first_name="first_name", + flsa_status="exempt", income={ "amount": 0, "currency": "currency", @@ -142,6 +143,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> employment_status="active", end_date="end_date", first_name="first_name", + flsa_status="exempt", income={ "amount": 0, "currency": "currency", diff --git a/tests/api_resources/test_access_tokens.py b/tests/api_resources/test_access_tokens.py index d71bb756..0dda9602 100644 --- a/tests/api_resources/test_access_tokens.py +++ b/tests/api_resources/test_access_tokens.py @@ -46,6 +46,7 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncFinch]: class TestAccessTokens: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize def test_method_create(self, client: Finch) -> None: access_token = client.access_tokens.create( @@ -53,6 +54,7 @@ def test_method_create(self, client: Finch) -> None: ) assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize def test_method_create_with_all_params(self, client: Finch) -> None: access_token = client.access_tokens.create( @@ -63,6 +65,7 @@ def test_method_create_with_all_params(self, client: Finch) -> None: ) assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize def test_raw_response_create(self, client: Finch) -> None: response = client.access_tokens.with_raw_response.create( @@ -74,6 +77,7 @@ def test_raw_response_create(self, client: Finch) -> None: access_token = response.parse() assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize def test_streaming_response_create(self, client: Finch) -> None: with client.access_tokens.with_streaming_response.create( @@ -91,6 +95,7 @@ def test_streaming_response_create(self, client: Finch) -> None: class TestAsyncAccessTokens: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: access_token = await async_client.access_tokens.create( @@ -98,6 +103,7 @@ async def test_method_create(self, async_client: AsyncFinch) -> None: ) assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: access_token = await async_client.access_tokens.create( @@ -108,6 +114,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> ) assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize async def test_raw_response_create(self, async_client: AsyncFinch) -> None: response = await async_client.access_tokens.with_raw_response.create( @@ -119,6 +126,7 @@ async def test_raw_response_create(self, async_client: AsyncFinch) -> None: access_token = response.parse() assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + @pytest.mark.skip(reason="prism doesnt like the format for the API-Version header") @parametrize async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: async with async_client.access_tokens.with_streaming_response.create( diff --git a/tests/conftest.py b/tests/conftest.py index 6631269d..3700f724 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,6 +46,8 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") access_token = "My Access Token" +client_id = "4ab15e51-11ad-49f4-acae-f343b7794375" +client_secret = "My Client Secret" @pytest.fixture(scope="session") @@ -54,7 +56,13 @@ def client(request: FixtureRequest) -> Iterator[Finch]: if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Finch(base_url=base_url, access_token=access_token, _strict_response_validation=strict) as client: + with Finch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=strict, + ) as client: yield client @@ -79,6 +87,11 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncFinch]: raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") async with AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=strict, http_client=http_client + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=strict, + http_client=http_client, ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index c6b61701..144a4cfd 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -40,6 +40,8 @@ T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") access_token = "My Access Token" +client_id = "4ab15e51-11ad-49f4-acae-f343b7794375" +client_secret = "My Client Secret" def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]: @@ -132,6 +134,14 @@ def test_copy(self, client: Finch) -> None: assert copied.access_token == "another My Access Token" assert client.access_token == "My Access Token" + copied = client.copy(client_id="another 4ab15e51-11ad-49f4-acae-f343b7794375") + assert copied.client_id == "another 4ab15e51-11ad-49f4-acae-f343b7794375" + assert client.client_id == "4ab15e51-11ad-49f4-acae-f343b7794375" + + copied = client.copy(client_secret="another My Client Secret") + assert copied.client_secret == "another My Client Secret" + assert client.client_secret == "My Client Secret" + def test_copy_default_options(self, client: Finch) -> None: # options that have a default are overridden correctly copied = client.copy(max_retries=7) @@ -152,6 +162,8 @@ def test_copy_default_headers(self) -> None: client = Finch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -188,7 +200,12 @@ def test_copy_default_headers(self) -> None: def test_copy_default_query(self) -> None: client = Finch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + default_query={"foo": "bar"}, ) assert _get_params(client)["foo"] == "bar" @@ -314,7 +331,12 @@ def test_request_timeout(self, client: Finch) -> None: def test_client_timeout_option(self) -> None: client = Finch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + timeout=httpx.Timeout(0), ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -327,7 +349,12 @@ 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 = Finch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -339,7 +366,12 @@ 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 = Finch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -351,7 +383,12 @@ 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 = Finch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -366,6 +403,8 @@ async def test_invalid_http_client(self) -> None: Finch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=cast(Any, http_client), ) @@ -374,6 +413,8 @@ def test_default_headers_option(self) -> None: test_client = Finch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -384,6 +425,8 @@ def test_default_headers_option(self) -> None: test_client2 = Finch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -398,11 +441,29 @@ def test_default_headers_option(self) -> None: test_client2.close() def test_validate_headers(self) -> None: - client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + client = Finch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("Authorization") == f"Bearer {access_token}" - client2 = Finch(base_url=base_url, access_token=None, _strict_response_validation=True) + with update_env( + **{ + "FINCH_CLIENT_ID": Omit(), + "FINCH_CLIENT_SECRET": Omit(), + } + ): + client2 = Finch( + base_url=base_url, + access_token=None, + client_id=None, + client_secret=None, + _strict_response_validation=True, + ) with pytest.raises( TypeError, @@ -419,6 +480,8 @@ def test_default_query_option(self) -> None: client = Finch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_query={"query_param": "bar"}, ) @@ -593,6 +656,8 @@ def mock_handler(request: httpx.Request) -> httpx.Response: with Finch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), ) as client: @@ -687,7 +752,11 @@ class Model(BaseModel): def test_base_url_setter(self) -> None: client = Finch( - base_url="https://example.com/from_init", access_token=access_token, _strict_response_validation=True + base_url="https://example.com/from_init", + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, ) assert client.base_url == "https://example.com/from_init/" @@ -699,7 +768,12 @@ def test_base_url_setter(self) -> None: def test_base_url_env(self) -> None: with update_env(FINCH_BASE_URL="http://localhost:5000/from/env"): - client = Finch(access_token=access_token, _strict_response_validation=True) + client = Finch( + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( @@ -708,11 +782,15 @@ def test_base_url_env(self) -> None: Finch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, ), Finch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -736,11 +814,15 @@ def test_base_url_trailing_slash(self, client: Finch) -> None: Finch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, ), Finch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -764,11 +846,15 @@ def test_base_url_no_trailing_slash(self, client: Finch) -> None: Finch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, ), Finch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.Client(), ), @@ -787,7 +873,13 @@ def test_absolute_request_url(self, client: Finch) -> None: client.close() def test_copied_client_does_not_close_http(self) -> None: - test_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + test_client = Finch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) assert not test_client.is_closed() copied = test_client.copy() @@ -798,7 +890,13 @@ def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() def test_client_context_manager(self) -> None: - test_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + test_client = Finch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -822,6 +920,8 @@ def test_client_max_retries_validation(self) -> None: Finch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, max_retries=cast(Any, None), ) @@ -833,12 +933,24 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + strict_client = Finch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - non_strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) + non_strict_client = Finch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=False, + ) response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -977,6 +1089,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1051,6 +1171,14 @@ def test_copy(self, async_client: AsyncFinch) -> None: assert copied.access_token == "another My Access Token" assert async_client.access_token == "My Access Token" + copied = async_client.copy(client_id="another 4ab15e51-11ad-49f4-acae-f343b7794375") + assert copied.client_id == "another 4ab15e51-11ad-49f4-acae-f343b7794375" + assert async_client.client_id == "4ab15e51-11ad-49f4-acae-f343b7794375" + + copied = async_client.copy(client_secret="another My Client Secret") + assert copied.client_secret == "another My Client Secret" + assert async_client.client_secret == "My Client Secret" + def test_copy_default_options(self, async_client: AsyncFinch) -> None: # options that have a default are overridden correctly copied = async_client.copy(max_retries=7) @@ -1071,6 +1199,8 @@ async def test_copy_default_headers(self) -> None: client = AsyncFinch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -1107,7 +1237,12 @@ async def test_copy_default_headers(self) -> None: async def test_copy_default_query(self) -> None: client = AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, default_query={"foo": "bar"} + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + default_query={"foo": "bar"}, ) assert _get_params(client)["foo"] == "bar" @@ -1235,7 +1370,12 @@ async def test_request_timeout(self, async_client: AsyncFinch) -> None: async def test_client_timeout_option(self) -> None: client = AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, timeout=httpx.Timeout(0) + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + timeout=httpx.Timeout(0), ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1248,7 +1388,12 @@ 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 = AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1260,7 +1405,12 @@ 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 = AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1272,7 +1422,12 @@ 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 = AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, http_client=http_client + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + http_client=http_client, ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1287,6 +1442,8 @@ def test_invalid_http_client(self) -> None: AsyncFinch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=cast(Any, http_client), ) @@ -1295,6 +1452,8 @@ async def test_default_headers_option(self) -> None: test_client = AsyncFinch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_headers={"X-Foo": "bar"}, ) @@ -1305,6 +1464,8 @@ async def test_default_headers_option(self) -> None: test_client2 = AsyncFinch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_headers={ "X-Foo": "stainless", @@ -1319,11 +1480,29 @@ async def test_default_headers_option(self) -> None: await test_client2.close() def test_validate_headers(self) -> None: - client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + client = AsyncFinch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("Authorization") == f"Bearer {access_token}" - client2 = AsyncFinch(base_url=base_url, access_token=None, _strict_response_validation=True) + with update_env( + **{ + "FINCH_CLIENT_ID": Omit(), + "FINCH_CLIENT_SECRET": Omit(), + } + ): + client2 = AsyncFinch( + base_url=base_url, + access_token=None, + client_id=None, + client_secret=None, + _strict_response_validation=True, + ) with pytest.raises( TypeError, @@ -1340,6 +1519,8 @@ async def test_default_query_option(self) -> None: client = AsyncFinch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, default_query={"query_param": "bar"}, ) @@ -1514,6 +1695,8 @@ async def mock_handler(request: httpx.Request) -> httpx.Response: async with AsyncFinch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), ) as client: @@ -1612,7 +1795,11 @@ class Model(BaseModel): async def test_base_url_setter(self) -> None: client = AsyncFinch( - base_url="https://example.com/from_init", access_token=access_token, _strict_response_validation=True + base_url="https://example.com/from_init", + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, ) assert client.base_url == "https://example.com/from_init/" @@ -1624,7 +1811,12 @@ async def test_base_url_setter(self) -> None: async def test_base_url_env(self) -> None: with update_env(FINCH_BASE_URL="http://localhost:5000/from/env"): - client = AsyncFinch(access_token=access_token, _strict_response_validation=True) + client = AsyncFinch( + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) assert client.base_url == "http://localhost:5000/from/env/" @pytest.mark.parametrize( @@ -1633,11 +1825,15 @@ async def test_base_url_env(self) -> None: AsyncFinch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, ), AsyncFinch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1661,11 +1857,15 @@ async def test_base_url_trailing_slash(self, client: AsyncFinch) -> None: AsyncFinch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, ), AsyncFinch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1689,11 +1889,15 @@ async def test_base_url_no_trailing_slash(self, client: AsyncFinch) -> None: AsyncFinch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, ), AsyncFinch( base_url="http://localhost:5000/custom/path/", access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, http_client=httpx.AsyncClient(), ), @@ -1712,7 +1916,13 @@ async def test_absolute_request_url(self, client: AsyncFinch) -> None: await client.close() async def test_copied_client_does_not_close_http(self) -> None: - test_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + test_client = AsyncFinch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) assert not test_client.is_closed() copied = test_client.copy() @@ -1724,7 +1934,13 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - test_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + test_client = AsyncFinch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) async with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -1748,6 +1964,8 @@ async def test_client_max_retries_validation(self) -> None: AsyncFinch( base_url=base_url, access_token=access_token, + client_id=client_id, + client_secret=client_secret, _strict_response_validation=True, max_retries=cast(Any, None), ) @@ -1759,12 +1977,24 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + strict_client = AsyncFinch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=True, + ) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - non_strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) + non_strict_client = AsyncFinch( + base_url=base_url, + access_token=access_token, + client_id=client_id, + client_secret=client_secret, + _strict_response_validation=False, + ) response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1909,6 +2139,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient() diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 00000000..9db9eaef --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from finch import _compat +from finch._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'