diff --git a/.release-please-manifest.json b/.release-please-manifest.json index caf14871..de0960ab 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.11.0" + ".": "1.12.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 7349bde2..429b6e48 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 39 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-2526a31a361274411e6cfc64858b1b084a22ffb491a9490374b717534827b3e1.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-cf610d4dbb7e3d84161b5783a0861b2e551422eb5cf727dde86a839325d7ef76.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dcc948b..c872ed99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 1.12.0 (2024-12-17) + +Full Changelog: [v1.11.0...v1.12.0](https://github.com/Finch-API/finch-api-python/compare/v1.11.0...v1.12.0) + +### Features + +* **api:** api update ([#539](https://github.com/Finch-API/finch-api-python/issues/539)) ([8808bc2](https://github.com/Finch-API/finch-api-python/commit/8808bc28d454383cc3375261c5fd68040ba1f63e)) +* **api:** api update ([#542](https://github.com/Finch-API/finch-api-python/issues/542)) ([bf7e2d0](https://github.com/Finch-API/finch-api-python/commit/bf7e2d04a43f70b3dd034ea5d9aae240f21aa271)) +* **api:** api update ([#545](https://github.com/Finch-API/finch-api-python/issues/545)) ([838c2e1](https://github.com/Finch-API/finch-api-python/commit/838c2e12252ee85f1e01c54b18563190f2afa331)) +* **api:** api update ([#546](https://github.com/Finch-API/finch-api-python/issues/546)) ([3fc385f](https://github.com/Finch-API/finch-api-python/commit/3fc385f739eb1d566d3b93a738795f9a0cc76ddf)) +* **api:** api update ([#551](https://github.com/Finch-API/finch-api-python/issues/551)) ([de22285](https://github.com/Finch-API/finch-api-python/commit/de222853bcf92960f3fc27668d66f2df06d13c23)) + + +### Chores + +* **internal:** add support for TypeAliasType ([#544](https://github.com/Finch-API/finch-api-python/issues/544)) ([0b2d28e](https://github.com/Finch-API/finch-api-python/commit/0b2d28e6721227e13ff9f5a9f99cec896532d6cd)) +* **internal:** bump pydantic dependency ([#540](https://github.com/Finch-API/finch-api-python/issues/540)) ([a6bbbbf](https://github.com/Finch-API/finch-api-python/commit/a6bbbbfd913a3900b0d69dcbc0df6869c4e30f1d)) +* **internal:** bump pyright ([#537](https://github.com/Finch-API/finch-api-python/issues/537)) ([1233851](https://github.com/Finch-API/finch-api-python/commit/123385153a2babb6771c64bc3712f5f50a31c4cd)) +* **internal:** bump pyright ([#543](https://github.com/Finch-API/finch-api-python/issues/543)) ([3310b4b](https://github.com/Finch-API/finch-api-python/commit/3310b4bcf1a29c2a966b0c32b8acc91f6955db4e)) +* **internal:** codegen related update ([#535](https://github.com/Finch-API/finch-api-python/issues/535)) ([9bd789d](https://github.com/Finch-API/finch-api-python/commit/9bd789d78363cb269be7e6b213de5d5ba40448cd)) +* **internal:** fix some typos ([#552](https://github.com/Finch-API/finch-api-python/issues/552)) ([0f1f7c8](https://github.com/Finch-API/finch-api-python/commit/0f1f7c8bb990280d63ec21177f84b4cc7d431cff)) +* **internal:** remove some duplicated imports ([#547](https://github.com/Finch-API/finch-api-python/issues/547)) ([7bfcea0](https://github.com/Finch-API/finch-api-python/commit/7bfcea02a906fb2e2d811186b4bc2f46e1ac61e0)) +* **internal:** updated imports ([#548](https://github.com/Finch-API/finch-api-python/issues/548)) ([aa2ca67](https://github.com/Finch-API/finch-api-python/commit/aa2ca675334c9058759c96662219e2efc3d35aa8)) +* make the `Omit` type public ([#538](https://github.com/Finch-API/finch-api-python/issues/538)) ([02a3af8](https://github.com/Finch-API/finch-api-python/commit/02a3af89c699d07d41c3ca28e8e92082935254ca)) + + +### Documentation + +* **readme:** example snippet for client context manager ([#550](https://github.com/Finch-API/finch-api-python/issues/550)) ([a5fff19](https://github.com/Finch-API/finch-api-python/commit/a5fff19f34774cef04415d99b744258f8e966c36)) +* **readme:** fix http client proxies example ([#541](https://github.com/Finch-API/finch-api-python/issues/541)) ([35049ae](https://github.com/Finch-API/finch-api-python/commit/35049aefde2604411e4ecc5b8abaf1cc9a327164)) + ## 1.11.0 (2024-11-28) Full Changelog: [v1.10.1...v1.11.0](https://github.com/Finch-API/finch-api-python/compare/v1.10.1...v1.11.0) diff --git a/README.md b/README.md index 1a97f28b..a482eca8 100644 --- a/README.md +++ b/README.md @@ -379,18 +379,19 @@ can also get all the extra fields on the Pydantic model as a dict with You can directly override the [httpx client](https://www.python-httpx.org/api/#client) to customize it for your use case, including: -- Support for proxies -- Custom transports +- Support for [proxies](https://www.python-httpx.org/advanced/proxies/) +- Custom [transports](https://www.python-httpx.org/advanced/transports/) - Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality ```python +import httpx from finch import Finch, DefaultHttpxClient client = Finch( # Or use the `FINCH_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( - proxies="http://my.test.proxy.example.com", + proxy="http://my.test.proxy.example.com", transport=httpx.HTTPTransport(local_address="0.0.0.0"), ), ) @@ -406,6 +407,16 @@ client.with_options(http_client=DefaultHttpxClient(...)) By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. +```py +from finch import Finch + +with Finch() as client: + # make requests here + ... + +# HTTP client is now closed +``` + ## Versioning This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: diff --git a/pyproject.toml b/pyproject.toml index 50e1c60a..6a53396d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "finch-api" -version = "1.11.0" +version = "1.12.0" description = "The official Python library for the Finch API" dynamic = ["readme"] license = "Apache-2.0" @@ -10,7 +10,7 @@ authors = [ dependencies = [ "httpx>=0.23.0, <1", "pydantic>=1.9.0, <3", - "typing-extensions>=4.7, <5", + "typing-extensions>=4.10, <5", "anyio>=3.5.0, <5", "distro>=1.7.0, <2", "sniffio", diff --git a/requirements-dev.lock b/requirements-dev.lock index 3fa2b889..545f94f7 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -62,13 +62,13 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest -pydantic==2.9.2 +pydantic==2.10.3 # via finch-api -pydantic-core==2.23.4 +pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.380 +pyright==1.1.390 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 @@ -97,6 +97,7 @@ typing-extensions==4.12.2 # via mypy # via pydantic # via pydantic-core + # via pyright virtualenv==20.24.5 # via nox zipp==3.17.0 diff --git a/requirements.lock b/requirements.lock index 205e3487..ab955494 100644 --- a/requirements.lock +++ b/requirements.lock @@ -30,9 +30,9 @@ httpx==0.25.2 idna==3.4 # via anyio # via httpx -pydantic==2.9.2 +pydantic==2.10.3 # via finch-api -pydantic-core==2.23.4 +pydantic-core==2.27.1 # via pydantic sniffio==1.3.0 # via anyio diff --git a/src/finch/__init__.py b/src/finch/__init__.py index 0ca73426..b46d4f6e 100644 --- a/src/finch/__init__.py +++ b/src/finch/__init__.py @@ -1,7 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from . import types -from ._types import NOT_GIVEN, NoneType, NotGiven, Transport, ProxiesTypes +from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path from ._client import Finch, Client, Stream, Timeout, Transport, AsyncFinch, AsyncClient, AsyncStream, RequestOptions from ._models import BaseModel @@ -36,6 +36,7 @@ "ProxiesTypes", "NotGiven", "NOT_GIVEN", + "Omit", "FinchError", "APIError", "APIStatusError", diff --git a/src/finch/_client.py b/src/finch/_client.py index 457b2d33..46a7fa61 100644 --- a/src/finch/_client.py +++ b/src/finch/_client.py @@ -9,7 +9,7 @@ import httpx -from . import resources, _exceptions +from . import _exceptions from ._qs import Querystring from ._types import ( NOT_GIVEN, @@ -27,6 +27,7 @@ get_async_library, ) from ._version import __version__ +from .resources import account, webhooks, providers, access_tokens, request_forwarding from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError from ._base_client import ( @@ -37,31 +38,26 @@ SyncHttpxClientWrapper, AsyncHttpxClientWrapper, ) +from .resources.hris import hris +from .resources.jobs import jobs +from .resources.connect import connect +from .resources.payroll import payroll +from .resources.sandbox import sandbox -__all__ = [ - "Timeout", - "Transport", - "ProxiesTypes", - "RequestOptions", - "resources", - "Finch", - "AsyncFinch", - "Client", - "AsyncClient", -] +__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Finch", "AsyncFinch", "Client", "AsyncClient"] class Finch(SyncAPIClient): - access_tokens: resources.AccessTokens - hris: resources.HRIS - providers: resources.Providers - account: resources.Account - webhooks: resources.Webhooks - request_forwarding: resources.RequestForwarding - jobs: resources.Jobs - sandbox: resources.Sandbox - payroll: resources.Payroll - connect: resources.Connect + access_tokens: access_tokens.AccessTokens + hris: hris.HRIS + providers: providers.Providers + account: account.Account + webhooks: webhooks.Webhooks + request_forwarding: request_forwarding.RequestForwarding + jobs: jobs.Jobs + sandbox: sandbox.Sandbox + payroll: payroll.Payroll + connect: connect.Connect with_raw_response: FinchWithRawResponse with_streaming_response: FinchWithStreamedResponse @@ -143,16 +139,16 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.access_tokens = resources.AccessTokens(self) - self.hris = resources.HRIS(self) - self.providers = resources.Providers(self) - self.account = resources.Account(self) - self.webhooks = resources.Webhooks(self) - self.request_forwarding = resources.RequestForwarding(self) - self.jobs = resources.Jobs(self) - self.sandbox = resources.Sandbox(self) - self.payroll = resources.Payroll(self) - self.connect = resources.Connect(self) + self.access_tokens = access_tokens.AccessTokens(self) + self.hris = hris.HRIS(self) + self.providers = providers.Providers(self) + self.account = account.Account(self) + self.webhooks = webhooks.Webhooks(self) + self.request_forwarding = request_forwarding.RequestForwarding(self) + self.jobs = jobs.Jobs(self) + self.sandbox = sandbox.Sandbox(self) + self.payroll = payroll.Payroll(self) + self.connect = connect.Connect(self) self.with_raw_response = FinchWithRawResponse(self) self.with_streaming_response = FinchWithStreamedResponse(self) @@ -388,16 +384,16 @@ def _make_status_error( class AsyncFinch(AsyncAPIClient): - access_tokens: resources.AsyncAccessTokens - hris: resources.AsyncHRIS - providers: resources.AsyncProviders - account: resources.AsyncAccount - webhooks: resources.AsyncWebhooks - request_forwarding: resources.AsyncRequestForwarding - jobs: resources.AsyncJobs - sandbox: resources.AsyncSandbox - payroll: resources.AsyncPayroll - connect: resources.AsyncConnect + access_tokens: access_tokens.AsyncAccessTokens + hris: hris.AsyncHRIS + providers: providers.AsyncProviders + account: account.AsyncAccount + webhooks: webhooks.AsyncWebhooks + request_forwarding: request_forwarding.AsyncRequestForwarding + jobs: jobs.AsyncJobs + sandbox: sandbox.AsyncSandbox + payroll: payroll.AsyncPayroll + connect: connect.AsyncConnect with_raw_response: AsyncFinchWithRawResponse with_streaming_response: AsyncFinchWithStreamedResponse @@ -479,16 +475,16 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.access_tokens = resources.AsyncAccessTokens(self) - self.hris = resources.AsyncHRIS(self) - self.providers = resources.AsyncProviders(self) - self.account = resources.AsyncAccount(self) - self.webhooks = resources.AsyncWebhooks(self) - self.request_forwarding = resources.AsyncRequestForwarding(self) - self.jobs = resources.AsyncJobs(self) - self.sandbox = resources.AsyncSandbox(self) - self.payroll = resources.AsyncPayroll(self) - self.connect = resources.AsyncConnect(self) + self.access_tokens = access_tokens.AsyncAccessTokens(self) + self.hris = hris.AsyncHRIS(self) + self.providers = providers.AsyncProviders(self) + self.account = account.AsyncAccount(self) + self.webhooks = webhooks.AsyncWebhooks(self) + self.request_forwarding = request_forwarding.AsyncRequestForwarding(self) + self.jobs = jobs.AsyncJobs(self) + self.sandbox = sandbox.AsyncSandbox(self) + self.payroll = payroll.AsyncPayroll(self) + self.connect = connect.AsyncConnect(self) self.with_raw_response = AsyncFinchWithRawResponse(self) self.with_streaming_response = AsyncFinchWithStreamedResponse(self) @@ -725,54 +721,56 @@ def _make_status_error( class FinchWithRawResponse: def __init__(self, client: Finch) -> None: - self.access_tokens = resources.AccessTokensWithRawResponse(client.access_tokens) - self.hris = resources.HRISWithRawResponse(client.hris) - self.providers = resources.ProvidersWithRawResponse(client.providers) - self.account = resources.AccountWithRawResponse(client.account) - self.request_forwarding = resources.RequestForwardingWithRawResponse(client.request_forwarding) - self.jobs = resources.JobsWithRawResponse(client.jobs) - self.sandbox = resources.SandboxWithRawResponse(client.sandbox) - self.payroll = resources.PayrollWithRawResponse(client.payroll) - self.connect = resources.ConnectWithRawResponse(client.connect) + self.access_tokens = access_tokens.AccessTokensWithRawResponse(client.access_tokens) + self.hris = hris.HRISWithRawResponse(client.hris) + self.providers = providers.ProvidersWithRawResponse(client.providers) + self.account = account.AccountWithRawResponse(client.account) + self.request_forwarding = request_forwarding.RequestForwardingWithRawResponse(client.request_forwarding) + self.jobs = jobs.JobsWithRawResponse(client.jobs) + self.sandbox = sandbox.SandboxWithRawResponse(client.sandbox) + self.payroll = payroll.PayrollWithRawResponse(client.payroll) + self.connect = connect.ConnectWithRawResponse(client.connect) class AsyncFinchWithRawResponse: def __init__(self, client: AsyncFinch) -> None: - self.access_tokens = resources.AsyncAccessTokensWithRawResponse(client.access_tokens) - self.hris = resources.AsyncHRISWithRawResponse(client.hris) - self.providers = resources.AsyncProvidersWithRawResponse(client.providers) - self.account = resources.AsyncAccountWithRawResponse(client.account) - self.request_forwarding = resources.AsyncRequestForwardingWithRawResponse(client.request_forwarding) - self.jobs = resources.AsyncJobsWithRawResponse(client.jobs) - self.sandbox = resources.AsyncSandboxWithRawResponse(client.sandbox) - self.payroll = resources.AsyncPayrollWithRawResponse(client.payroll) - self.connect = resources.AsyncConnectWithRawResponse(client.connect) + self.access_tokens = access_tokens.AsyncAccessTokensWithRawResponse(client.access_tokens) + self.hris = hris.AsyncHRISWithRawResponse(client.hris) + self.providers = providers.AsyncProvidersWithRawResponse(client.providers) + self.account = account.AsyncAccountWithRawResponse(client.account) + self.request_forwarding = request_forwarding.AsyncRequestForwardingWithRawResponse(client.request_forwarding) + self.jobs = jobs.AsyncJobsWithRawResponse(client.jobs) + self.sandbox = sandbox.AsyncSandboxWithRawResponse(client.sandbox) + self.payroll = payroll.AsyncPayrollWithRawResponse(client.payroll) + self.connect = connect.AsyncConnectWithRawResponse(client.connect) class FinchWithStreamedResponse: def __init__(self, client: Finch) -> None: - self.access_tokens = resources.AccessTokensWithStreamingResponse(client.access_tokens) - self.hris = resources.HRISWithStreamingResponse(client.hris) - self.providers = resources.ProvidersWithStreamingResponse(client.providers) - self.account = resources.AccountWithStreamingResponse(client.account) - self.request_forwarding = resources.RequestForwardingWithStreamingResponse(client.request_forwarding) - self.jobs = resources.JobsWithStreamingResponse(client.jobs) - self.sandbox = resources.SandboxWithStreamingResponse(client.sandbox) - self.payroll = resources.PayrollWithStreamingResponse(client.payroll) - self.connect = resources.ConnectWithStreamingResponse(client.connect) + self.access_tokens = access_tokens.AccessTokensWithStreamingResponse(client.access_tokens) + self.hris = hris.HRISWithStreamingResponse(client.hris) + self.providers = providers.ProvidersWithStreamingResponse(client.providers) + self.account = account.AccountWithStreamingResponse(client.account) + self.request_forwarding = request_forwarding.RequestForwardingWithStreamingResponse(client.request_forwarding) + self.jobs = jobs.JobsWithStreamingResponse(client.jobs) + self.sandbox = sandbox.SandboxWithStreamingResponse(client.sandbox) + self.payroll = payroll.PayrollWithStreamingResponse(client.payroll) + self.connect = connect.ConnectWithStreamingResponse(client.connect) class AsyncFinchWithStreamedResponse: def __init__(self, client: AsyncFinch) -> None: - self.access_tokens = resources.AsyncAccessTokensWithStreamingResponse(client.access_tokens) - self.hris = resources.AsyncHRISWithStreamingResponse(client.hris) - self.providers = resources.AsyncProvidersWithStreamingResponse(client.providers) - self.account = resources.AsyncAccountWithStreamingResponse(client.account) - self.request_forwarding = resources.AsyncRequestForwardingWithStreamingResponse(client.request_forwarding) - self.jobs = resources.AsyncJobsWithStreamingResponse(client.jobs) - self.sandbox = resources.AsyncSandboxWithStreamingResponse(client.sandbox) - self.payroll = resources.AsyncPayrollWithStreamingResponse(client.payroll) - self.connect = resources.AsyncConnectWithStreamingResponse(client.connect) + self.access_tokens = access_tokens.AsyncAccessTokensWithStreamingResponse(client.access_tokens) + self.hris = hris.AsyncHRISWithStreamingResponse(client.hris) + self.providers = providers.AsyncProvidersWithStreamingResponse(client.providers) + self.account = account.AsyncAccountWithStreamingResponse(client.account) + self.request_forwarding = request_forwarding.AsyncRequestForwardingWithStreamingResponse( + client.request_forwarding + ) + self.jobs = jobs.AsyncJobsWithStreamingResponse(client.jobs) + self.sandbox = sandbox.AsyncSandboxWithStreamingResponse(client.sandbox) + self.payroll = payroll.AsyncPayrollWithStreamingResponse(client.payroll) + self.connect = connect.AsyncConnectWithStreamingResponse(client.connect) Client = Finch diff --git a/src/finch/_legacy_response.py b/src/finch/_legacy_response.py index 739b2f93..ad595b48 100644 --- a/src/finch/_legacy_response.py +++ b/src/finch/_legacy_response.py @@ -24,7 +24,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_arg, is_annotated_type +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -188,9 +188,15 @@ def elapsed(self) -> datetime.timedelta: return self.http_response.elapsed def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + # unwrap `Annotated[T, ...]` -> `T` - if to and is_annotated_type(to): - to = extract_type_arg(to, 0) + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) if self._stream: if to: @@ -226,18 +232,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), ), ) - cast_to = to if to is not None else self._cast_to - - # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(cast_to): - cast_to = extract_type_arg(cast_to, 0) - if cast_to is NoneType: return cast(R, None) diff --git a/src/finch/_models.py b/src/finch/_models.py index 6cb469e2..7a547ce5 100644 --- a/src/finch/_models.py +++ b/src/finch/_models.py @@ -46,6 +46,7 @@ strip_not_given, extract_type_arg, is_annotated_type, + is_type_alias_type, strip_annotated_type, ) from ._compat import ( @@ -428,6 +429,8 @@ def construct_type(*, value: object, type_: object) -> object: # we allow `object` as the input type because otherwise, passing things like # `Literal['value']` will be reported as a type error by type checkers type_ = cast("type[object]", type_) + if is_type_alias_type(type_): + type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): diff --git a/src/finch/_response.py b/src/finch/_response.py index d22f84dc..c1d11a9c 100644 --- a/src/finch/_response.py +++ b/src/finch/_response.py @@ -25,7 +25,7 @@ import pydantic from ._types import NoneType -from ._utils import is_given, extract_type_arg, is_annotated_type, extract_type_var_from_base +from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type @@ -126,9 +126,15 @@ def __repr__(self) -> str: ) def _parse(self, *, to: type[_T] | None = None) -> R | _T: + cast_to = to if to is not None else self._cast_to + + # unwrap `TypeAlias('Name', T)` -> `T` + if is_type_alias_type(cast_to): + cast_to = cast_to.__value__ # type: ignore[unreachable] + # unwrap `Annotated[T, ...]` -> `T` - if to and is_annotated_type(to): - to = extract_type_arg(to, 0) + if cast_to and is_annotated_type(cast_to): + cast_to = extract_type_arg(cast_to, 0) if self._is_sse_stream: if to: @@ -164,18 +170,12 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: return cast( R, stream_cls( - cast_to=self._cast_to, + cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), ), ) - cast_to = to if to is not None else self._cast_to - - # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(cast_to): - cast_to = extract_type_arg(cast_to, 0) - if cast_to is NoneType: return cast(R, None) diff --git a/src/finch/_types.py b/src/finch/_types.py index 06f00715..643bfe5e 100644 --- a/src/finch/_types.py +++ b/src/finch/_types.py @@ -194,10 +194,8 @@ def get(self, __key: str) -> str | None: ... StrBytesIntFloat = Union[str, bytes, int, float] # Note: copied from Pydantic -# https://github.com/pydantic/pydantic/blob/32ea570bf96e84234d2992e1ddf40ab8a565925a/pydantic/main.py#L49 -IncEx: TypeAlias = Union[ - Set[int], Set[str], Mapping[int, Union["IncEx", Literal[True]]], Mapping[str, Union["IncEx", Literal[True]]] -] +# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79 +IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]] PostParser = Callable[[Any], Any] diff --git a/src/finch/_utils/__init__.py b/src/finch/_utils/__init__.py index a7cff3c0..d4fda26f 100644 --- a/src/finch/_utils/__init__.py +++ b/src/finch/_utils/__init__.py @@ -39,6 +39,7 @@ is_iterable_type as is_iterable_type, is_required_type as is_required_type, is_annotated_type as is_annotated_type, + is_type_alias_type as is_type_alias_type, strip_annotated_type as strip_annotated_type, extract_type_var_from_base as extract_type_var_from_base, ) diff --git a/src/finch/_utils/_typing.py b/src/finch/_utils/_typing.py index c036991f..278749b1 100644 --- a/src/finch/_utils/_typing.py +++ b/src/finch/_utils/_typing.py @@ -1,8 +1,17 @@ from __future__ import annotations +import sys +import typing +import typing_extensions from typing import Any, TypeVar, Iterable, cast from collections import abc as _c_abc -from typing_extensions import Required, Annotated, get_args, get_origin +from typing_extensions import ( + TypeIs, + Required, + Annotated, + get_args, + get_origin, +) from .._types import InheritsGeneric from .._compat import is_union as _is_union @@ -36,6 +45,26 @@ def is_typevar(typ: type) -> bool: return type(typ) == TypeVar # type: ignore +_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) +if sys.version_info >= (3, 12): + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + + +def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: + """Return whether the provided argument is an instance of `TypeAliasType`. + + ```python + type Int = int + is_type_alias_type(Int) + # > True + Str = TypeAliasType("Str", str) + is_type_alias_type(Str) + # > True + ``` + """ + return isinstance(tp, _TYPE_ALIAS_TYPES) + + # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): diff --git a/src/finch/_version.py b/src/finch/_version.py index e6a13861..2ebb1bc5 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.11.0" # x-release-please-version +__version__ = "1.12.0" # x-release-please-version diff --git a/src/finch/pagination.py b/src/finch/pagination.py index 33ed744d..c2afd69f 100644 --- a/src/finch/pagination.py +++ b/src/finch/pagination.py @@ -136,23 +136,15 @@ def _get_page_items(self) -> List[_T]: def next_page_info(self) -> Optional[PageInfo]: offset = None if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - offset = self.paging.offset + if self.paging.offset is not None: + offset = self.paging.offset if offset is None: return None length = len(self._get_page_items()) current_count = offset + length - count = None - if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - count = self.paging.count - if count is None: - return None - - if current_count < count: - return PageInfo(params={"offset": current_count}) - - return None + return PageInfo(params={"offset": current_count}) class AsyncIndividualsPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): @@ -171,23 +163,15 @@ def _get_page_items(self) -> List[_T]: def next_page_info(self) -> Optional[PageInfo]: offset = None if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - offset = self.paging.offset + if self.paging.offset is not None: + offset = self.paging.offset if offset is None: return None length = len(self._get_page_items()) current_count = offset + length - count = None - if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - count = self.paging.count - if count is None: - return None - - if current_count < count: - return PageInfo(params={"offset": current_count}) - - return None + return PageInfo(params={"offset": current_count}) class SyncPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]): @@ -205,23 +189,15 @@ def _get_page_items(self) -> List[_T]: def next_page_info(self) -> Optional[PageInfo]: offset = None if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - offset = self.paging.offset + if self.paging.offset is not None: + offset = self.paging.offset if offset is None: return None length = len(self._get_page_items()) current_count = offset + length - count = None - if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - count = self.paging.count - if count is None: - return None - - if current_count < count: - return PageInfo(params={"offset": current_count}) - - return None + return PageInfo(params={"offset": current_count}) class AsyncPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): @@ -239,20 +215,12 @@ def _get_page_items(self) -> List[_T]: def next_page_info(self) -> Optional[PageInfo]: offset = None if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - offset = self.paging.offset + if self.paging.offset is not None: + offset = self.paging.offset if offset is None: return None length = len(self._get_page_items()) current_count = offset + length - count = None - if self.paging is not None: # pyright: ignore[reportUnnecessaryComparison] - count = self.paging.count - if count is None: - return None - - if current_count < count: - return PageInfo(params={"offset": current_count}) - - return None + return PageInfo(params={"offset": current_count}) diff --git a/src/finch/resources/hris/hris.py b/src/finch/resources/hris/hris.py index 50c5a621..abbd52fd 100644 --- a/src/finch/resources/hris/hris.py +++ b/src/finch/resources/hris/hris.py @@ -10,14 +10,6 @@ CompanyResourceWithStreamingResponse, AsyncCompanyResourceWithStreamingResponse, ) -from .benefits import ( - Benefits, - AsyncBenefits, - BenefitsWithRawResponse, - AsyncBenefitsWithRawResponse, - BenefitsWithStreamingResponse, - AsyncBenefitsWithStreamingResponse, -) from .payments import ( Payments, AsyncPayments, @@ -60,7 +52,14 @@ PayStatementsWithStreamingResponse, AsyncPayStatementsWithStreamingResponse, ) -from .benefits.benefits import Benefits, AsyncBenefits +from .benefits.benefits import ( + Benefits, + AsyncBenefits, + BenefitsWithRawResponse, + AsyncBenefitsWithRawResponse, + BenefitsWithStreamingResponse, + AsyncBenefitsWithStreamingResponse, +) __all__ = ["HRIS", "AsyncHRIS"] diff --git a/src/finch/resources/sandbox/employment.py b/src/finch/resources/sandbox/employment.py index 3adb0c1a..7a79c759 100644 --- a/src/finch/resources/sandbox/employment.py +++ b/src/finch/resources/sandbox/employment.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Iterable, Optional +from typing_extensions import Literal import httpx @@ -52,6 +53,10 @@ def update( custom_fields: Iterable[employment_update_params.CustomField] | NotGiven = NOT_GIVEN, department: Optional[employment_update_params.Department] | NotGiven = NOT_GIVEN, employment: Optional[employment_update_params.Employment] | NotGiven = NOT_GIVEN, + employment_status: Optional[ + Literal["active", "deceased", "leave", "onboarding", "prehire", "retired", "terminated"] + ] + | NotGiven = NOT_GIVEN, end_date: Optional[str] | NotGiven = NOT_GIVEN, first_name: Optional[str] | NotGiven = NOT_GIVEN, income: Optional[IncomeParam] | NotGiven = NOT_GIVEN, @@ -86,6 +91,8 @@ def update( employment: The employment object. + employment_status: The detailed employment status of the individual. + first_name: The legal first name of the individual. income: The employee's income as reported by the provider. This may not always be @@ -126,6 +133,7 @@ def update( "custom_fields": custom_fields, "department": department, "employment": employment, + "employment_status": employment_status, "end_date": end_date, "first_name": first_name, "income": income, @@ -177,6 +185,10 @@ async def update( custom_fields: Iterable[employment_update_params.CustomField] | NotGiven = NOT_GIVEN, department: Optional[employment_update_params.Department] | NotGiven = NOT_GIVEN, employment: Optional[employment_update_params.Employment] | NotGiven = NOT_GIVEN, + employment_status: Optional[ + Literal["active", "deceased", "leave", "onboarding", "prehire", "retired", "terminated"] + ] + | NotGiven = NOT_GIVEN, end_date: Optional[str] | NotGiven = NOT_GIVEN, first_name: Optional[str] | NotGiven = NOT_GIVEN, income: Optional[IncomeParam] | NotGiven = NOT_GIVEN, @@ -211,6 +223,8 @@ async def update( employment: The employment object. + employment_status: The detailed employment status of the individual. + first_name: The legal first name of the individual. income: The employee's income as reported by the provider. This may not always be @@ -251,6 +265,7 @@ async def update( "custom_fields": custom_fields, "department": department, "employment": employment, + "employment_status": employment_status, "end_date": end_date, "first_name": first_name, "income": income, diff --git a/src/finch/resources/sandbox/sandbox.py b/src/finch/resources/sandbox/sandbox.py index 33f73435..10dfc5ae 100644 --- a/src/finch/resources/sandbox/sandbox.py +++ b/src/finch/resources/sandbox/sandbox.py @@ -2,14 +2,6 @@ from __future__ import annotations -from .jobs import ( - Jobs, - AsyncJobs, - JobsWithRawResponse, - AsyncJobsWithRawResponse, - JobsWithStreamingResponse, - AsyncJobsWithStreamingResponse, -) from .company import ( Company, AsyncCompany, @@ -35,7 +27,14 @@ DirectoryWithStreamingResponse, AsyncDirectoryWithStreamingResponse, ) -from .jobs.jobs import Jobs, AsyncJobs +from .jobs.jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) from .employment import ( Employment, AsyncEmployment, @@ -53,7 +52,7 @@ AsyncIndividualWithStreamingResponse, ) from ..._resource import SyncAPIResource, AsyncAPIResource -from .connections import ( +from .connections.connections import ( Connections, AsyncConnections, ConnectionsWithRawResponse, @@ -61,7 +60,6 @@ ConnectionsWithStreamingResponse, AsyncConnectionsWithStreamingResponse, ) -from .connections.connections import Connections, AsyncConnections __all__ = ["Sandbox", "AsyncSandbox"] diff --git a/src/finch/types/account_update_event.py b/src/finch/types/account_update_event.py index fe85887d..792283ec 100644 --- a/src/finch/types/account_update_event.py +++ b/src/finch/types/account_update_event.py @@ -187,6 +187,8 @@ class AccountUpdateEventDataAuthenticationMethodSupportedFieldsEmployment(BaseMo employment: Optional[AccountUpdateEventDataAuthenticationMethodSupportedFieldsEmploymentEmployment] = None + employment_status: Optional[bool] = None + end_date: Optional[bool] = None first_name: Optional[bool] = None diff --git a/src/finch/types/create_access_token_response.py b/src/finch/types/create_access_token_response.py index 2a1b685a..c443de74 100644 --- a/src/finch/types/create_access_token_response.py +++ b/src/finch/types/create_access_token_response.py @@ -48,3 +48,6 @@ class CreateAccessTokenResponse(BaseModel): The ID of your customer you provided to Finch when a connect session was created for this connection. """ + + token_type: Optional[str] = None + """The RFC 8693 token type (Finch uses `bearer` tokens)""" diff --git a/src/finch/types/hris/benefits/individual_enroll_many_params.py b/src/finch/types/hris/benefits/individual_enroll_many_params.py index 857de1b9..95164f87 100644 --- a/src/finch/types/hris/benefits/individual_enroll_many_params.py +++ b/src/finch/types/hris/benefits/individual_enroll_many_params.py @@ -2,10 +2,16 @@ from __future__ import annotations -from typing import Iterable -from typing_extensions import Required, TypedDict +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict -__all__ = ["IndividualEnrollManyParams", "Individual"] +__all__ = [ + "IndividualEnrollManyParams", + "Individual", + "IndividualConfiguration", + "IndividualConfigurationCompanyContribution", + "IndividualConfigurationEmployeeDeduction", +] class IndividualEnrollManyParams(TypedDict, total=False): @@ -13,8 +19,46 @@ class IndividualEnrollManyParams(TypedDict, total=False): """Array of the individual_id to enroll and a configuration object.""" +class IndividualConfigurationCompanyContribution(TypedDict, total=False): + amount: int + """ + Amount in cents for fixed type or basis points (1/100th of a percent) for + percent type + """ + + type: Literal["fixed", "percent"] + + +class IndividualConfigurationEmployeeDeduction(TypedDict, total=False): + amount: int + """ + Amount in cents for fixed type or basis points (1/100th of a percent) for + percent type + """ + + type: Literal["fixed", "percent"] + + +class IndividualConfiguration(TypedDict, total=False): + annual_contribution_limit: Literal["individual", "family"] + """ + For HSA benefits only - whether the contribution limit is for an individual or + family + """ + + annual_maximum: Optional[int] + """Maximum annual amount in cents""" + + catch_up: bool + """For retirement benefits only - whether catch up contributions are enabled""" + + company_contribution: IndividualConfigurationCompanyContribution + + employee_deduction: IndividualConfigurationEmployeeDeduction + + class Individual(TypedDict, total=False): - configuration: object + configuration: IndividualConfiguration individual_id: str """Finch id (uuidv4) for the individual to enroll""" diff --git a/src/finch/types/hris/employment_data.py b/src/finch/types/hris/employment_data.py index 6c9a161d..cc18ecc2 100644 --- a/src/finch/types/hris/employment_data.py +++ b/src/finch/types/hris/employment_data.py @@ -57,6 +57,15 @@ class EmploymentData(BaseModel): employment: Optional[Employment] = None """The employment object.""" + employment_status: Optional[ + Literal["active", "deceased", "leave", "onboarding", "prehire", "retired", "terminated"] + ] = None + """The detailed employment status of the individual. + + Available options: `active`, `deceased`, `leave`, `onboarding`, `prehire`, + `retired`, `terminated`. + """ + end_date: Optional[str] = None first_name: Optional[str] = None diff --git a/src/finch/types/provider.py b/src/finch/types/provider.py index 8e81f3a5..b7051379 100644 --- a/src/finch/types/provider.py +++ b/src/finch/types/provider.py @@ -184,6 +184,8 @@ class AuthenticationMethodSupportedFieldsEmployment(BaseModel): employment: Optional[AuthenticationMethodSupportedFieldsEmploymentEmployment] = None + employment_status: Optional[bool] = None + end_date: Optional[bool] = None first_name: Optional[bool] = None diff --git a/src/finch/types/sandbox/connection_create_response.py b/src/finch/types/sandbox/connection_create_response.py index 1eac9e87..fd314016 100644 --- a/src/finch/types/sandbox/connection_create_response.py +++ b/src/finch/types/sandbox/connection_create_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List +from typing import List, Optional from typing_extensions import Literal from ..._models import BaseModel @@ -26,3 +26,5 @@ class ConnectionCreateResponse(BaseModel): provider_id: str """The ID of the provider associated with the `access_token`.""" + + token_type: Optional[str] = None diff --git a/src/finch/types/sandbox/directory_create_params.py b/src/finch/types/sandbox/directory_create_params.py index f8320f44..1684d178 100644 --- a/src/finch/types/sandbox/directory_create_params.py +++ b/src/finch/types/sandbox/directory_create_params.py @@ -90,6 +90,11 @@ class Body(TypedDict, total=False): employment: Optional[BodyEmployment] """The employment object.""" + employment_status: Optional[ + Literal["active", "deceased", "leave", "onboarding", "prehire", "retired", "terminated"] + ] + """The detailed employment status of the individual.""" + encrypted_ssn: Optional[str] """Social Security Number of the individual in **encrypted** format. diff --git a/src/finch/types/sandbox/employment_update_params.py b/src/finch/types/sandbox/employment_update_params.py index 0b6382c5..651e5eaa 100644 --- a/src/finch/types/sandbox/employment_update_params.py +++ b/src/finch/types/sandbox/employment_update_params.py @@ -28,6 +28,11 @@ class EmploymentUpdateParams(TypedDict, total=False): employment: Optional[Employment] """The employment object.""" + employment_status: Optional[ + Literal["active", "deceased", "leave", "onboarding", "prehire", "retired", "terminated"] + ] + """The detailed employment status of the individual.""" + end_date: Optional[str] first_name: Optional[str] diff --git a/src/finch/types/sandbox/employment_update_response.py b/src/finch/types/sandbox/employment_update_response.py index 803332ea..072af47f 100644 --- a/src/finch/types/sandbox/employment_update_response.py +++ b/src/finch/types/sandbox/employment_update_response.py @@ -58,6 +58,11 @@ class EmploymentUpdateResponse(BaseModel): employment: Optional[Employment] = None """The employment object.""" + employment_status: Optional[ + Literal["active", "deceased", "leave", "onboarding", "prehire", "retired", "terminated"] + ] = None + """The detailed employment status of the individual.""" + end_date: Optional[str] = None first_name: Optional[str] = None diff --git a/tests/api_resources/sandbox/test_employment.py b/tests/api_resources/sandbox/test_employment.py index 96546527..677cbc8e 100644 --- a/tests/api_resources/sandbox/test_employment.py +++ b/tests/api_resources/sandbox/test_employment.py @@ -40,6 +40,7 @@ def test_method_update_with_all_params(self, client: Finch) -> None: "subtype": "full_time", "type": "employee", }, + employment_status="active", end_date="end_date", first_name="first_name", income={ @@ -135,6 +136,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> "subtype": "full_time", "type": "employee", }, + employment_status="active", end_date="end_date", first_name="first_name", income={ diff --git a/tests/test_client.py b/tests/test_client.py index efbc1186..b43a181c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -358,11 +358,11 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden"}, + params={"foo": "baz", "query_param": "overridden"}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden"} + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} def test_request_extra_json(self) -> None: request = self.client._build_request( @@ -1242,11 +1242,11 @@ def test_default_query_option(self) -> None: FinalRequestOptions( method="get", url="/foo", - params={"foo": "baz", "query_param": "overriden"}, + params={"foo": "baz", "query_param": "overridden"}, ) ) url = httpx.URL(request.url) - assert dict(url.params) == {"foo": "baz", "query_param": "overriden"} + assert dict(url.params) == {"foo": "baz", "query_param": "overridden"} def test_request_extra_json(self) -> None: request = self.client._build_request( diff --git a/tests/test_models.py b/tests/test_models.py index faebc131..3e66ab0e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,7 @@ import json from typing import Any, Dict, List, Union, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal, Annotated +from typing_extensions import Literal, Annotated, TypeAliasType import pytest import pydantic @@ -828,3 +828,19 @@ class B(BaseModel): # if the discriminator details object stays the same between invocations then # we hit the cache assert UnionType.__discriminator__ is discriminator + + +@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +def test_type_alias_type() -> None: + Alias = TypeAliasType("Alias", str) + + class Model(BaseModel): + alias: Alias + union: Union[int, Alias] + + m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.alias, str) + assert m.alias == "foo" + assert isinstance(m.union, str) + assert m.union == "bar" diff --git a/tests/utils.py b/tests/utils.py index 2af6d870..9a6d6385 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -16,6 +16,7 @@ is_union_type, extract_type_arg, is_annotated_type, + is_type_alias_type, ) from finch._compat import PYDANTIC_V2, field_outer_type, get_model_fields from finch._models import BaseModel @@ -51,6 +52,9 @@ def assert_matches_type( path: list[str], allow_none: bool = False, ) -> None: + if is_type_alias_type(type_): + type_ = type_.__value__ + # unwrap `Annotated[T, ...]` -> `T` if is_annotated_type(type_): type_ = extract_type_arg(type_, 0)