diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index b9da964d..bbeb30b1 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -17,6 +17,7 @@
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": ".venv/bin/python",
+ "python.defaultInterpreterPath": ".venv/bin/python",
"python.typeChecking": "basic",
"terminal.integrated.env.linux": {
"PATH": "/home/vscode/.rye/shims:${env:PATH}"
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 4208b5cb..1b77f506 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.6.0"
+ ".": "0.7.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3d0fc5d..fd51e016 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,35 @@
# Changelog
+## 0.7.0 (2023-11-15)
+
+Full Changelog: [v0.6.0...v0.7.0](https://github.com/Finch-API/finch-api-python/compare/v0.6.0...v0.7.0)
+
+### Features
+
+* **api:** updates ([#184](https://github.com/Finch-API/finch-api-python/issues/184)) ([1fbf21e](https://github.com/Finch-API/finch-api-python/commit/1fbf21e96dc700db462b619da54897ce156301a9))
+* **client:** support passing chunk size for binary responses ([#175](https://github.com/Finch-API/finch-api-python/issues/175)) ([3d02d4b](https://github.com/Finch-API/finch-api-python/commit/3d02d4ba528ad199981a185def69bb80d939d445))
+* **client:** support reading the base url from an env variable ([#186](https://github.com/Finch-API/finch-api-python/issues/186)) ([bb2dc38](https://github.com/Finch-API/finch-api-python/commit/bb2dc38e25da74fcbcdc2a34b4706ec304e9657f))
+
+
+### Bug Fixes
+
+* **client:** retry if SSLWantReadError occurs in the async client ([#181](https://github.com/Finch-API/finch-api-python/issues/181)) ([b2a7d2c](https://github.com/Finch-API/finch-api-python/commit/b2a7d2c0a617f358a12e7556fc53b740880892a4))
+* **client:** serialise pydantic v1 default fields correctly in params ([#180](https://github.com/Finch-API/finch-api-python/issues/180)) ([cb69944](https://github.com/Finch-API/finch-api-python/commit/cb6994491d3c2bf4112443018bc7d2d6d2b78fd4))
+* **models:** mark unknown fields as set in pydantic v1 ([#179](https://github.com/Finch-API/finch-api-python/issues/179)) ([c8261c9](https://github.com/Finch-API/finch-api-python/commit/c8261c9fb39b0a275dde59a78c67feb15d84c0f9))
+
+
+### Chores
+
+* **internal:** base client updates ([#178](https://github.com/Finch-API/finch-api-python/issues/178)) ([d06251d](https://github.com/Finch-API/finch-api-python/commit/d06251dfdb660fc7843b55c533f5b59906197160))
+* **internal:** fix devcontainer interpeter path ([#183](https://github.com/Finch-API/finch-api-python/issues/183)) ([ca48726](https://github.com/Finch-API/finch-api-python/commit/ca487263a47beab1f4dce8c052e734358d6893ef))
+* **internal:** fix typo in NotGiven docstring ([#182](https://github.com/Finch-API/finch-api-python/issues/182)) ([ca198e9](https://github.com/Finch-API/finch-api-python/commit/ca198e9a817cf76eadd912909a4581ef1ad2fd29))
+
+
+### Documentation
+
+* fix code comment typo ([#185](https://github.com/Finch-API/finch-api-python/issues/185)) ([09df068](https://github.com/Finch-API/finch-api-python/commit/09df068fad1545affb78bb5a982eb4ba4c9ed080))
+* reword package description ([#177](https://github.com/Finch-API/finch-api-python/issues/177)) ([ee5340a](https://github.com/Finch-API/finch-api-python/commit/ee5340ae2a36d26e2c9e12add86e8ba0a1603d2e))
+
## 0.6.0 (2023-11-08)
Full Changelog: [v0.5.0...v0.6.0](https://github.com/Finch-API/finch-api-python/compare/v0.5.0...v0.6.0)
diff --git a/README.md b/README.md
index a404df51..83861cd5 100644
--- a/README.md
+++ b/README.md
@@ -320,6 +320,7 @@ import httpx
from finch import Finch
client = Finch(
+ # Or use the `FINCH_BASE_URL` env var
base_url="http://my.test.server.example.com:8083",
http_client=httpx.Client(
proxies="http://my.test.proxy.example.com",
diff --git a/api.md b/api.md
index df158a2e..0fb9d472 100644
--- a/api.md
+++ b/api.md
@@ -1,3 +1,9 @@
+# Shared Types
+
+```python
+from finch.types import OperationSupport, OperationSupportMatrix
+```
+
# Finch
Methods:
@@ -96,11 +102,14 @@ Types:
```python
from finch.types.hris import (
BenefitContribution,
+ BenefitFeaturesAndOperations,
BenefitFrequency,
BenefitType,
+ BenefitsSupport,
BenfitContribution,
CompanyBenefit,
CreateCompanyBenefitsResponse,
+ SupportPerBenefitType,
SupportedBenefit,
UpdateCompanyBenefitResponse,
)
diff --git a/pyproject.toml b/pyproject.toml
index 9622e610..cf670e63 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "finch-api"
-version = "0.6.0"
-description = "Client library for the Finch API"
+version = "0.7.0"
+description = "The official Python library for the Finch API"
readme = "README.md"
license = "Apache-2.0"
authors = [
diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py
index e37759cd..3db8b6fa 100644
--- a/src/finch/_base_client.py
+++ b/src/finch/_base_client.py
@@ -1320,12 +1320,6 @@ async def _request(
if retries > 0:
return await self._retry_request(options, cast_to, retries, stream=stream, stream_cls=stream_cls)
raise APITimeoutError(request=request) from err
- except httpx.ReadTimeout as err:
- # We explicitly do not retry on ReadTimeout errors as this means
- # that the server processing the request has taken 60 seconds
- # (our default timeout). This likely indicates that something
- # is not working as expected on the server side.
- raise
except httpx.TimeoutException as err:
if retries > 0:
return await self._retry_request(options, cast_to, retries, stream=stream, stream_cls=stream_cls)
@@ -1727,9 +1721,14 @@ def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]:
return self.response.iter_raw(chunk_size)
@override
- def stream_to_file(self, file: str | os.PathLike[str]) -> None:
+ def stream_to_file(
+ self,
+ file: str | os.PathLike[str],
+ *,
+ chunk_size: int | None = None,
+ ) -> None:
with open(file, mode="wb") as f:
- for data in self.response.iter_bytes():
+ for data in self.response.iter_bytes(chunk_size):
f.write(data)
@override
@@ -1757,10 +1756,15 @@ async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[byt
return self.response.aiter_raw(chunk_size)
@override
- async def astream_to_file(self, file: str | os.PathLike[str]) -> None:
+ async def astream_to_file(
+ self,
+ file: str | os.PathLike[str],
+ *,
+ chunk_size: int | None = None,
+ ) -> None:
path = anyio.Path(file)
async with await path.open(mode="wb") as f:
- async for data in self.response.aiter_bytes():
+ async for data in self.response.aiter_bytes(chunk_size):
await f.write(data)
@override
diff --git a/src/finch/_client.py b/src/finch/_client.py
index 7da9096f..6b129988 100644
--- a/src/finch/_client.py
+++ b/src/finch/_client.py
@@ -112,6 +112,8 @@ def __init__(
webhook_secret = os.environ.get("FINCH_WEBHOOK_SECRET")
self.webhook_secret = webhook_secret
+ if base_url is None:
+ base_url = os.environ.get("FINCH_BASE_URL")
if base_url is None:
base_url = f"https://api.tryfinch.com"
@@ -414,6 +416,8 @@ def __init__(
webhook_secret = os.environ.get("FINCH_WEBHOOK_SECRET")
self.webhook_secret = webhook_secret
+ if base_url is None:
+ base_url = os.environ.get("FINCH_BASE_URL")
if base_url is None:
base_url = f"https://api.tryfinch.com"
diff --git a/src/finch/_models.py b/src/finch/_models.py
index 00d787ca..6d5aad59 100644
--- a/src/finch/_models.py
+++ b/src/finch/_models.py
@@ -121,6 +121,7 @@ def construct(
if PYDANTIC_V2:
_extra[key] = value
else:
+ _fields_set.add(key)
fields_values[key] = value
object.__setattr__(m, "__dict__", fields_values)
@@ -148,7 +149,7 @@ def construct(
if not PYDANTIC_V2:
# we define aliases for some of the new pydantic v2 methods so
# that we can just document these methods without having to specify
- # a specifc pydantic version as some users may not know which
+ # a specific pydantic version as some users may not know which
# pydantic version they are currently using
@override
diff --git a/src/finch/_streaming.py b/src/finch/_streaming.py
index b0600fc9..913159fd 100644
--- a/src/finch/_streaming.py
+++ b/src/finch/_streaming.py
@@ -45,10 +45,15 @@ def __stream__(self) -> Iterator[ResponseT]:
cast_to = self._cast_to
response = self.response
process_data = self._client._process_response_data
+ iterator = self._iter_events()
- for sse in self._iter_events():
+ for sse in iterator:
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ # Ensure the entire stream is consumed
+ for sse in iterator:
+ ...
+
class AsyncStream(Generic[ResponseT]):
"""Provides the core interface to iterate over an asynchronous stream response."""
@@ -83,10 +88,15 @@ async def __stream__(self) -> AsyncIterator[ResponseT]:
cast_to = self._cast_to
response = self.response
process_data = self._client._process_response_data
+ iterator = self._iter_events()
- async for sse in self._iter_events():
+ async for sse in iterator:
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ # Ensure the entire stream is consumed
+ async for sse in iterator:
+ ...
+
class ServerSentEvent:
def __init__(
diff --git a/src/finch/_types.py b/src/finch/_types.py
index 1fd2729a..e12f064d 100644
--- a/src/finch/_types.py
+++ b/src/finch/_types.py
@@ -123,7 +123,12 @@ def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]:
pass
@abstractmethod
- def stream_to_file(self, file: str | PathLike[str]) -> None:
+ def stream_to_file(
+ self,
+ file: str | PathLike[str],
+ *,
+ chunk_size: int | None = None,
+ ) -> None:
"""
Stream the output to the given file.
"""
@@ -172,7 +177,13 @@ async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[byt
"""
pass
- async def astream_to_file(self, file: str | PathLike[str]) -> None:
+ @abstractmethod
+ async def astream_to_file(
+ self,
+ file: str | PathLike[str],
+ *,
+ chunk_size: int | None = None,
+ ) -> None:
"""
Stream the output to the given file.
"""
@@ -268,8 +279,8 @@ class NotGiven:
```py
def get(timeout: Union[int, NotGiven, None] = NotGiven()) -> Response: ...
- get(timout=1) # 1s timeout
- get(timout=None) # No timeout
+ get(timeout=1) # 1s timeout
+ get(timeout=None) # No timeout
get() # Default timeout behavior, which may not be statically known at the method definition.
```
"""
diff --git a/src/finch/_utils/_transform.py b/src/finch/_utils/_transform.py
index dc497ea3..d953505f 100644
--- a/src/finch/_utils/_transform.py
+++ b/src/finch/_utils/_transform.py
@@ -168,7 +168,7 @@ def _transform_recursive(
return data
if isinstance(data, pydantic.BaseModel):
- return model_dump(data, exclude_unset=True, exclude_defaults=True)
+ return model_dump(data, exclude_unset=True)
return _transform_value(data, annotation)
diff --git a/src/finch/_version.py b/src/finch/_version.py
index 991bac2b..da6748b7 100644
--- a/src/finch/_version.py
+++ b/src/finch/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless.
__title__ = "finch"
-__version__ = "0.6.0" # x-release-please-version
+__version__ = "0.7.0" # x-release-please-version
diff --git a/src/finch/resources/request_forwarding.py b/src/finch/resources/request_forwarding.py
index 21d6e44b..2464bb35 100644
--- a/src/finch/resources/request_forwarding.py
+++ b/src/finch/resources/request_forwarding.py
@@ -41,12 +41,16 @@ def forward(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> RequestForwardingForwardResponse:
- """
- The Forward API allows you to make direct requests to an employment system.
+ """The Forward API allows you to make direct requests to an employment system.
+
+ If
+ Finch’s unified API doesn’t have a data model that cleanly fits your needs, then
+ Forward allows you to push or pull data models directly against an integration’s
+ API.
Args:
- method: The HTTP method for the forwarded request. Valid values include: `GET`, `POST`,
- `PUT`, `DELETE`, and `PATCH`.
+ method: The HTTP method for the forwarded request. Valid values include: `GET` , `POST`
+ , `PUT` , `DELETE` , and `PATCH`.
route: The URL route path for the forwarded request. This value must begin with a
forward-slash ( / ) and may only contain alphanumeric characters, hyphens, and
@@ -111,12 +115,16 @@ async def forward(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
) -> RequestForwardingForwardResponse:
- """
- The Forward API allows you to make direct requests to an employment system.
+ """The Forward API allows you to make direct requests to an employment system.
+
+ If
+ Finch’s unified API doesn’t have a data model that cleanly fits your needs, then
+ Forward allows you to push or pull data models directly against an integration’s
+ API.
Args:
- method: The HTTP method for the forwarded request. Valid values include: `GET`, `POST`,
- `PUT`, `DELETE`, and `PATCH`.
+ method: The HTTP method for the forwarded request. Valid values include: `GET` , `POST`
+ , `PUT` , `DELETE` , and `PATCH`.
route: The URL route path for the forwarded request. This value must begin with a
forward-slash ( / ) and may only contain alphanumeric characters, hyphens, and
diff --git a/src/finch/types/__init__.py b/src/finch/types/__init__.py
index 7f44cbc5..eee945a5 100644
--- a/src/finch/types/__init__.py
+++ b/src/finch/types/__init__.py
@@ -5,6 +5,8 @@
from .money import Money as Money
from .income import Income as Income
from .paging import Paging as Paging
+from .shared import OperationSupport as OperationSupport
+from .shared import OperationSupportMatrix as OperationSupportMatrix
from .location import Location as Location
from .provider import Provider as Provider
from .introspection import Introspection as Introspection
diff --git a/src/finch/types/hris/__init__.py b/src/finch/types/hris/__init__.py
index 95dcb512..da28d4a5 100644
--- a/src/finch/types/hris/__init__.py
+++ b/src/finch/types/hris/__init__.py
@@ -9,6 +9,7 @@
from .pay_statement import PayStatement as PayStatement
from .company_benefit import CompanyBenefit as CompanyBenefit
from .employment_data import EmploymentData as EmploymentData
+from .benefits_support import BenefitsSupport as BenefitsSupport
from .benefit_frequency import BenefitFrequency as BenefitFrequency
from .supported_benefit import SupportedBenefit as SupportedBenefit
from .benfit_contribution import BenfitContribution as BenfitContribution
@@ -21,9 +22,13 @@
from .pay_statement_response import PayStatementResponse as PayStatementResponse
from .individual_in_directory import IndividualInDirectory as IndividualInDirectory
from .employment_data_response import EmploymentDataResponse as EmploymentDataResponse
+from .support_per_benefit_type import SupportPerBenefitType as SupportPerBenefitType
from .pay_statement_response_body import (
PayStatementResponseBody as PayStatementResponseBody,
)
+from .benefit_features_and_operations import (
+ BenefitFeaturesAndOperations as BenefitFeaturesAndOperations,
+)
from .employment_retrieve_many_params import (
EmploymentRetrieveManyParams as EmploymentRetrieveManyParams,
)
diff --git a/src/finch/types/hris/benefit_features_and_operations.py b/src/finch/types/hris/benefit_features_and_operations.py
new file mode 100644
index 00000000..78fc27b3
--- /dev/null
+++ b/src/finch/types/hris/benefit_features_and_operations.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .benefit_frequency import BenefitFrequency
+from .support_per_benefit_type import SupportPerBenefitType
+
+__all__ = ["BenefitFeaturesAndOperations", "SupportedFeatures"]
+
+
+class SupportedFeatures(BaseModel):
+ annual_maximum: Optional[bool] = None
+ """Whether the provider supports an annual maximum for this benefit."""
+
+ catch_up: Optional[bool] = None
+ """Whether the provider supports catch up for this benefit.
+
+ This field will only be true for retirement benefits.
+ """
+
+ company_contribution: Optional[List[Literal["fixed", "percent"]]] = None
+ """Supported contribution types.
+
+ An empty array indicates contributions are not supported.
+ """
+
+ description: Optional[str] = None
+
+ employee_deduction: Optional[List[Literal["fixed", "percent"]]] = None
+ """Supported deduction types.
+
+ An empty array indicates deductions are not supported.
+ """
+
+ frequencies: Optional[List[Optional[BenefitFrequency]]] = None
+ """The list of frequencies supported by the provider for this benefit"""
+
+ hsa_contribution_limit: Optional[List[Literal["individual", "family"]]] = None
+ """Whether the provider supports HSA contribution limits.
+
+ Empty if this feature is not supported for the benefit. This array only has
+ values for HSA benefits.
+ """
+
+
+class BenefitFeaturesAndOperations(BaseModel):
+ supported_features: Optional[SupportedFeatures] = None
+
+ supported_operations: Optional[SupportPerBenefitType] = None
diff --git a/src/finch/types/hris/benefit_frequency.py b/src/finch/types/hris/benefit_frequency.py
index b4a7414f..515774fe 100644
--- a/src/finch/types/hris/benefit_frequency.py
+++ b/src/finch/types/hris/benefit_frequency.py
@@ -5,4 +5,4 @@
__all__ = ["BenefitFrequency"]
-BenefitFrequency = Optional[Literal["one_time", "every_paycheck"]]
+BenefitFrequency = Optional[Literal["one_time", "every_paycheck", "monthly"]]
diff --git a/src/finch/types/hris/benefits_support.py b/src/finch/types/hris/benefits_support.py
new file mode 100644
index 00000000..7cf88efa
--- /dev/null
+++ b/src/finch/types/hris/benefits_support.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from typing import TYPE_CHECKING, Optional
+
+from ..._models import BaseModel
+from .benefit_features_and_operations import BenefitFeaturesAndOperations
+
+__all__ = ["BenefitsSupport"]
+
+
+class BenefitsSupport(BaseModel):
+ commuter: Optional[BenefitFeaturesAndOperations] = None
+
+ custom_post_tax: Optional[BenefitFeaturesAndOperations] = None
+
+ custom_pre_tax: Optional[BenefitFeaturesAndOperations] = None
+
+ fsa_dependent_care: Optional[BenefitFeaturesAndOperations] = None
+
+ fsa_medical: Optional[BenefitFeaturesAndOperations] = None
+
+ hsa_post: Optional[BenefitFeaturesAndOperations] = None
+
+ hsa_pre: Optional[BenefitFeaturesAndOperations] = None
+
+ s125_dental: Optional[BenefitFeaturesAndOperations] = None
+
+ s125_medical: Optional[BenefitFeaturesAndOperations] = None
+
+ s125_vision: Optional[BenefitFeaturesAndOperations] = None
+
+ simple: Optional[BenefitFeaturesAndOperations] = None
+
+ simple_ira: Optional[BenefitFeaturesAndOperations] = None
+
+ if TYPE_CHECKING:
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> Optional[BenefitFeaturesAndOperations]:
+ ...
diff --git a/src/finch/types/hris/employment_data.py b/src/finch/types/hris/employment_data.py
index 1de9c0f8..a0bf1220 100644
--- a/src/finch/types/hris/employment_data.py
+++ b/src/finch/types/hris/employment_data.py
@@ -3,13 +3,17 @@
from typing import List, Optional
from typing_extensions import Literal
-from pydantic import Field as FieldInfo
-
from ..income import Income
from ..._models import BaseModel
from ..location import Location
-__all__ = ["EmploymentData", "Department", "Employment", "Manager"]
+__all__ = ["EmploymentData", "CustomField", "Department", "Employment", "Manager"]
+
+
+class CustomField(BaseModel):
+ name: Optional[str] = None
+
+ value: Optional[object] = None
class Department(BaseModel):
@@ -41,6 +45,12 @@ class EmploymentData(BaseModel):
class_code: Optional[str] = None
"""Worker's compensation classification code for this employee"""
+ custom_fields: Optional[List[CustomField]] = None
+ """Custom fields for the individual.
+
+ These are fields which are defined by the employer in the system.
+ """
+
department: Optional[Department] = None
"""The department object."""
@@ -96,7 +106,7 @@ class EmploymentData(BaseModel):
Please reach out to your Finch representative if you would like access.
"""
- work_id2: Optional[str] = FieldInfo(alias="work_id_2", default=None)
+ work_id_2: Optional[str] = None
"""Note: This property is only available if enabled for your account.
Please reach out to your Finch representative if you would like access.
diff --git a/src/finch/types/hris/individual.py b/src/finch/types/hris/individual.py
index 89e3e5bf..8f8ae112 100644
--- a/src/finch/types/hris/individual.py
+++ b/src/finch/types/hris/individual.py
@@ -29,6 +29,20 @@ class Individual(BaseModel):
emails: Optional[List[Email]] = None
+ ethnicity: Optional[
+ Literal[
+ "asian",
+ "white",
+ "black_or_african_american",
+ "native_hawaiian_or_pacific_islander",
+ "american_indian_or_alaska_native",
+ "hispanic_or_latino",
+ "two_or_more_races",
+ "decline_to_specify",
+ ]
+ ] = None
+ """The EEOC-defined ethnicity of the individual."""
+
first_name: Optional[str] = None
"""The legal first name of the individual."""
diff --git a/src/finch/types/hris/support_per_benefit_type.py b/src/finch/types/hris/support_per_benefit_type.py
new file mode 100644
index 00000000..2ce0dcee
--- /dev/null
+++ b/src/finch/types/hris/support_per_benefit_type.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from typing import Optional
+
+from ..shared import OperationSupportMatrix
+from ..._models import BaseModel
+
+__all__ = ["SupportPerBenefitType"]
+
+
+class SupportPerBenefitType(BaseModel):
+ company_benefits: Optional[OperationSupportMatrix] = None
+
+ individual_benefits: Optional[OperationSupportMatrix] = None
diff --git a/src/finch/types/introspection.py b/src/finch/types/introspection.py
index a8f26da6..b9d15202 100644
--- a/src/finch/types/introspection.py
+++ b/src/finch/types/introspection.py
@@ -8,6 +8,9 @@
class Introspection(BaseModel):
+ account_id: str
+ """The Finch uuid of the account used to connect this company."""
+
client_id: str
"""The client id of the application associated with the `access_token`."""
diff --git a/src/finch/types/provider.py b/src/finch/types/provider.py
index 880077a0..0b732b0f 100644
--- a/src/finch/types/provider.py
+++ b/src/finch/types/provider.py
@@ -1,16 +1,397 @@
# File generated from our OpenAPI spec by Stainless.
from typing import List, Optional
+from typing_extensions import Literal
+from .hris import BenefitsSupport
from .._models import BaseModel
-__all__ = ["Provider"]
+__all__ = [
+ "Provider",
+ "AuthenticationMethod",
+ "AuthenticationMethodSupportedFields",
+ "AuthenticationMethodSupportedFieldsCompany",
+ "AuthenticationMethodSupportedFieldsCompanyAccounts",
+ "AuthenticationMethodSupportedFieldsCompanyDepartments",
+ "AuthenticationMethodSupportedFieldsCompanyDepartmentsParent",
+ "AuthenticationMethodSupportedFieldsCompanyEntity",
+ "AuthenticationMethodSupportedFieldsCompanyLocations",
+ "AuthenticationMethodSupportedFieldsDirectory",
+ "AuthenticationMethodSupportedFieldsDirectoryIndividuals",
+ "AuthenticationMethodSupportedFieldsDirectoryIndividualsManager",
+ "AuthenticationMethodSupportedFieldsDirectoryPaging",
+ "AuthenticationMethodSupportedFieldsEmployment",
+ "AuthenticationMethodSupportedFieldsEmploymentDepartment",
+ "AuthenticationMethodSupportedFieldsEmploymentEmployment",
+ "AuthenticationMethodSupportedFieldsEmploymentIncome",
+ "AuthenticationMethodSupportedFieldsEmploymentLocation",
+ "AuthenticationMethodSupportedFieldsIndividual",
+ "AuthenticationMethodSupportedFieldsIndividualEmails",
+ "AuthenticationMethodSupportedFieldsIndividualPhoneNumbers",
+ "AuthenticationMethodSupportedFieldsIndividualResidence",
+ "AuthenticationMethodSupportedFieldsPayStatement",
+ "AuthenticationMethodSupportedFieldsPayStatementPaging",
+ "AuthenticationMethodSupportedFieldsPayStatementPayStatements",
+ "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEarnings",
+ "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions",
+ "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions",
+ "AuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes",
+ "AuthenticationMethodSupportedFieldsPayment",
+ "AuthenticationMethodSupportedFieldsPaymentPayPeriod",
+]
+
+
+class AuthenticationMethodSupportedFieldsCompanyAccounts(BaseModel):
+ account_name: Optional[bool] = None
+
+ account_number: Optional[bool] = None
+
+ account_type: Optional[bool] = None
+
+ institution_name: Optional[bool] = None
+
+ routing_number: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsCompanyDepartmentsParent(BaseModel):
+ name: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsCompanyDepartments(BaseModel):
+ name: Optional[bool] = None
+
+ parent: Optional[AuthenticationMethodSupportedFieldsCompanyDepartmentsParent] = None
+
+
+class AuthenticationMethodSupportedFieldsCompanyEntity(BaseModel):
+ subtype: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsCompanyLocations(BaseModel):
+ city: Optional[bool] = None
+
+ country: Optional[bool] = None
+
+ line1: Optional[bool] = None
+
+ line2: Optional[bool] = None
+
+ postal_code: Optional[bool] = None
+
+ state: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsCompany(BaseModel):
+ id: Optional[bool] = None
+
+ accounts: Optional[AuthenticationMethodSupportedFieldsCompanyAccounts] = None
+
+ departments: Optional[AuthenticationMethodSupportedFieldsCompanyDepartments] = None
+
+ ein: Optional[bool] = None
+
+ entity: Optional[AuthenticationMethodSupportedFieldsCompanyEntity] = None
+
+ legal_name: Optional[bool] = None
+
+ locations: Optional[AuthenticationMethodSupportedFieldsCompanyLocations] = None
+
+ primary_email: Optional[bool] = None
+
+ primary_phone_number: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsDirectoryIndividualsManager(BaseModel):
+ id: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsDirectoryIndividuals(BaseModel):
+ id: Optional[bool] = None
+
+ department: Optional[bool] = None
+
+ first_name: Optional[bool] = None
+
+ is_active: Optional[bool] = None
+
+ last_name: Optional[bool] = None
+
+ manager: Optional[AuthenticationMethodSupportedFieldsDirectoryIndividualsManager] = None
+
+ middle_name: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsDirectoryPaging(BaseModel):
+ count: Optional[bool] = None
+
+ offset: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsDirectory(BaseModel):
+ individuals: Optional[AuthenticationMethodSupportedFieldsDirectoryIndividuals] = None
+
+ paging: Optional[AuthenticationMethodSupportedFieldsDirectoryPaging] = None
+
+
+class AuthenticationMethodSupportedFieldsEmploymentDepartment(BaseModel):
+ name: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsEmploymentEmployment(BaseModel):
+ subtype: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsEmploymentIncome(BaseModel):
+ amount: Optional[bool] = None
+
+ currency: Optional[bool] = None
+
+ unit: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsEmploymentLocation(BaseModel):
+ city: Optional[bool] = None
+
+ country: Optional[bool] = None
+
+ line1: Optional[bool] = None
+
+ line2: Optional[bool] = None
+
+ postal_code: Optional[bool] = None
+
+ state: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsEmployment(BaseModel):
+ id: Optional[bool] = None
+
+ class_code: Optional[bool] = None
+
+ custom_fields: Optional[bool] = None
+
+ department: Optional[AuthenticationMethodSupportedFieldsEmploymentDepartment] = None
+
+ employment: Optional[AuthenticationMethodSupportedFieldsEmploymentEmployment] = None
+
+ end_date: Optional[bool] = None
+
+ first_name: Optional[bool] = None
+
+ income_history: Optional[bool] = None
+
+ income: Optional[AuthenticationMethodSupportedFieldsEmploymentIncome] = None
+
+ is_active: Optional[bool] = None
+
+ last_name: Optional[bool] = None
+
+ location: Optional[AuthenticationMethodSupportedFieldsEmploymentLocation] = None
+
+ manager: Optional[object] = None
+
+ middle_name: Optional[bool] = None
+
+ start_date: Optional[bool] = None
+
+ title: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsIndividualEmails(BaseModel):
+ data: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsIndividualPhoneNumbers(BaseModel):
+ data: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsIndividualResidence(BaseModel):
+ city: Optional[bool] = None
+
+ country: Optional[bool] = None
+
+ line1: Optional[bool] = None
+
+ line2: Optional[bool] = None
+
+ postal_code: Optional[bool] = None
+
+ state: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsIndividual(BaseModel):
+ id: Optional[bool] = None
+
+ dob: Optional[bool] = None
+
+ emails: Optional[AuthenticationMethodSupportedFieldsIndividualEmails] = None
+
+ ethnicity: Optional[bool] = None
+
+ first_name: Optional[bool] = None
+
+ gender: Optional[bool] = None
+
+ last_name: Optional[bool] = None
+
+ middle_name: Optional[bool] = None
+
+ phone_numbers: Optional[AuthenticationMethodSupportedFieldsIndividualPhoneNumbers] = None
+
+ preferred_name: Optional[bool] = None
+
+ residence: Optional[AuthenticationMethodSupportedFieldsIndividualResidence] = None
+
+ ssn: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsPayStatementPaging(BaseModel):
+ count: bool
+
+ offset: bool
+
+
+class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEarnings(BaseModel):
+ amount: Optional[bool] = None
+
+ currency: Optional[bool] = None
+
+ name: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions(BaseModel):
+ amount: Optional[bool] = None
+
+ currency: Optional[bool] = None
+
+ name: Optional[bool] = None
+
+ pre_tax: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions(BaseModel):
+ amount: Optional[bool] = None
+
+ currency: Optional[bool] = None
+
+ name: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes(BaseModel):
+ amount: Optional[bool] = None
+
+ currency: Optional[bool] = None
+
+ employer: Optional[bool] = None
+
+ name: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsPayStatementPayStatements(BaseModel):
+ earnings: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatementsEarnings] = None
+
+ employee_deductions: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions] = None
+
+ employer_deductions: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions] = None
+
+ gross_pay: Optional[bool] = None
+
+ individual_id: Optional[bool] = None
+
+ net_pay: Optional[bool] = None
+
+ payment_method: Optional[bool] = None
+
+ taxes: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes] = None
+
+ total_hours: Optional[bool] = None
+
+ type: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsPayStatement(BaseModel):
+ paging: Optional[AuthenticationMethodSupportedFieldsPayStatementPaging] = None
+
+ pay_statements: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatements] = None
+
+
+class AuthenticationMethodSupportedFieldsPaymentPayPeriod(BaseModel):
+ end_date: Optional[bool] = None
+
+ start_date: Optional[bool] = None
+
+
+class AuthenticationMethodSupportedFieldsPayment(BaseModel):
+ id: Optional[bool] = None
+
+ company_debit: Optional[bool] = None
+
+ debit_date: Optional[bool] = None
+
+ employee_taxes: Optional[bool] = None
+
+ employer_taxes: Optional[bool] = None
+
+ gross_pay: Optional[bool] = None
+
+ individual_ids: Optional[bool] = None
+
+ net_pay: Optional[bool] = None
+
+ pay_date: Optional[bool] = None
+
+ pay_period: Optional[AuthenticationMethodSupportedFieldsPaymentPayPeriod] = None
+
+
+class AuthenticationMethodSupportedFields(BaseModel):
+ company: Optional[AuthenticationMethodSupportedFieldsCompany] = None
+
+ directory: Optional[AuthenticationMethodSupportedFieldsDirectory] = None
+
+ employment: Optional[AuthenticationMethodSupportedFieldsEmployment] = None
+
+ individual: Optional[AuthenticationMethodSupportedFieldsIndividual] = None
+
+ pay_statement: Optional[AuthenticationMethodSupportedFieldsPayStatement] = None
+
+ payment: Optional[AuthenticationMethodSupportedFieldsPayment] = None
+
+
+class AuthenticationMethod(BaseModel):
+ benefits_support: Optional[BenefitsSupport] = None
+ """Each benefit type and their supported features.
+
+ If the benefit type is not supported, the property will be null
+ """
+
+ supported_fields: Optional[AuthenticationMethodSupportedFields] = None
+ """The supported data fields returned by our HR and payroll endpoints"""
+
+ type: Optional[Literal["assisted", "credential", "api_token", "api_credential", "oauth"]] = None
+ """The type of authentication method."""
class Provider(BaseModel):
id: Optional[str] = None
"""The id of the payroll provider used in Connect."""
+ authentication_methods: Optional[List[AuthenticationMethod]] = None
+ """The list of authentication methods supported by the provider."""
+
display_name: Optional[str] = None
"""The display name of the payroll provider."""
@@ -22,8 +403,9 @@ class Provider(BaseModel):
manual: Optional[bool] = None
"""
- Whether the Finch integration with this provider uses the Assisted Connect Flow
- by default.
+ [DEPRECATED] Whether the Finch integration with this provider uses the Assisted
+ Connect Flow by default. This field is now deprecated. Please check for a `type`
+ of `assisted` in the `authentication_methods` field instead.
"""
mfa_required: Optional[bool] = None
diff --git a/src/finch/types/request_forwarding_forward_params.py b/src/finch/types/request_forwarding_forward_params.py
index 50b7dfcc..19fe0062 100644
--- a/src/finch/types/request_forwarding_forward_params.py
+++ b/src/finch/types/request_forwarding_forward_params.py
@@ -12,7 +12,7 @@ class RequestForwardingForwardParams(TypedDict, total=False):
method: Required[str]
"""The HTTP method for the forwarded request.
- Valid values include: `GET`, `POST`, `PUT`, `DELETE`, and `PATCH`.
+ Valid values include: `GET` , `POST` , `PUT` , `DELETE` , and `PATCH`.
"""
route: Required[str]
diff --git a/src/finch/types/request_forwarding_forward_response.py b/src/finch/types/request_forwarding_forward_response.py
index 508c8e89..267a4cf9 100644
--- a/src/finch/types/request_forwarding_forward_response.py
+++ b/src/finch/types/request_forwarding_forward_response.py
@@ -26,7 +26,7 @@ class Request(BaseModel):
method: str
"""The HTTP method that was specified for the forwarded request.
- Valid values include: `GET`, `POST`, `PUT` , `DELETE`, and `PATCH`.
+ Valid values include: `GET` , `POST` , `PUT` , `DELETE` , and `PATCH`.
"""
params: Optional[object]
diff --git a/src/finch/types/shared/__init__.py b/src/finch/types/shared/__init__.py
new file mode 100644
index 00000000..3be971aa
--- /dev/null
+++ b/src/finch/types/shared/__init__.py
@@ -0,0 +1,4 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from .operation_support import OperationSupport as OperationSupport
+from .operation_support_matrix import OperationSupportMatrix as OperationSupportMatrix
diff --git a/src/finch/types/shared/operation_support.py b/src/finch/types/shared/operation_support.py
new file mode 100644
index 00000000..9ae5ebf6
--- /dev/null
+++ b/src/finch/types/shared/operation_support.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from typing_extensions import Literal
+
+__all__ = ["OperationSupport"]
+
+OperationSupport = Literal["supported", "not_supported_by_finch", "not_supported_by_provider", "client_access_only"]
diff --git a/src/finch/types/shared/operation_support_matrix.py b/src/finch/types/shared/operation_support_matrix.py
new file mode 100644
index 00000000..c8b8ec58
--- /dev/null
+++ b/src/finch/types/shared/operation_support_matrix.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from typing import Optional
+
+from ..._models import BaseModel
+from .operation_support import OperationSupport
+
+__all__ = ["OperationSupportMatrix"]
+
+
+class OperationSupportMatrix(BaseModel):
+ create: Optional[OperationSupport] = None
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
+
+ delete: Optional[OperationSupport] = None
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
+
+ read: Optional[OperationSupport] = None
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
+
+ update: Optional[OperationSupport] = None
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
diff --git a/src/finch/types/shared_params/__init__.py b/src/finch/types/shared_params/__init__.py
new file mode 100644
index 00000000..3be971aa
--- /dev/null
+++ b/src/finch/types/shared_params/__init__.py
@@ -0,0 +1,4 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from .operation_support import OperationSupport as OperationSupport
+from .operation_support_matrix import OperationSupportMatrix as OperationSupportMatrix
diff --git a/src/finch/types/shared_params/operation_support.py b/src/finch/types/shared_params/operation_support.py
new file mode 100644
index 00000000..290a5214
--- /dev/null
+++ b/src/finch/types/shared_params/operation_support.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+__all__ = ["OperationSupport"]
+
+OperationSupport = Literal["supported", "not_supported_by_finch", "not_supported_by_provider", "client_access_only"]
diff --git a/src/finch/types/shared_params/operation_support_matrix.py b/src/finch/types/shared_params/operation_support_matrix.py
new file mode 100644
index 00000000..4fa6df6b
--- /dev/null
+++ b/src/finch/types/shared_params/operation_support_matrix.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Stainless.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from ..shared import OperationSupport
+from .operation_support import OperationSupport
+
+__all__ = ["OperationSupportMatrix"]
+
+
+class OperationSupportMatrix(TypedDict, total=False):
+ create: OperationSupport
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
+
+ delete: OperationSupport
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
+
+ read: OperationSupport
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
+
+ update: OperationSupport
+ """
+ - `supported`: This operation is supported by both the provider and Finch
+ - `not_supported_by_finch`: This operation is not supported by Finch but
+ supported by the provider
+ - `not_supported_by_provider`: This operation is not supported by the provider,
+ so Finch cannot support
+ - `client_access_only`: This behavior is supported by the provider, but only
+ available to the client and not to Finch
+ """
diff --git a/tests/test_client.py b/tests/test_client.py
index d2644f51..d6013ba5 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -26,6 +26,8 @@
make_request_options,
)
+from .utils import update_env
+
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
access_token = "My Access Token"
@@ -41,12 +43,12 @@ class TestFinch:
@pytest.mark.respx(base_url=base_url)
def test_raw_response(self, respx_mock: MockRouter) -> None:
- respx_mock.post("/foo").mock(return_value=httpx.Response(200, json='{"foo": "bar"}'))
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
response = self.client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
- assert response.json() == '{"foo": "bar"}'
+ assert response.json() == {"foo": "bar"}
@pytest.mark.respx(base_url=base_url)
def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
@@ -57,7 +59,7 @@ def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
response = self.client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
- assert response.json() == '{"foo": "bar"}'
+ assert response.json() == {"foo": "bar"}
def test_copy(self) -> None:
copied = self.client.copy()
@@ -418,6 +420,11 @@ class Model2(BaseModel):
assert isinstance(response, Model1)
assert response.foo == 1
+ def test_base_url_env(self) -> None:
+ with update_env(FINCH_BASE_URL="http://localhost:5000/from/env"):
+ client = Finch(access_token=access_token, _strict_response_validation=True)
+ assert client.base_url == "http://localhost:5000/from/env/"
+
@pytest.mark.parametrize(
"client",
[
@@ -683,12 +690,12 @@ class TestAsyncFinch:
@pytest.mark.respx(base_url=base_url)
@pytest.mark.asyncio
async def test_raw_response(self, respx_mock: MockRouter) -> None:
- respx_mock.post("/foo").mock(return_value=httpx.Response(200, json='{"foo": "bar"}'))
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
response = await self.client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
- assert response.json() == '{"foo": "bar"}'
+ assert response.json() == {"foo": "bar"}
@pytest.mark.respx(base_url=base_url)
@pytest.mark.asyncio
@@ -700,7 +707,7 @@ async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
response = await self.client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
- assert response.json() == '{"foo": "bar"}'
+ assert response.json() == {"foo": "bar"}
def test_copy(self) -> None:
copied = self.client.copy()
@@ -1061,6 +1068,11 @@ class Model2(BaseModel):
assert isinstance(response, Model1)
assert response.foo == 1
+ def test_base_url_env(self) -> None:
+ with update_env(FINCH_BASE_URL="http://localhost:5000/from/env"):
+ client = AsyncFinch(access_token=access_token, _strict_response_validation=True)
+ assert client.base_url == "http://localhost:5000/from/env/"
+
@pytest.mark.parametrize(
"client",
[
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 83a905bb..7b5f1f7c 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -7,6 +7,7 @@
import pytest
from finch._utils import PropertyInfo, transform, parse_datetime
+from finch._compat import PYDANTIC_V2
from finch._models import BaseModel
@@ -210,14 +211,20 @@ def test_pydantic_unknown_field() -> None:
def test_pydantic_mismatched_types() -> None:
model = MyModel.construct(foo=True)
- with pytest.warns(UserWarning):
+ if PYDANTIC_V2:
+ with pytest.warns(UserWarning):
+ params = transform(model, Any)
+ else:
params = transform(model, Any)
assert params == {"foo": True}
def test_pydantic_mismatched_object_type() -> None:
model = MyModel.construct(foo=MyModel.construct(hello="world"))
- with pytest.warns(UserWarning):
+ if PYDANTIC_V2:
+ with pytest.warns(UserWarning):
+ params = transform(model, Any)
+ else:
params = transform(model, Any)
assert params == {"foo": {"hello": "world"}}
@@ -230,3 +237,29 @@ def test_pydantic_nested_objects() -> None:
model = ModelNestedObjects.construct(nested={"foo": "stainless"})
assert isinstance(model.nested, MyModel)
assert transform(model, Any) == {"nested": {"foo": "stainless"}}
+
+
+class ModelWithDefaultField(BaseModel):
+ foo: str
+ with_none_default: Union[str, None] = None
+ with_str_default: str = "foo"
+
+
+def test_pydantic_default_field() -> None:
+ # should be excluded when defaults are used
+ model = ModelWithDefaultField.construct()
+ assert model.with_none_default is None
+ assert model.with_str_default == "foo"
+ assert transform(model, Any) == {}
+
+ # should be included when the default value is explicitly given
+ model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo")
+ assert model.with_none_default is None
+ assert model.with_str_default == "foo"
+ assert transform(model, Any) == {"with_none_default": None, "with_str_default": "foo"}
+
+ # should be included when a non-default value is explicitly given
+ model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz")
+ assert model.with_none_default == "bar"
+ assert model.with_str_default == "baz"
+ assert transform(model, Any) == {"with_none_default": "bar", "with_str_default": "baz"}
diff --git a/tests/utils.py b/tests/utils.py
index be21e620..bc62203d 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,7 +1,9 @@
from __future__ import annotations
+import os
import traceback
-from typing import Any, TypeVar, cast
+import contextlib
+from typing import Any, TypeVar, Iterator, cast
from datetime import date, datetime
from typing_extensions import Literal, get_args, get_origin, assert_type
@@ -103,3 +105,16 @@ def _assert_list_type(type_: type[object], value: object) -> None:
inner_type = get_args(type_)[0]
for entry in value:
assert_type(inner_type, entry) # type: ignore
+
+
+@contextlib.contextmanager
+def update_env(**new_env: str) -> Iterator[None]:
+ old = os.environ.copy()
+
+ try:
+ os.environ.update(new_env)
+
+ yield None
+ finally:
+ os.environ.clear()
+ os.environ.update(old)