diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 55d20255..ff261bad 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} USER vscode -RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.35.0" RYE_INSTALL_OPTION="--yes" bash +RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash ENV PATH=/home/vscode/.rye/shims:$PATH RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8a8a4f7..3b286e5a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Install dependencies @@ -42,7 +42,7 @@ jobs: curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Bootstrap diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index ddd68577..e35862a0 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -21,7 +21,7 @@ jobs: curl -sSf https://rye.astral.sh/get | bash echo "$HOME/.rye/shims" >> $GITHUB_PATH env: - RYE_VERSION: '0.35.0' + RYE_VERSION: '0.44.0' RYE_INSTALL_OPTION: '--yes' - name: Publish to PyPI diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bc845f32..6a197bef 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.16.0" + ".": "1.17.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 06d70961..fc279b3c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 41 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-7a816d4a5f0039230590a6662f3513d5756344ca662761ecbc49016593f65836.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-69819ddc6d03624ee8d880317fca03afab50a0a843218f1d9f14616e8a003dad.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc40806..5cc38458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## 1.17.0 (2025-03-25) + +Full Changelog: [v1.16.0...v1.17.0](https://github.com/Finch-API/finch-api-python/compare/v1.16.0...v1.17.0) + +### Features + +* **api:** api update ([#624](https://github.com/Finch-API/finch-api-python/issues/624)) ([6179f15](https://github.com/Finch-API/finch-api-python/commit/6179f1558884a9f1663f65496d7b15957b710ae8)) +* **api:** manual updates ([#612](https://github.com/Finch-API/finch-api-python/issues/612)) ([ac90061](https://github.com/Finch-API/finch-api-python/commit/ac900617af1bc37b728cbbc69ba22c5061ffddb7)) + + +### Bug Fixes + +* **ci:** ensure pip is always available ([#622](https://github.com/Finch-API/finch-api-python/issues/622)) ([bffb854](https://github.com/Finch-API/finch-api-python/commit/bffb8540f25ba56cfeb452d0c92dbadb291d88e9)) +* **ci:** remove publishing patch ([#623](https://github.com/Finch-API/finch-api-python/issues/623)) ([419c35a](https://github.com/Finch-API/finch-api-python/commit/419c35ad3f89c7da406c33a67069f0e8a7937aa4)) +* **types:** handle more discriminated union shapes ([#621](https://github.com/Finch-API/finch-api-python/issues/621)) ([971eac8](https://github.com/Finch-API/finch-api-python/commit/971eac8aef3ad9a5b87b70567726cb0c524b9251)) + + +### Chores + +* **docs:** update client docstring ([#610](https://github.com/Finch-API/finch-api-python/issues/610)) ([2bae32b](https://github.com/Finch-API/finch-api-python/commit/2bae32b4504f2e17d53238d2ef7c6267c9d19636)) +* **internal:** bump rye to 0.44.0 ([#619](https://github.com/Finch-API/finch-api-python/issues/619)) ([c62de3c](https://github.com/Finch-API/finch-api-python/commit/c62de3cf8821a5bfb814547c68939e78411d0681)) +* **internal:** codegen related update ([#611](https://github.com/Finch-API/finch-api-python/issues/611)) ([f397f20](https://github.com/Finch-API/finch-api-python/commit/f397f2097d2d349d16a46c2fbf79432c6d48290c)) +* **internal:** codegen related update ([#614](https://github.com/Finch-API/finch-api-python/issues/614)) ([6dd5f30](https://github.com/Finch-API/finch-api-python/commit/6dd5f30dfb8eb5b82500fd18e9472813b84bfdfc)) +* **internal:** codegen related update ([#615](https://github.com/Finch-API/finch-api-python/issues/615)) ([1ffccaf](https://github.com/Finch-API/finch-api-python/commit/1ffccafbb26c672fdea41495b91774babb9cdb06)) +* **internal:** codegen related update ([#617](https://github.com/Finch-API/finch-api-python/issues/617)) ([9c2b530](https://github.com/Finch-API/finch-api-python/commit/9c2b530e4a89d19276ba031558bc9f077069aa92)) +* **internal:** codegen related update ([#620](https://github.com/Finch-API/finch-api-python/issues/620)) ([735fb42](https://github.com/Finch-API/finch-api-python/commit/735fb42343c68e6e484a0ac962c0946fc4643659)) +* **internal:** remove extra empty newlines ([#618](https://github.com/Finch-API/finch-api-python/issues/618)) ([a18d4b5](https://github.com/Finch-API/finch-api-python/commit/a18d4b59192e8efdd5ce03d6e3e88fcc0135f9ec)) + + +### Documentation + +* update URLs from stainlessapi.com to stainless.com ([#608](https://github.com/Finch-API/finch-api-python/issues/608)) ([5df7857](https://github.com/Finch-API/finch-api-python/commit/5df78577187ab33565e3cc2b72fcd0a51c9531db)) + ## 1.16.0 (2025-02-27) Full Changelog: [v1.15.0...v1.16.0](https://github.com/Finch-API/finch-api-python/compare/v1.15.0...v1.16.0) diff --git a/README.md b/README.md index 9b82e4ec..b1b54166 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The Finch Python library provides convenient access to the Finch REST API from a application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). -It is generated with [Stainless](https://www.stainlessapi.com/). +It is generated with [Stainless](https://www.stainless.com/). ## Documentation diff --git a/SECURITY.md b/SECURITY.md index 6cef554f..b6499508 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Reporting Security Issues -This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. +This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. -To report a security issue, please contact the Stainless team at security@stainlessapi.com. +To report a security issue, please contact the Stainless team at security@stainless.com. ## Responsible Disclosure diff --git a/api.md b/api.md index cf19f855..15b5ef12 100644 --- a/api.md +++ b/api.md @@ -133,12 +133,12 @@ from finch.types.hris import ( BenefitFrequency, BenefitType, BenefitsSupport, - BenfitContribution, CompanyBenefit, CreateCompanyBenefitsResponse, SupportPerBenefitType, SupportedBenefit, UpdateCompanyBenefitResponse, + BenfitContribution, ) ``` @@ -238,14 +238,14 @@ Methods: Types: ```python -from finch.types.jobs import AutomatedAsyncJob, AutomatedCreateResponse +from finch.types.jobs import AutomatedAsyncJob, AutomatedCreateResponse, AutomatedListResponse ``` Methods: - client.jobs.automated.create(\*\*params) -> AutomatedCreateResponse - client.jobs.automated.retrieve(job_id) -> AutomatedAsyncJob -- client.jobs.automated.list(\*\*params) -> SyncPage[AutomatedAsyncJob] +- client.jobs.automated.list(\*\*params) -> AutomatedListResponse ## Manual diff --git a/bin/publish-pypi b/bin/publish-pypi index 05bfccbb..826054e9 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -3,7 +3,4 @@ set -eux mkdir -p dist rye build --clean -# Patching importlib-metadata version until upstream library version is updated -# https://github.com/pypa/twine/issues/977#issuecomment-2189800841 -"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1' rye publish --yes --token=$PYPI_TOKEN diff --git a/pyproject.toml b/pyproject.toml index 897b2659..2a035143 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "finch-api" -version = "1.16.0" +version = "1.17.0" description = "The official Python library for the Finch API" dynamic = ["readme"] license = "Apache-2.0" @@ -38,7 +38,6 @@ Homepage = "https://github.com/Finch-API/finch-api-python" Repository = "https://github.com/Finch-API/finch-api-python" - [tool.rye] managed = true # version pins are in requirements-dev.lock @@ -87,7 +86,7 @@ typecheck = { chain = [ "typecheck:mypy" = "mypy ." [build-system] -requires = ["hatchling", "hatch-fancy-pypi-readme"] +requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] build-backend = "hatchling.build" [tool.hatch.build] @@ -152,7 +151,6 @@ reportImplicitOverride = true reportImportCycles = false reportPrivateUsage = false - [tool.ruff] line-length = 120 output-format = "grouped" diff --git a/release-please-config.json b/release-please-config.json index d416b017..b55afaba 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -60,16 +60,8 @@ } ], "reviewers": [ - "araujodavid", - "NandoSangenetto", - "delainerogers", - "ericpsimon", "jordanbrauer", - "vitorfreitas", - "miguel-finch", - "edkim-finch", - "bteodosioFinch", - "ashar-finch" + "minupalaniappan" ], "release-type": "python", "extra-files": [ diff --git a/requirements-dev.lock b/requirements-dev.lock index fe794e91..b2e2daa3 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.6.0 diff --git a/requirements.lock b/requirements.lock index 7d43b8d3..d9942c73 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,6 +7,7 @@ # all-features: true # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.6.0 diff --git a/scripts/test b/scripts/test index 4fa5698b..2b878456 100755 --- a/scripts/test +++ b/scripts/test @@ -52,6 +52,8 @@ else echo fi +export DEFER_PYDANTIC_BUILD=false + echo "==> Running tests" rye run pytest "$@" diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py index 992b035c..d552f2cb 100644 --- a/src/finch/_base_client.py +++ b/src/finch/_base_client.py @@ -9,7 +9,6 @@ import inspect import logging import platform -import warnings import email.utils from types import TracebackType from random import random @@ -36,7 +35,7 @@ import httpx import distro import pydantic -from httpx import URL, Limits +from httpx import URL from pydantic import PrivateAttr from . import _exceptions @@ -51,13 +50,10 @@ Timeout, NotGiven, ResponseT, - Transport, AnyMapping, PostParser, - ProxiesTypes, RequestFiles, HttpxSendArgs, - AsyncTransport, RequestOptions, HttpxRequestFiles, ModelBuilderProtocol, @@ -338,9 +334,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]): _base_url: URL max_retries: int timeout: Union[float, Timeout, None] - _limits: httpx.Limits - _proxies: ProxiesTypes | None - _transport: Transport | AsyncTransport | None _strict_response_validation: bool _idempotency_header: str | None _default_stream_cls: type[_DefaultStreamT] | None = None @@ -353,9 +346,6 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None = DEFAULT_TIMEOUT, - limits: httpx.Limits, - transport: Transport | AsyncTransport | None, - proxies: ProxiesTypes | None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: @@ -363,9 +353,6 @@ def __init__( self._base_url = self._enforce_trailing_slash(URL(base_url)) self.max_retries = max_retries self.timeout = timeout - self._limits = limits - self._proxies = proxies - self._transport = transport self._custom_headers = custom_headers or {} self._custom_query = custom_query or {} self._strict_response_validation = _strict_response_validation @@ -801,46 +788,11 @@ def __init__( base_url: str | URL, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: Transport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.Client | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, _strict_response_validation: bool, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -861,12 +813,9 @@ def __init__( super().__init__( version=version, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, base_url=base_url, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -876,9 +825,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: @@ -1387,45 +1333,10 @@ def __init__( _strict_response_validation: bool, max_retries: int = DEFAULT_MAX_RETRIES, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, - transport: AsyncTransport | None = None, - proxies: ProxiesTypes | None = None, - limits: Limits | None = None, http_client: httpx.AsyncClient | None = None, custom_headers: Mapping[str, str] | None = None, custom_query: Mapping[str, object] | None = None, ) -> None: - kwargs: dict[str, Any] = {} - if limits is not None: - warnings.warn( - "The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `connection_pool_limits`") - else: - limits = DEFAULT_CONNECTION_LIMITS - - if transport is not None: - kwargs["transport"] = transport - warnings.warn( - "The `transport` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `transport`") - - if proxies is not None: - kwargs["proxies"] = proxies - warnings.warn( - "The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - category=DeprecationWarning, - stacklevel=3, - ) - if http_client is not None: - raise ValueError("The `http_client` argument is mutually exclusive with `proxies`") - if not is_given(timeout): # if the user passed in a custom http client with a non-default # timeout set then we use that timeout. @@ -1447,11 +1358,8 @@ def __init__( super().__init__( version=version, base_url=base_url, - limits=limits, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - proxies=proxies, - transport=transport, max_retries=max_retries, custom_query=custom_query, custom_headers=custom_headers, @@ -1461,9 +1369,6 @@ def __init__( base_url=base_url, # cast to a valid type because mypy doesn't understand our type narrowing timeout=cast(Timeout, timeout), - limits=limits, - follow_redirects=True, - **kwargs, # type: ignore ) def is_closed(self) -> bool: diff --git a/src/finch/_client.py b/src/finch/_client.py index 46a7fa61..e98dadf2 100644 --- a/src/finch/_client.py +++ b/src/finch/_client.py @@ -19,7 +19,6 @@ NotGiven, Transport, ProxiesTypes, - AsyncTransport, RequestOptions, ) from ._utils import ( @@ -32,11 +31,8 @@ from ._exceptions import APIStatusError from ._base_client import ( DEFAULT_MAX_RETRIES, - DEFAULT_CONNECTION_LIMITS, SyncAPIClient, AsyncAPIClient, - SyncHttpxClientWrapper, - AsyncHttpxClientWrapper, ) from .resources.hris import hris from .resources.jobs import jobs @@ -83,12 +79,6 @@ def __init__( # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details. http_client: httpx.Client | None = None, - # See httpx documentation for [custom transports](https://www.python-httpx.org/advanced/#custom-transports) - transport: Transport | None = None, - # See httpx documentation for [proxies](https://www.python-httpx.org/advanced/#http-proxying) - proxies: ProxiesTypes | None = None, - # See httpx documentation for [limits](https://www.python-httpx.org/advanced/#pool-limit-configuration) - connection_pool_limits: httpx.Limits | None = None, # Enable or disable schema validation for data returned by the API. # When enabled an error APIResponseValidationError is raised # if the API responds with invalid data for the expected schema. @@ -131,9 +121,6 @@ def __init__( max_retries=max_retries, timeout=timeout, http_client=http_client, - transport=transport, - proxies=proxies, - limits=connection_pool_limits, custom_headers=default_headers, custom_query=default_query, _strict_response_validation=_strict_response_validation, @@ -219,7 +206,6 @@ def copy( base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.Client | None = None, - connection_pool_limits: httpx.Limits | None = None, max_retries: int | NotGiven = NOT_GIVEN, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, @@ -248,24 +234,7 @@ def copy( elif set_default_query is not None: params = set_default_query - if connection_pool_limits is not None: - if http_client is not None: - raise ValueError("The 'http_client' argument is mutually exclusive with 'connection_pool_limits'") - - if not isinstance(self._client, SyncHttpxClientWrapper): - raise ValueError( - "A custom HTTP client has been set and is mutually exclusive with the 'connection_pool_limits' argument" - ) - - http_client = None - else: - if self._limits is not DEFAULT_CONNECTION_LIMITS: - connection_pool_limits = self._limits - else: - connection_pool_limits = None - - http_client = http_client or self._client - + http_client = http_client or self._client return self.__class__( access_token=access_token or self.access_token, client_id=client_id or self.client_id, @@ -274,7 +243,6 @@ def copy( base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, - connection_pool_limits=connection_pool_limits, max_retries=max_retries if is_given(max_retries) else self.max_retries, default_headers=headers, default_query=params, @@ -419,12 +387,6 @@ def __init__( # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`. # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details. http_client: httpx.AsyncClient | None = None, - # See httpx documentation for [custom transports](https://www.python-httpx.org/advanced/#custom-transports) - transport: AsyncTransport | None = None, - # See httpx documentation for [proxies](https://www.python-httpx.org/advanced/#http-proxying) - proxies: ProxiesTypes | None = None, - # See httpx documentation for [limits](https://www.python-httpx.org/advanced/#pool-limit-configuration) - connection_pool_limits: httpx.Limits | None = None, # Enable or disable schema validation for data returned by the API. # When enabled an error APIResponseValidationError is raised # if the API responds with invalid data for the expected schema. @@ -435,7 +397,7 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async Finch client instance. + """Construct a new async AsyncFinch client instance. This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `client_id` from `FINCH_CLIENT_ID` @@ -467,9 +429,6 @@ def __init__( max_retries=max_retries, timeout=timeout, http_client=http_client, - transport=transport, - proxies=proxies, - limits=connection_pool_limits, custom_headers=default_headers, custom_query=default_query, _strict_response_validation=_strict_response_validation, @@ -555,7 +514,6 @@ def copy( base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = NOT_GIVEN, http_client: httpx.AsyncClient | None = None, - connection_pool_limits: httpx.Limits | None = None, max_retries: int | NotGiven = NOT_GIVEN, default_headers: Mapping[str, str] | None = None, set_default_headers: Mapping[str, str] | None = None, @@ -584,24 +542,7 @@ def copy( elif set_default_query is not None: params = set_default_query - if connection_pool_limits is not None: - if http_client is not None: - raise ValueError("The 'http_client' argument is mutually exclusive with 'connection_pool_limits'") - - if not isinstance(self._client, AsyncHttpxClientWrapper): - raise ValueError( - "A custom HTTP client has been set and is mutually exclusive with the 'connection_pool_limits' argument" - ) - - http_client = None - else: - if self._limits is not DEFAULT_CONNECTION_LIMITS: - connection_pool_limits = self._limits - else: - connection_pool_limits = None - - http_client = http_client or self._client - + http_client = http_client or self._client return self.__class__( access_token=access_token or self.access_token, client_id=client_id or self.client_id, @@ -610,7 +551,6 @@ def copy( base_url=base_url or self.base_url, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, - connection_pool_limits=connection_pool_limits, max_retries=max_retries if is_given(max_retries) else self.max_retries, default_headers=headers, default_query=params, diff --git a/src/finch/_models.py b/src/finch/_models.py index c4401ff8..b51a1bf5 100644 --- a/src/finch/_models.py +++ b/src/finch/_models.py @@ -65,7 +65,7 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: - from pydantic_core.core_schema import ModelField, LiteralSchema, ModelFieldsSchema + from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema __all__ = ["BaseModel", "GenericModel"] @@ -646,15 +646,18 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None: schema = model.__pydantic_core_schema__ + if schema["type"] == "definitions": + schema = schema["schema"] + if schema["type"] != "model": return None + schema = cast("ModelSchema", schema) fields_schema = schema["schema"] if fields_schema["type"] != "model-fields": return None fields_schema = cast("ModelFieldsSchema", fields_schema) - field = fields_schema["fields"].get(field_name) if not field: return None diff --git a/src/finch/_version.py b/src/finch/_version.py index 49d3934d..8d7d3f5a 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.16.0" # x-release-please-version +__version__ = "1.17.0" # x-release-please-version diff --git a/src/finch/resources/hris/documents.py b/src/finch/resources/hris/documents.py index 4413f816..6c60b2f8 100644 --- a/src/finch/resources/hris/documents.py +++ b/src/finch/resources/hris/documents.py @@ -60,8 +60,8 @@ def list( ) -> DocumentListResponse: """**Beta:** This endpoint is in beta and may change. - - Retrieve a list of company-wide documents. + Retrieve a list of + company-wide documents. Args: individual_ids: Comma-delimited list of stable Finch uuids for each individual. If empty, @@ -115,8 +115,8 @@ def retreive( ) -> DocumentRetreiveResponse: """**Beta:** This endpoint is in beta and may change. - - Retrieve details of a specific document by its ID. + Retrieve details of a + specific document by its ID. Args: extra_headers: Send extra headers @@ -179,8 +179,8 @@ async def list( ) -> DocumentListResponse: """**Beta:** This endpoint is in beta and may change. - - Retrieve a list of company-wide documents. + Retrieve a list of + company-wide documents. Args: individual_ids: Comma-delimited list of stable Finch uuids for each individual. If empty, @@ -234,8 +234,8 @@ async def retreive( ) -> DocumentRetreiveResponse: """**Beta:** This endpoint is in beta and may change. - - Retrieve details of a specific document by its ID. + Retrieve details of a + specific document by its ID. Args: extra_headers: Send extra headers diff --git a/src/finch/resources/jobs/automated.py b/src/finch/resources/jobs/automated.py index ce3e70ca..3fa895ce 100644 --- a/src/finch/resources/jobs/automated.py +++ b/src/finch/resources/jobs/automated.py @@ -16,10 +16,10 @@ from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper -from ...pagination import SyncPage, AsyncPage from ...types.jobs import automated_list_params, automated_create_params -from ..._base_client import AsyncPaginator, make_request_options +from ..._base_client import make_request_options from ...types.jobs.automated_async_job import AutomatedAsyncJob +from ...types.jobs.automated_list_response import AutomatedListResponse from ...types.jobs.automated_create_response import AutomatedCreateResponse __all__ = ["Automated", "AsyncAutomated"] @@ -200,7 +200,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncPage[AutomatedAsyncJob]: + ) -> AutomatedListResponse: """Get all automated jobs. Automated jobs are completed by a machine. By default, @@ -220,9 +220,8 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( + return self._get( "/jobs/automated", - page=SyncPage[AutomatedAsyncJob], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -236,7 +235,7 @@ def list( automated_list_params.AutomatedListParams, ), ), - model=AutomatedAsyncJob, + cast_to=AutomatedListResponse, ) @@ -404,7 +403,7 @@ async def retrieve( cast_to=AutomatedAsyncJob, ) - def list( + async def list( self, *, limit: int | NotGiven = NOT_GIVEN, @@ -415,7 +414,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[AutomatedAsyncJob, AsyncPage[AutomatedAsyncJob]]: + ) -> AutomatedListResponse: """Get all automated jobs. Automated jobs are completed by a machine. By default, @@ -435,15 +434,14 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( + return await self._get( "/jobs/automated", - page=AsyncPage[AutomatedAsyncJob], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform( + query=await async_maybe_transform( { "limit": limit, "offset": offset, @@ -451,7 +449,7 @@ def list( automated_list_params.AutomatedListParams, ), ), - model=AutomatedAsyncJob, + cast_to=AutomatedListResponse, ) diff --git a/src/finch/types/jobs/__init__.py b/src/finch/types/jobs/__init__.py index b5eaa10b..ee2d3e8e 100644 --- a/src/finch/types/jobs/__init__.py +++ b/src/finch/types/jobs/__init__.py @@ -6,4 +6,5 @@ from .automated_async_job import AutomatedAsyncJob as AutomatedAsyncJob from .automated_list_params import AutomatedListParams as AutomatedListParams from .automated_create_params import AutomatedCreateParams as AutomatedCreateParams +from .automated_list_response import AutomatedListResponse as AutomatedListResponse from .automated_create_response import AutomatedCreateResponse as AutomatedCreateResponse diff --git a/src/finch/types/jobs/automated_list_response.py b/src/finch/types/jobs/automated_list_response.py new file mode 100644 index 00000000..d934dea6 --- /dev/null +++ b/src/finch/types/jobs/automated_list_response.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from .automated_async_job import AutomatedAsyncJob + +__all__ = ["AutomatedListResponse", "Meta", "MetaQuotas", "MetaQuotasDataSyncAll"] + + +class MetaQuotasDataSyncAll(BaseModel): + allowed_refreshes: Optional[int] = None + + remaining_refreshes: Optional[int] = None + + +class MetaQuotas(BaseModel): + data_sync_all: Optional[MetaQuotasDataSyncAll] = None + + +class Meta(BaseModel): + quotas: Optional[MetaQuotas] = None + """Information about remaining quotas for this connection. + + Only applicable for customers opted in to use Finch's Data Sync Refresh endpoint + (`POST /jobs/automated`). Please contact a Finch representative for more + details. + """ + + +class AutomatedListResponse(BaseModel): + data: List[AutomatedAsyncJob] + + meta: Meta diff --git a/src/finch/types/webhook_event.py b/src/finch/types/webhook_event.py index 17bb1d4f..d0dc5702 100644 --- a/src/finch/types/webhook_event.py +++ b/src/finch/types/webhook_event.py @@ -1,8 +1,9 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Union -from typing_extensions import TypeAlias +from typing_extensions import Annotated, TypeAlias +from .._utils import PropertyInfo from .company_event import CompanyEvent from .payment_event import PaymentEvent from .directory_event import DirectoryEvent @@ -14,13 +15,16 @@ __all__ = ["WebhookEvent"] -WebhookEvent: TypeAlias = Union[ - AccountUpdateEvent, - JobCompletionEvent, - CompanyEvent, - DirectoryEvent, - EmploymentEvent, - IndividualEvent, - PaymentEvent, - PayStatementEvent, +WebhookEvent: TypeAlias = Annotated[ + Union[ + AccountUpdateEvent, + JobCompletionEvent, + CompanyEvent, + DirectoryEvent, + EmploymentEvent, + IndividualEvent, + PaymentEvent, + PayStatementEvent, + ], + PropertyInfo(discriminator="event_type"), ] diff --git a/tests/api_resources/connect/test_sessions.py b/tests/api_resources/connect/test_sessions.py index c9965727..cc53a9b7 100644 --- a/tests/api_resources/connect/test_sessions.py +++ b/tests/api_resources/connect/test_sessions.py @@ -35,7 +35,7 @@ def test_method_new_with_all_params(self, client: Finch) -> None: customer_id="x", customer_name="x", products=["company"], - customer_email="dev@stainlessapi.com", + customer_email="dev@stainless.com", integration={ "auth_method": "assisted", "provider": "provider", @@ -135,7 +135,7 @@ async def test_method_new_with_all_params(self, async_client: AsyncFinch) -> Non customer_id="x", customer_name="x", products=["company"], - customer_email="dev@stainlessapi.com", + customer_email="dev@stainless.com", integration={ "auth_method": "assisted", "provider": "provider", diff --git a/tests/api_resources/jobs/test_automated.py b/tests/api_resources/jobs/test_automated.py index db83b0e5..c0dc5e49 100644 --- a/tests/api_resources/jobs/test_automated.py +++ b/tests/api_resources/jobs/test_automated.py @@ -9,8 +9,11 @@ from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch.pagination import SyncPage, AsyncPage -from finch.types.jobs import AutomatedAsyncJob, AutomatedCreateResponse +from finch.types.jobs import ( + AutomatedAsyncJob, + AutomatedListResponse, + AutomatedCreateResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -124,7 +127,7 @@ def test_path_params_retrieve(self, client: Finch) -> None: @parametrize def test_method_list(self, client: Finch) -> None: automated = client.jobs.automated.list() - assert_matches_type(SyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Finch) -> None: @@ -132,7 +135,7 @@ def test_method_list_with_all_params(self, client: Finch) -> None: limit=0, offset=0, ) - assert_matches_type(SyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) @parametrize def test_raw_response_list(self, client: Finch) -> None: @@ -141,7 +144,7 @@ def test_raw_response_list(self, client: Finch) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() - assert_matches_type(SyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) @parametrize def test_streaming_response_list(self, client: Finch) -> None: @@ -150,7 +153,7 @@ def test_streaming_response_list(self, client: Finch) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() - assert_matches_type(SyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) assert cast(Any, response.is_closed) is True @@ -264,7 +267,7 @@ async def test_path_params_retrieve(self, async_client: AsyncFinch) -> None: @parametrize async def test_method_list(self, async_client: AsyncFinch) -> None: automated = await async_client.jobs.automated.list() - assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncFinch) -> None: @@ -272,7 +275,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncFinch) -> No limit=0, offset=0, ) - assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncFinch) -> None: @@ -281,7 +284,7 @@ async def test_raw_response_list(self, async_client: AsyncFinch) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() - assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: @@ -290,6 +293,6 @@ async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = await response.parse() - assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) + assert_matches_type(AutomatedListResponse, automated, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/sandbox/test_company.py b/tests/api_resources/sandbox/test_company.py index 3c89e1c6..aa0e957f 100644 --- a/tests/api_resources/sandbox/test_company.py +++ b/tests/api_resources/sandbox/test_company.py @@ -26,7 +26,7 @@ def test_method_update(self, client: Finch) -> None: entity={}, legal_name="legal_name", locations=[{}], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) assert_matches_type(CompanyUpdateResponse, company, path=["response"]) @@ -67,7 +67,7 @@ def test_method_update_with_all_params(self, client: Finch) -> None: "state": "state", } ], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) assert_matches_type(CompanyUpdateResponse, company, path=["response"]) @@ -81,7 +81,7 @@ def test_raw_response_update(self, client: Finch) -> None: entity={}, legal_name="legal_name", locations=[{}], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) @@ -99,7 +99,7 @@ def test_streaming_response_update(self, client: Finch) -> None: entity={}, legal_name="legal_name", locations=[{}], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) as response: assert not response.is_closed @@ -123,7 +123,7 @@ async def test_method_update(self, async_client: AsyncFinch) -> None: entity={}, legal_name="legal_name", locations=[{}], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) assert_matches_type(CompanyUpdateResponse, company, path=["response"]) @@ -164,7 +164,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> "state": "state", } ], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) assert_matches_type(CompanyUpdateResponse, company, path=["response"]) @@ -178,7 +178,7 @@ async def test_raw_response_update(self, async_client: AsyncFinch) -> None: entity={}, legal_name="legal_name", locations=[{}], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) @@ -196,7 +196,7 @@ async def test_streaming_response_update(self, async_client: AsyncFinch) -> None entity={}, legal_name="legal_name", locations=[{}], - primary_email="primary_email", + primary_email="dev@stainless.com", primary_phone_number="primary_phone_number", ) as response: assert not response.is_closed diff --git a/tests/test_client.py b/tests/test_client.py index 52d6f178..a52f63d4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -18,7 +18,6 @@ import httpx import pytest -import packaging.version as version from respx import MockRouter from pydantic import ValidationError @@ -645,113 +644,6 @@ def test_absolute_request_url(self, client: Finch) -> None: ) assert request.url == "https://myapi.com/foo" - @pytest.mark.skipif( - version.parse(httpx.__version__) >= version.parse("0.28.0"), - reason="Test is only relevant for httpx versions < 0.28.0", - ) - def test_transport_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `transport` argument is deprecated. The `http_client` argument should be passed instead", - ): - transport = httpx.MockTransport( - lambda: None, # type: ignore - ) - - client = Finch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, transport=transport - ) - - assert client._client._transport is transport - - def test_transport_option_mutually_exclusive_with_http_client(self) -> None: - with httpx.Client() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `transport`"): - with pytest.warns(DeprecationWarning): - Finch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - transport=httpx.MockTransport( - lambda: None, # type: ignore - ), - http_client=http_client, - ) - - @pytest.mark.skipif( - version.parse(httpx.__version__) >= version.parse("0.28.0"), - reason="Test is only relevant for httpx versions < 0.28.0", - ) - def test_connection_pool_limits_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - ): - connection_pool_limits = httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ) - - client = Finch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - connection_pool_limits=connection_pool_limits, - ) - - assert isinstance(client._client._transport, httpx.HTTPTransport) - assert client._client._transport._pool._max_connections == 101 - assert client._client._transport._pool._max_keepalive_connections == 76 - assert client._client._transport._pool._keepalive_expiry == 23 - - def test_connection_pool_limits_option_mutually_exclusive_with_http_client(self) -> None: - with httpx.Client() as http_client: - with pytest.raises( - ValueError, match="The `http_client` argument is mutually exclusive with `connection_pool_limits`" - ): - with pytest.warns(DeprecationWarning): - Finch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - connection_pool_limits=httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ), - http_client=http_client, - ) - - @pytest.mark.skipif( - version.parse(httpx.__version__) >= version.parse("0.28.0"), - reason="Test is only relevant for httpx versions < 0.28.0", - ) - def test_proxies_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - ): - proxies = "https://www.example.com/proxy" - - client = Finch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, proxies=proxies - ) - - mounts = list(client._client._mounts.keys()) - assert len(mounts) == 1 - - pattern = mounts[0].pattern - assert pattern == "all://" - - def test_proxies_option_mutually_exclusive_with_http_client(self) -> None: - with httpx.Client() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `proxies`"): - with pytest.warns(DeprecationWarning): - Finch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - proxies="https://www.example.com/proxy", - http_client=http_client, - ) - def test_copied_client_does_not_close_http(self) -> None: client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) assert not client.is_closed() @@ -1541,113 +1433,6 @@ def test_absolute_request_url(self, client: AsyncFinch) -> None: ) assert request.url == "https://myapi.com/foo" - @pytest.mark.skipif( - version.parse(httpx.__version__) >= version.parse("0.28.0"), - reason="Test is only relevant for httpx versions < 0.28.0", - ) - def test_transport_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `transport` argument is deprecated. The `http_client` argument should be passed instead", - ): - transport = httpx.MockTransport( - lambda: None, # type: ignore - ) - - client = AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, transport=transport - ) - - assert client._client._transport is transport - - async def test_transport_option_mutually_exclusive_with_http_client(self) -> None: - async with httpx.AsyncClient() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `transport`"): - with pytest.warns(DeprecationWarning): - AsyncFinch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - transport=httpx.MockTransport( - lambda: None, # type: ignore - ), - http_client=http_client, - ) - - @pytest.mark.skipif( - version.parse(httpx.__version__) >= version.parse("0.28.0"), - reason="Test is only relevant for httpx versions < 0.28.0", - ) - def test_connection_pool_limits_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `connection_pool_limits` argument is deprecated. The `http_client` argument should be passed instead", - ): - connection_pool_limits = httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ) - - client = AsyncFinch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - connection_pool_limits=connection_pool_limits, - ) - - assert isinstance(client._client._transport, httpx.AsyncHTTPTransport) - assert client._client._transport._pool._max_connections == 101 - assert client._client._transport._pool._max_keepalive_connections == 76 - assert client._client._transport._pool._keepalive_expiry == 23 - - async def test_connection_pool_limits_option_mutually_exclusive_with_http_client(self) -> None: - async with httpx.AsyncClient() as http_client: - with pytest.raises( - ValueError, match="The `http_client` argument is mutually exclusive with `connection_pool_limits`" - ): - with pytest.warns(DeprecationWarning): - AsyncFinch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - connection_pool_limits=httpx.Limits( - max_connections=101, max_keepalive_connections=76, keepalive_expiry=23 - ), - http_client=http_client, - ) - - @pytest.mark.skipif( - version.parse(httpx.__version__) >= version.parse("0.28.0"), - reason="Test is only relevant for httpx versions < 0.28.0", - ) - def test_proxies_option_is_deprecated(self) -> None: - with pytest.warns( - DeprecationWarning, - match="The `proxies` argument is deprecated. The `http_client` argument should be passed instead", - ): - proxies = "https://www.example.com/proxy" - - client = AsyncFinch( - base_url=base_url, access_token=access_token, _strict_response_validation=True, proxies=proxies - ) - - mounts = list(client._client._mounts.keys()) - assert len(mounts) == 1 - - pattern = mounts[0].pattern - assert pattern == "all://" - - async def test_proxies_option_mutually_exclusive_with_http_client(self) -> None: - async with httpx.AsyncClient() as http_client: - with pytest.raises(ValueError, match="The `http_client` argument is mutually exclusive with `proxies`"): - with pytest.warns(DeprecationWarning): - AsyncFinch( - base_url=base_url, - access_token=access_token, - _strict_response_validation=True, - proxies="https://www.example.com/proxy", - http_client=http_client, - ) - async def test_copied_client_does_not_close_http(self) -> None: client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) assert not client.is_closed() diff --git a/tests/test_models.py b/tests/test_models.py index 2def6ec5..ee0f1618 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -854,3 +854,35 @@ class Model(BaseModel): m = construct_type(value={"cls": "foo"}, type_=Model) assert isinstance(m, Model) assert isinstance(m.cls, str) + + +def test_discriminated_union_case() -> None: + class A(BaseModel): + type: Literal["a"] + + data: bool + + class B(BaseModel): + type: Literal["b"] + + data: List[Union[A, object]] + + class ModelA(BaseModel): + type: Literal["modelA"] + + data: int + + class ModelB(BaseModel): + type: Literal["modelB"] + + required: str + + data: Union[A, B] + + # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required` + m = construct_type( + value={"type": "modelB", "data": {"type": "a", "data": True}}, + type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]), + ) + + assert isinstance(m, ModelB)