diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2b98114..187c937b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,12 +7,17 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/finch-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -30,10 +35,10 @@ jobs: - name: Run lints run: ./scripts/lint - upload: - if: github.repository == 'stainless-sdks/finch-python' + build: + if: github.repository == 'stainless-sdks/finch-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 - name: upload + name: build permissions: contents: read id-token: write @@ -41,6 +46,20 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run build + run: rye build + - name: Get GitHub OIDC Token id: github-oidc uses: actions/github-script@v6 @@ -58,6 +77,7 @@ jobs: timeout-minutes: 10 name: test runs-on: ${{ github.repository == 'stainless-sdks/finch-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b8af36c3..35c30adc 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.29.0" + ".": "1.30.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index eecdfce3..f1c7d32b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-944a0f9d35f3b8ec2ba62fa12e551cf89f0b845f8ed1e3c7f67a9fb80b32d96f.yml -openapi_spec_hash: 37c849e7b5dd941c011385b49467e077 -config_hash: 53778a0b839c4f6ad34fbba051f5e8a6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-f7e741bc6e0175fd96a9db5348092b90a77b0985154c0814bb681ad5dccdf19a.yml +openapi_spec_hash: b348a9ef407a8e91dd770fcb219d4ac5 +config_hash: 5146b12344dae76238940989dac1e8a0 diff --git a/CHANGELOG.md b/CHANGELOG.md index b45e750e..a8c2c54b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## 1.30.0 (2025-07-02) + +Full Changelog: [v1.29.0...v1.30.0](https://github.com/Finch-API/finch-api-python/compare/v1.29.0...v1.30.0) + +### Features + +* **api:** api update ([97ef62d](https://github.com/Finch-API/finch-api-python/commit/97ef62d11c21650cb6f34a69f3309cdbf034df86)) +* **api:** api update ([68ed0de](https://github.com/Finch-API/finch-api-python/commit/68ed0de62a49c340a1ee7dc71d941d71b6d780c4)) +* **api:** api update ([a31325e](https://github.com/Finch-API/finch-api-python/commit/a31325e6eeb04cfa725ceee5637b869dd02be1a8)) +* **api:** api update ([b95ac19](https://github.com/Finch-API/finch-api-python/commit/b95ac1971a10372d70fc7ccdb3cf63f4411e9d2e)) +* **client:** add follow_redirects request option ([f9bdda7](https://github.com/Finch-API/finch-api-python/commit/f9bdda77ef1fcb1460594a16f1b8726bbaad2b52)) +* **client:** add support for aiohttp ([9529a96](https://github.com/Finch-API/finch-api-python/commit/9529a960260e47e147e10a9fec012f279089628b)) + + +### Bug Fixes + +* **ci:** correct conditional ([5bd88a7](https://github.com/Finch-API/finch-api-python/commit/5bd88a7228b013e66794a9731e3d3669d1a43321)) +* **ci:** release-doctor — report correct token name ([d424f44](https://github.com/Finch-API/finch-api-python/commit/d424f44c05c5c05bf31b43e1da74acea40324553)) +* **client:** correctly parse binary response | stream ([260c761](https://github.com/Finch-API/finch-api-python/commit/260c761024d36f0bb4efc3cf5a5e0e3c22b5f83e)) +* **client:** fix PayStatementResponse body ([edcea6c](https://github.com/Finch-API/finch-api-python/commit/edcea6c7ac55661d3e46aec1c42576988b510dfc)) +* **client:** manual fix ([d1dc8a4](https://github.com/Finch-API/finch-api-python/commit/d1dc8a4aa3df7a0199ab04958a766b56b3be0dc5)) + + +### Chores + +* **ci:** change upload type ([2d63eed](https://github.com/Finch-API/finch-api-python/commit/2d63eeda7540306cf29e14ac475c6f5cc740d768)) +* **ci:** enable for pull requests ([3a05f2a](https://github.com/Finch-API/finch-api-python/commit/3a05f2a17a2f0067486b6c8427b2d50c18a79a80)) +* **ci:** only run for pushes and fork pull requests ([398de61](https://github.com/Finch-API/finch-api-python/commit/398de612e40086b2814a79c2fcdd0d73bba289fc)) +* **docs:** grammar improvements ([2d4a30e](https://github.com/Finch-API/finch-api-python/commit/2d4a30edc2f84fc7effc9b7719aae8ed51646338)) +* **docs:** remove reference to rye shell ([2b6929f](https://github.com/Finch-API/finch-api-python/commit/2b6929f8eca16bff688b19907f38e05ad2d2b092)) +* fix access tokens test ([8411cf6](https://github.com/Finch-API/finch-api-python/commit/8411cf6e5185b4f2dd70ac030d30d7c9808e55b5)) +* **internal:** update conftest.py ([ed2f82d](https://github.com/Finch-API/finch-api-python/commit/ed2f82d6ecc8662e4717c9f7b75d912226c5df25)) +* **readme:** update badges ([e27f6d8](https://github.com/Finch-API/finch-api-python/commit/e27f6d80e9b9bdbb23fa3a6c9cc84ecbbf790638)) +* **tests:** add tests for httpx client instantiation & proxies ([e51c8f7](https://github.com/Finch-API/finch-api-python/commit/e51c8f78bb45e0c3dbce5dc67401390078a2fd33)) +* **tests:** run tests in parallel ([25017c4](https://github.com/Finch-API/finch-api-python/commit/25017c43233e726dd80d1e7ca41eb02ec9456871)) +* **tests:** skip endpoints with basic auth ([9548dd5](https://github.com/Finch-API/finch-api-python/commit/9548dd59525baf33a8d1977b132e93b2fe1d7f96)) +* **tests:** skip some failing tests on the latest python versions ([668de92](https://github.com/Finch-API/finch-api-python/commit/668de928c9cd69af00755b2a201f3e26a24f9630)) + + +### Documentation + +* **client:** fix httpx.Timeout documentation reference ([c17f081](https://github.com/Finch-API/finch-api-python/commit/c17f0817149ae17fe866bc79f8613cbccc0ded72)) + ## 1.29.0 (2025-05-16) Full Changelog: [v1.28.0...v1.29.0](https://github.com/Finch-API/finch-api-python/compare/v1.28.0...v1.29.0) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9879f76..21f44701 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix diff --git a/README.md b/README.md index dd9945f6..f9395885 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Finch Python API library -[![PyPI version](https://img.shields.io/pypi/v/finch-api.svg)](https://pypi.org/project/finch-api/) +[![PyPI version]()](https://pypi.org/project/finch-api/) The Finch Python library provides convenient access to the Finch REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, @@ -57,6 +57,37 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from PyPI +pip install finch-api[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import asyncio +from finch import DefaultAioHttpClient +from finch import AsyncFinch + + +async def main() -> None: + async with AsyncFinch( + access_token="My Access Token", + http_client=DefaultAioHttpClient(), + ) as client: + page = await client.hris.directory.list() + print(page.individuals) + + +asyncio.run(main()) +``` + ## Using types Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: @@ -219,7 +250,7 @@ client.with_options(max_retries=5).hris.directory.list() ### Timeouts By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from finch import Finch diff --git a/SECURITY.md b/SECURITY.md index b6499508..1e432e1e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Finch please follow the respective company's security reporting guidelines. +or products provided by Finch, please follow the respective company's security reporting guidelines. ### Finch Terms and Policies -Please contact founders@tryfinch.com for any questions or concerns regarding security of our services. +Please contact founders@tryfinch.com for any questions or concerns regarding the security of our services. --- diff --git a/api.md b/api.md index 1e750c7c..f6de6000 100644 --- a/api.md +++ b/api.md @@ -128,7 +128,12 @@ Methods: Types: ```python -from finch.types.hris import PayStatement, PayStatementResponse, PayStatementResponseBody +from finch.types.hris import ( + PayStatement, + PayStatementDataSyncInProgress, + PayStatementResponse, + PayStatementResponseBody, +) ``` Methods: @@ -170,7 +175,6 @@ from finch.types.hris import ( SupportPerBenefitType, SupportedBenefit, UpdateCompanyBenefitResponse, - BenefitListSupportedBenefitsResponse, BenfitContribution, ) ``` @@ -181,7 +185,7 @@ Methods: - client.hris.benefits.retrieve(benefit_id) -> CompanyBenefit - client.hris.benefits.update(benefit_id, \*\*params) -> UpdateCompanyBenefitResponse - client.hris.benefits.list() -> SyncSinglePage[CompanyBenefit] -- client.hris.benefits.list_supported_benefits() -> SyncSinglePage[BenefitListSupportedBenefitsResponse] +- client.hris.benefits.list_supported_benefits() -> SyncSinglePage[SupportedBenefit] ### Individuals diff --git a/bin/check-release-environment b/bin/check-release-environment index 49f294c8..b845b0f4 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The FINCH_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi lenErrors=${#errors[@]} diff --git a/pyproject.toml b/pyproject.toml index 4b64a1e0..6b804c9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "finch-api" -version = "1.29.0" +version = "1.30.0" description = "The official Python library for the Finch API" dynamic = ["readme"] license = "Apache-2.0" @@ -37,6 +37,8 @@ classifiers = [ Homepage = "https://github.com/Finch-API/finch-api-python" Repository = "https://github.com/Finch-API/finch-api-python" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] [tool.rye] managed = true @@ -54,6 +56,7 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "rich>=13.7.1", "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -125,7 +128,7 @@ replacement = '[\1](https://github.com/Finch-API/finch-api-python/tree/main/\g<2 [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" diff --git a/requirements-dev.lock b/requirements-dev.lock index 6ef50771..56aeed96 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,6 +10,13 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via finch-api + # via httpx-aiohttp +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 @@ -17,6 +24,10 @@ anyio==4.4.0 # via httpx argcomplete==3.1.2 # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -30,18 +41,27 @@ distro==1.8.0 exceptiongroup==1.2.2 # via anyio # via pytest +execnet==2.1.1 + # via pytest-xdist filelock==3.12.4 # via virtualenv +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 # via finch-api + # via httpx-aiohttp # via respx +httpx-aiohttp==0.1.6 + # via finch-api idna==3.4 # via anyio # via httpx + # via yarl importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest @@ -49,6 +69,9 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py +multidict==6.4.4 + # via aiohttp + # via yarl mypy==1.14.1 mypy-extensions==1.0.0 # via mypy @@ -63,6 +86,9 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via finch-api pydantic-core==2.27.1 @@ -72,7 +98,9 @@ pygments==2.18.0 pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio + # via pytest-xdist pytest-asyncio==0.24.0 +pytest-xdist==3.7.0 python-dateutil==2.8.2 # via time-machine pytz==2023.3.post1 @@ -94,11 +122,14 @@ tomli==2.0.2 typing-extensions==4.12.2 # via anyio # via finch-api + # via multidict # via mypy # via pydantic # via pydantic-core # via pyright virtualenv==20.24.5 # via nox +yarl==1.20.0 + # via aiohttp zipp==3.17.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index d9942c73..9261dbc2 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,11 +10,22 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via finch-api + # via httpx-aiohttp +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 # via finch-api # via httpx +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -22,15 +33,28 @@ distro==1.8.0 # via finch-api exceptiongroup==1.2.2 # via anyio +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 # via finch-api + # via httpx-aiohttp +httpx-aiohttp==0.1.6 + # via finch-api idna==3.4 # via anyio # via httpx + # via yarl +multidict==6.4.4 + # via aiohttp + # via yarl +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via finch-api pydantic-core==2.27.1 @@ -41,5 +65,8 @@ sniffio==1.3.0 typing-extensions==4.12.2 # via anyio # via finch-api + # via multidict # via pydantic # via pydantic-core +yarl==1.20.0 + # via aiohttp diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index d89a9ed0..bb0a7a76 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash set -exuo pipefail -RESPONSE=$(curl -X POST "$URL" \ +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ -H "Authorization: Bearer $AUTH" \ -H "Content-Type: application/json") @@ -12,13 +14,13 @@ if [[ "$SIGNED_URL" == "null" ]]; then exit 1 fi -UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ - -H "Content-Type: application/gzip" \ - --data-binary @- "$SIGNED_URL" 2>&1) +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: binary/octet-stream" \ + --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/finch-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/finch-python/$SHA/$FILENAME'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 diff --git a/src/finch/__init__.py b/src/finch/__init__.py index 181d238f..41c80a92 100644 --- a/src/finch/__init__.py +++ b/src/finch/__init__.py @@ -26,7 +26,7 @@ UnprocessableEntityError, APIResponseValidationError, ) -from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging __all__ = [ @@ -68,6 +68,7 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] if not _t.TYPE_CHECKING: diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py index c46eaebc..1caaa8d7 100644 --- a/src/finch/_base_client.py +++ b/src/finch/_base_client.py @@ -961,6 +961,9 @@ def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1083,7 +1086,14 @@ def _process_response( origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1294,6 +1304,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1302,8 +1330,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): @@ -1475,6 +1507,9 @@ async def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1597,7 +1632,14 @@ async def _process_response( origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") diff --git a/src/finch/_models.py b/src/finch/_models.py index 798956f1..4f214980 100644 --- a/src/finch/_models.py +++ b/src/finch/_models.py @@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): idempotency_key: str json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. diff --git a/src/finch/_types.py b/src/finch/_types.py index 643bfe5e..202fb730 100644 --- a/src/finch/_types.py +++ b/src/finch/_types.py @@ -101,6 +101,7 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted @@ -217,3 +218,4 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool diff --git a/src/finch/_version.py b/src/finch/_version.py index dc7a3a54..89726bc8 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.29.0" # x-release-please-version +__version__ = "1.30.0" # x-release-please-version diff --git a/src/finch/resources/connect/sessions.py b/src/finch/resources/connect/sessions.py index 80c13640..c88c3a67 100644 --- a/src/finch/resources/connect/sessions.py +++ b/src/finch/resources/connect/sessions.py @@ -77,8 +77,8 @@ def new( Create a new connect session for an employer Args: - minutes_to_expire: The number of minutes until the session expires (defaults to 43,200, which is 30 - days) + minutes_to_expire: The number of minutes until the session expires (defaults to 129,600, which is + 90 days) extra_headers: Send extra headers @@ -235,8 +235,8 @@ async def new( Create a new connect session for an employer Args: - minutes_to_expire: The number of minutes until the session expires (defaults to 43,200, which is 30 - days) + minutes_to_expire: The number of minutes until the session expires (defaults to 129,600, which is + 90 days) extra_headers: Send extra headers diff --git a/src/finch/resources/hris/benefits/benefits.py b/src/finch/resources/hris/benefits/benefits.py index ed4dbe08..eecb7b7f 100644 --- a/src/finch/resources/hris/benefits/benefits.py +++ b/src/finch/resources/hris/benefits/benefits.py @@ -26,9 +26,9 @@ from ....types.hris.benefit_type import BenefitType from ....types.hris.company_benefit import CompanyBenefit from ....types.hris.benefit_frequency import BenefitFrequency +from ....types.hris.supported_benefit import SupportedBenefit from ....types.hris.update_company_benefit_response import UpdateCompanyBenefitResponse from ....types.hris.create_company_benefits_response import CreateCompanyBenefitsResponse -from ....types.hris.benefit_list_supported_benefits_response import BenefitListSupportedBenefitsResponse __all__ = ["Benefits", "AsyncBenefits"] @@ -211,15 +211,15 @@ def list_supported_benefits( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncSinglePage[BenefitListSupportedBenefitsResponse]: + ) -> SyncSinglePage[SupportedBenefit]: """Get deductions metadata""" return self._get_api_list( "/employer/benefits/meta", - page=SyncSinglePage[BenefitListSupportedBenefitsResponse], + page=SyncSinglePage[SupportedBenefit], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - model=BenefitListSupportedBenefitsResponse, + model=SupportedBenefit, ) @@ -401,15 +401,15 @@ def list_supported_benefits( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[BenefitListSupportedBenefitsResponse, AsyncSinglePage[BenefitListSupportedBenefitsResponse]]: + ) -> AsyncPaginator[SupportedBenefit, AsyncSinglePage[SupportedBenefit]]: """Get deductions metadata""" return self._get_api_list( "/employer/benefits/meta", - page=AsyncSinglePage[BenefitListSupportedBenefitsResponse], + page=AsyncSinglePage[SupportedBenefit], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - model=BenefitListSupportedBenefitsResponse, + model=SupportedBenefit, ) diff --git a/src/finch/resources/sandbox/payment.py b/src/finch/resources/sandbox/payment.py index 8092fe60..dc2ec15e 100644 --- a/src/finch/resources/sandbox/payment.py +++ b/src/finch/resources/sandbox/payment.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Iterable +from typing import Union, Iterable +from datetime import date import httpx @@ -42,9 +43,9 @@ def with_streaming_response(self) -> PaymentWithStreamingResponse: def create( self, *, - end_date: str | NotGiven = NOT_GIVEN, + end_date: Union[str, date] | NotGiven = NOT_GIVEN, pay_statements: Iterable[payment_create_params.PayStatement] | NotGiven = NOT_GIVEN, - start_date: str | NotGiven = NOT_GIVEN, + start_date: Union[str, date] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -56,6 +57,8 @@ def create( Add a new sandbox payment Args: + pay_statements: Array of pay statements to include in the payment. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -104,9 +107,9 @@ def with_streaming_response(self) -> AsyncPaymentWithStreamingResponse: async def create( self, *, - end_date: str | NotGiven = NOT_GIVEN, + end_date: Union[str, date] | NotGiven = NOT_GIVEN, pay_statements: Iterable[payment_create_params.PayStatement] | NotGiven = NOT_GIVEN, - start_date: str | NotGiven = NOT_GIVEN, + start_date: Union[str, date] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -118,6 +121,8 @@ async def create( Add a new sandbox payment Args: + pay_statements: Array of pay statements to include in the payment. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/finch/types/__init__.py b/src/finch/types/__init__.py index 4c2b5d4e..14bf231d 100644 --- a/src/finch/types/__init__.py +++ b/src/finch/types/__init__.py @@ -12,7 +12,6 @@ ) from .location import Location as Location from .provider import Provider as Provider -from .money_param import MoneyParam as MoneyParam from .income_param import IncomeParam as IncomeParam from .company_event import CompanyEvent as CompanyEvent from .introspection import Introspection as Introspection diff --git a/src/finch/types/connect/session_new_params.py b/src/finch/types/connect/session_new_params.py index 5434176a..f49dc752 100644 --- a/src/finch/types/connect/session_new_params.py +++ b/src/finch/types/connect/session_new_params.py @@ -38,8 +38,8 @@ class SessionNewParams(TypedDict, total=False): minutes_to_expire: Optional[float] """ - The number of minutes until the session expires (defaults to 43,200, which is 30 - days) + The number of minutes until the session expires (defaults to 129,600, which is + 90 days) """ redirect_uri: Optional[str] diff --git a/src/finch/types/hris/__init__.py b/src/finch/types/hris/__init__.py index 4f9edc30..cbb3ebd9 100644 --- a/src/finch/types/hris/__init__.py +++ b/src/finch/types/hris/__init__.py @@ -14,6 +14,7 @@ from .benefits_support import BenefitsSupport as BenefitsSupport from .benefit_frequency import BenefitFrequency as BenefitFrequency from .document_response import DocumentResponse as DocumentResponse +from .supported_benefit import SupportedBenefit as SupportedBenefit from .benfit_contribution import BenfitContribution as BenfitContribution from .individual_response import IndividualResponse as IndividualResponse from .payment_list_params import PaymentListParams as PaymentListParams @@ -36,6 +37,4 @@ from .create_company_benefits_response import CreateCompanyBenefitsResponse as CreateCompanyBenefitsResponse from .directory_list_individuals_params import DirectoryListIndividualsParams as DirectoryListIndividualsParams from .pay_statement_retrieve_many_params import PayStatementRetrieveManyParams as PayStatementRetrieveManyParams -from .benefit_list_supported_benefits_response import ( - BenefitListSupportedBenefitsResponse as BenefitListSupportedBenefitsResponse, -) +from .pay_statement_data_sync_in_progress import PayStatementDataSyncInProgress as PayStatementDataSyncInProgress diff --git a/src/finch/types/hris/benefit_features_and_operations.py b/src/finch/types/hris/benefit_features_and_operations.py index b357f72f..89f9be7a 100644 --- a/src/finch/types/hris/benefit_features_and_operations.py +++ b/src/finch/types/hris/benefit_features_and_operations.py @@ -1,51 +1,15 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional -from typing_extensions import Literal +from typing import Optional from ..._models import BaseModel -from .benefit_frequency import BenefitFrequency +from .supported_benefit import SupportedBenefit 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[Optional[Literal["fixed", "percent"]]]] = None - """Supported contribution types. - - An empty array indicates contributions are not supported. - """ - - description: Optional[str] = None - - employee_deduction: Optional[List[Optional[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[Optional[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. - """ +__all__ = ["BenefitFeaturesAndOperations"] class BenefitFeaturesAndOperations(BaseModel): - supported_features: Optional[SupportedFeatures] = None + supported_features: Optional[SupportedBenefit] = None supported_operations: Optional[SupportPerBenefitType] = None diff --git a/src/finch/types/hris/benefit_type.py b/src/finch/types/hris/benefit_type.py index 2c8ad7f4..9f5b4132 100644 --- a/src/finch/types/hris/benefit_type.py +++ b/src/finch/types/hris/benefit_type.py @@ -7,24 +7,24 @@ BenefitType: TypeAlias = Optional[ Literal[ + "457", "401k", "401k_roth", "401k_loan", "403b", "403b_roth", - "457", "457_roth", - "s125_medical", - "s125_dental", - "s125_vision", - "hsa_pre", - "hsa_post", - "fsa_medical", - "fsa_dependent_care", - "simple_ira", - "simple", "commuter", "custom_post_tax", "custom_pre_tax", + "fsa_dependent_care", + "fsa_medical", + "hsa_post", + "hsa_pre", + "s125_dental", + "s125_medical", + "s125_vision", + "simple", + "simple_ira", ] ] diff --git a/src/finch/types/hris/company/pay_statement_item_list_response.py b/src/finch/types/hris/company/pay_statement_item_list_response.py index 31e9cf16..e2ca25b0 100644 --- a/src/finch/types/hris/company/pay_statement_item_list_response.py +++ b/src/finch/types/hris/company/pay_statement_item_list_response.py @@ -9,18 +9,18 @@ class Attributes(BaseModel): - employer: Optional[bool] = None - """`true` if the amount is paid by the employers. - - This field is only available for taxes. - """ - metadata: Optional[Dict[str, Optional[object]]] = None """The metadata of the pay statement item derived by the rules engine if available. Each attribute will be a key-value pair defined by a rule. """ + employer: Optional[bool] = None + """`true` if the amount is paid by the employers. + + This field is only available for taxes. + """ + pre_tax: Optional[bool] = None """`true` if the pay statement item is pre-tax. @@ -32,11 +32,11 @@ class Attributes(BaseModel): class PayStatementItemListResponse(BaseModel): - attributes: Optional[Attributes] = None + attributes: Attributes """The attributes of the pay statement item.""" - category: Optional[Literal["earnings", "taxes", "employee_deductions", "employer_contributions"]] = None + category: Literal["earnings", "taxes", "employee_deductions", "employer_contributions"] """The category of the pay statement item.""" - name: Optional[str] = None + name: str """The name of the pay statement item.""" diff --git a/src/finch/types/hris/employment_data.py b/src/finch/types/hris/employment_data.py index dab5f5f6..0804a2a0 100644 --- a/src/finch/types/hris/employment_data.py +++ b/src/finch/types/hris/employment_data.py @@ -10,20 +10,14 @@ __all__ = [ "EmploymentData", "UnionMember0", - "UnionMember0CustomField", "UnionMember0Department", "UnionMember0Employment", "UnionMember0Manager", + "UnionMember0CustomField", "BatchError", ] -class UnionMember0CustomField(BaseModel): - name: Optional[str] = None - - value: Union[Optional[str], Optional[List[object]], Optional[float], Optional[bool], Optional[object], None] = None - - class UnionMember0Department(BaseModel): name: Optional[str] = None """The name of the department associated with the individual.""" @@ -46,6 +40,12 @@ class UnionMember0Manager(BaseModel): """A stable Finch `id` (UUID v4) for an individual in the company.""" +class UnionMember0CustomField(BaseModel): + name: Optional[str] = None + + value: Union[Optional[str], Optional[List[object]], Optional[float], Optional[bool], Optional[object], None] = None + + class UnionMember0(BaseModel): id: str """A stable Finch `id` (UUID v4) for an individual in the company.""" @@ -53,13 +53,6 @@ class UnionMember0(BaseModel): class_code: Optional[str] = None """Worker's compensation classification code for this employee""" - custom_fields: Optional[List[UnionMember0CustomField]] = None - """Custom fields for the individual. - - These are fields which are defined by the employer in the system. Custom fields - are not currently supported for assisted connections. - """ - department: Optional[UnionMember0Department] = None """The department object.""" @@ -101,8 +94,12 @@ class UnionMember0(BaseModel): title: Optional[str] = None """The current title of the individual.""" - work_id: Optional[str] = None - """This field is deprecated in favour of `source_id`""" + custom_fields: Optional[List[UnionMember0CustomField]] = None + """Custom fields for the individual. + + These are fields which are defined by the employer in the system. Custom fields + are not currently supported for assisted connections. + """ income_history: Optional[List[Optional[Income]]] = None """The array of income history.""" @@ -117,6 +114,9 @@ class UnionMember0(BaseModel): source_id: Optional[str] = None """The source system's unique employment identifier for this individual""" + work_id: Optional[str] = None + """This field is deprecated in favour of `source_id`""" + class BatchError(BaseModel): code: float diff --git a/src/finch/types/hris/pay_statement.py b/src/finch/types/hris/pay_statement.py index 7c6f63b6..1c874672 100644 --- a/src/finch/types/hris/pay_statement.py +++ b/src/finch/types/hris/pay_statement.py @@ -25,7 +25,7 @@ class EarningAttributesMetadata(BaseModel): - metadata: Optional[Dict[str, Optional[object]]] = None + metadata: Dict[str, Optional[object]] """The metadata to be attached to the entity by existing rules. It is a key-value pairs where the values can be of any type (string, number, @@ -34,15 +34,13 @@ class EarningAttributesMetadata(BaseModel): class EarningAttributes(BaseModel): - metadata: Optional[EarningAttributesMetadata] = None + metadata: EarningAttributesMetadata class Earning(BaseModel): amount: Optional[int] = None """The earnings amount in cents.""" - attributes: Optional[EarningAttributes] = None - currency: Optional[str] = None """The earnings currency code.""" @@ -75,9 +73,11 @@ class Earning(BaseModel): ] = None """The type of earning.""" + attributes: Optional[EarningAttributes] = None + class EmployeeDeductionAttributesMetadata(BaseModel): - metadata: Optional[Dict[str, Optional[object]]] = None + metadata: Dict[str, Optional[object]] """The metadata to be attached to the entity by existing rules. It is a key-value pairs where the values can be of any type (string, number, @@ -86,15 +86,13 @@ class EmployeeDeductionAttributesMetadata(BaseModel): class EmployeeDeductionAttributes(BaseModel): - metadata: Optional[EmployeeDeductionAttributesMetadata] = None + metadata: EmployeeDeductionAttributesMetadata class EmployeeDeduction(BaseModel): amount: Optional[int] = None """The deduction amount in cents.""" - attributes: Optional[EmployeeDeductionAttributes] = None - currency: Optional[str] = None """The deduction currency.""" @@ -107,9 +105,11 @@ class EmployeeDeduction(BaseModel): type: Optional[BenefitType] = None """Type of benefit.""" + attributes: Optional[EmployeeDeductionAttributes] = None + class EmployerContributionAttributesMetadata(BaseModel): - metadata: Optional[Dict[str, Optional[object]]] = None + metadata: Dict[str, Optional[object]] """The metadata to be attached to the entity by existing rules. It is a key-value pairs where the values can be of any type (string, number, @@ -118,15 +118,10 @@ class EmployerContributionAttributesMetadata(BaseModel): class EmployerContributionAttributes(BaseModel): - metadata: Optional[EmployerContributionAttributesMetadata] = None + metadata: EmployerContributionAttributesMetadata class EmployerContribution(BaseModel): - amount: Optional[int] = None - """The contribution amount in cents.""" - - attributes: Optional[EmployerContributionAttributes] = None - currency: Optional[str] = None """The contribution currency.""" @@ -136,9 +131,14 @@ class EmployerContribution(BaseModel): type: Optional[BenefitType] = None """Type of benefit.""" + amount: Optional[int] = None + """The contribution amount in cents.""" + + attributes: Optional[EmployerContributionAttributes] = None + class TaxAttributesMetadata(BaseModel): - metadata: Optional[Dict[str, Optional[object]]] = None + metadata: Dict[str, Optional[object]] """The metadata to be attached to the entity by existing rules. It is a key-value pairs where the values can be of any type (string, number, @@ -147,15 +147,10 @@ class TaxAttributesMetadata(BaseModel): class TaxAttributes(BaseModel): - metadata: Optional[TaxAttributesMetadata] = None + metadata: TaxAttributesMetadata class Tax(BaseModel): - amount: Optional[int] = None - """The tax amount in cents.""" - - attributes: Optional[TaxAttributes] = None - currency: Optional[str] = None """The currency code.""" @@ -168,6 +163,11 @@ class Tax(BaseModel): type: Optional[Literal["state", "federal", "local", "fica"]] = None """The type of taxes.""" + amount: Optional[int] = None + """The tax amount in cents.""" + + attributes: Optional[TaxAttributes] = None + class PayStatement(BaseModel): earnings: Optional[List[Optional[Earning]]] = None @@ -180,7 +180,7 @@ class PayStatement(BaseModel): gross_pay: Optional[Money] = None - individual_id: Optional[str] = None + individual_id: str """A stable Finch `id` (UUID v4) for an individual in the company""" net_pay: Optional[Money] = None @@ -194,5 +194,5 @@ class PayStatement(BaseModel): total_hours: Optional[float] = None """The number of hours worked for this pay period""" - type: Optional[Literal["regular_payroll", "off_cycle_payroll", "one_time_payment"]] = None + type: Optional[Literal["off_cycle_payroll", "one_time_payment", "regular_payroll"]] = None """The type of the payment associated with the pay statement.""" diff --git a/src/finch/types/hris/pay_statement_data_sync_in_progress.py b/src/finch/types/hris/pay_statement_data_sync_in_progress.py new file mode 100644 index 00000000..c9561566 --- /dev/null +++ b/src/finch/types/hris/pay_statement_data_sync_in_progress.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["PayStatementDataSyncInProgress"] + + +class PayStatementDataSyncInProgress(BaseModel): + code: Literal[202] + + finch_code: Literal["data_sync_in_progress"] + + message: Literal["The pay statements for this payment are being fetched. Please check back later."] + + name: Literal["accepted"] diff --git a/src/finch/types/hris/pay_statement_response.py b/src/finch/types/hris/pay_statement_response.py index 35c33990..56825783 100644 --- a/src/finch/types/hris/pay_statement_response.py +++ b/src/finch/types/hris/pay_statement_response.py @@ -1,16 +1,31 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import Union, Optional +from typing_extensions import TypeAlias from ..._models import BaseModel from .pay_statement_response_body import PayStatementResponseBody +from .pay_statement_data_sync_in_progress import PayStatementDataSyncInProgress -__all__ = ["PayStatementResponse"] +__all__ = ["PayStatementResponse", "Body", "BodyBatchError"] + + +class BodyBatchError(BaseModel): + code: float + + message: str + + name: str + + finch_code: Optional[str] = None + + +Body: TypeAlias = Union[PayStatementResponseBody, BodyBatchError, PayStatementDataSyncInProgress] class PayStatementResponse(BaseModel): - body: Optional[PayStatementResponseBody] = None + body: Body - code: Optional[int] = None + code: int - payment_id: Optional[str] = None + payment_id: str diff --git a/src/finch/types/hris/pay_statement_response_body.py b/src/finch/types/hris/pay_statement_response_body.py index 51ef1584..454a807c 100644 --- a/src/finch/types/hris/pay_statement_response_body.py +++ b/src/finch/types/hris/pay_statement_response_body.py @@ -4,13 +4,19 @@ from ..._models import BaseModel from .pay_statement import PayStatement -from ..shared.paging import Paging -__all__ = ["PayStatementResponseBody"] +__all__ = ["PayStatementResponseBody", "Paging"] + + +class Paging(BaseModel): + offset: int + """The current start index of the returned list of elements""" + + count: Optional[int] = None + """The total number of elements for the entire query (not just the given page)""" class PayStatementResponseBody(BaseModel): - paging: Optional[Paging] = None + paging: Paging - pay_statements: Optional[List[PayStatement]] = None - """The array of pay statements for the current payment.""" + pay_statements: List[PayStatement] diff --git a/src/finch/types/hris/payment.py b/src/finch/types/hris/payment.py index d227f162..992006f1 100644 --- a/src/finch/types/hris/payment.py +++ b/src/finch/types/hris/payment.py @@ -16,7 +16,7 @@ class PayPeriod(BaseModel): class Payment(BaseModel): - id: Optional[str] = None + id: str """The unique id for the payment.""" company_debit: Optional[Money] = None @@ -40,14 +40,14 @@ class Payment(BaseModel): List[ Literal[ "annually", - "semi_annually", - "quarterly", - "monthly", - "semi_monthly", "bi_weekly", - "weekly", "daily", + "monthly", "other", + "quarterly", + "semi_annually", + "semi_monthly", + "weekly", ] ] ] = None diff --git a/src/finch/types/hris/benefit_list_supported_benefits_response.py b/src/finch/types/hris/supported_benefit.py similarity index 92% rename from src/finch/types/hris/benefit_list_supported_benefits_response.py rename to src/finch/types/hris/supported_benefit.py index e29b2b7f..08d94399 100644 --- a/src/finch/types/hris/benefit_list_supported_benefits_response.py +++ b/src/finch/types/hris/supported_benefit.py @@ -6,10 +6,10 @@ from ..._models import BaseModel from .benefit_frequency import BenefitFrequency -__all__ = ["BenefitListSupportedBenefitsResponse"] +__all__ = ["SupportedBenefit"] -class BenefitListSupportedBenefitsResponse(BaseModel): +class SupportedBenefit(BaseModel): annual_maximum: Optional[bool] = None """Whether the provider supports an annual maximum for this benefit.""" diff --git a/src/finch/types/income.py b/src/finch/types/income.py index d01ba0e9..2b1f36b2 100644 --- a/src/finch/types/income.py +++ b/src/finch/types/income.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import date from typing_extensions import Literal from .._models import BaseModel @@ -15,7 +16,7 @@ class Income(BaseModel): currency: Optional[str] = None """The currency code.""" - effective_date: Optional[str] = None + effective_date: Optional[date] = None """The date the income amount went into effect.""" unit: Optional[ diff --git a/src/finch/types/income_param.py b/src/finch/types/income_param.py index d27b1f8b..ed0bb0c2 100644 --- a/src/finch/types/income_param.py +++ b/src/finch/types/income_param.py @@ -2,8 +2,11 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing import Union, Optional +from datetime import date +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo __all__ = ["IncomeParam"] @@ -15,7 +18,7 @@ class IncomeParam(TypedDict, total=False): currency: Required[Optional[str]] """The currency code.""" - effective_date: Required[Optional[str]] + effective_date: Required[Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]] """The date the income amount went into effect.""" unit: Required[ diff --git a/src/finch/types/money.py b/src/finch/types/money.py index 21ebb963..86c433ad 100644 --- a/src/finch/types/money.py +++ b/src/finch/types/money.py @@ -11,4 +11,4 @@ class Money(BaseModel): amount: Optional[int] = None """Amount for money object (in cents)""" - currency: Optional[str] = None + currency: str diff --git a/src/finch/types/money_param.py b/src/finch/types/money_param.py deleted file mode 100644 index 180e8687..00000000 --- a/src/finch/types/money_param.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import TypedDict - -__all__ = ["MoneyParam"] - - -class MoneyParam(TypedDict, total=False): - amount: Optional[int] - """Amount for money object (in cents)""" - - currency: str diff --git a/src/finch/types/payroll/pay_group_list_response.py b/src/finch/types/payroll/pay_group_list_response.py index cd25dfcc..a1fb9826 100644 --- a/src/finch/types/payroll/pay_group_list_response.py +++ b/src/finch/types/payroll/pay_group_list_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import List from typing_extensions import Literal from ..._models import BaseModel @@ -9,25 +9,15 @@ class PayGroupListResponse(BaseModel): - id: Optional[str] = None + id: str """Finch id (uuidv4) for the pay group""" - name: Optional[str] = None + name: str """Name of the pay group""" - pay_frequencies: Optional[ - List[ - Literal[ - "annually", - "semi_annually", - "quarterly", - "monthly", - "semi_monthly", - "bi_weekly", - "weekly", - "daily", - "other", - ] + pay_frequencies: List[ + Literal[ + "annually", "bi_weekly", "daily", "monthly", "other", "quarterly", "semi_annually", "semi_monthly", "weekly" ] - ] = None + ] """List of pay frequencies associated with this pay group""" diff --git a/src/finch/types/payroll/pay_group_retrieve_response.py b/src/finch/types/payroll/pay_group_retrieve_response.py index b07ec41c..b3ff7092 100644 --- a/src/finch/types/payroll/pay_group_retrieve_response.py +++ b/src/finch/types/payroll/pay_group_retrieve_response.py @@ -19,7 +19,7 @@ class PayGroupRetrieveResponse(BaseModel): pay_frequencies: List[ Literal[ - "annually", "semi_annually", "quarterly", "monthly", "semi_monthly", "bi_weekly", "weekly", "daily", "other" + "annually", "bi_weekly", "daily", "monthly", "other", "quarterly", "semi_annually", "semi_monthly", "weekly" ] ] """List of pay frequencies associated with this pay group""" diff --git a/src/finch/types/sandbox/payment_create_params.py b/src/finch/types/sandbox/payment_create_params.py index d35089b0..1f74fb81 100644 --- a/src/finch/types/sandbox/payment_create_params.py +++ b/src/finch/types/sandbox/payment_create_params.py @@ -2,207 +2,140 @@ from __future__ import annotations -from typing import Dict, Iterable, Optional -from typing_extensions import Literal, TypedDict +from typing import Union, Iterable, Optional +from datetime import date +from typing_extensions import Literal, Required, Annotated, TypedDict -from ..money_param import MoneyParam -from ..hris.benefit_type import BenefitType +from ..._utils import PropertyInfo __all__ = [ "PaymentCreateParams", "PayStatement", "PayStatementEarning", - "PayStatementEarningAttributes", - "PayStatementEarningAttributesMetadata", "PayStatementEmployeeDeduction", - "PayStatementEmployeeDeductionAttributes", - "PayStatementEmployeeDeductionAttributesMetadata", "PayStatementEmployerContribution", - "PayStatementEmployerContributionAttributes", - "PayStatementEmployerContributionAttributesMetadata", "PayStatementTax", - "PayStatementTaxAttributes", - "PayStatementTaxAttributesMetadata", ] class PaymentCreateParams(TypedDict, total=False): - end_date: str + end_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")] pay_statements: Iterable[PayStatement] + """Array of pay statements to include in the payment.""" - start_date: str - - -class PayStatementEarningAttributesMetadata(TypedDict, total=False): - metadata: Dict[str, Optional[object]] - """The metadata to be attached to the entity by existing rules. - - It is a key-value pairs where the values can be of any type (string, number, - boolean, object, array, etc.). - """ - - -class PayStatementEarningAttributes(TypedDict, total=False): - metadata: PayStatementEarningAttributesMetadata + start_date: Annotated[Union[str, date], PropertyInfo(format="iso8601")] class PayStatementEarning(TypedDict, total=False): - amount: Optional[int] - """The earnings amount in cents.""" - - attributes: Optional[PayStatementEarningAttributes] - - currency: Optional[str] - """The earnings currency code.""" - - hours: Optional[float] - """The number of hours associated with this earning. - - (For salaried employees, this could be hours per pay period, `0` or `null`, - depending on the provider). - """ - - name: Optional[str] - """The exact name of the deduction from the pay statement.""" - - type: Optional[ - Literal[ - "salary", - "wage", - "reimbursement", - "overtime", - "severance", - "double_overtime", - "pto", - "sick", - "bonus", - "commission", - "tips", - "1099", - "other", - ] + amount: int + + hours: float + + name: str + + type: Literal[ + "bonus", + "commission", + "double_overtime", + "other", + "overtime", + "pto", + "reimbursement", + "salary", + "severance", + "sick", + "tips", + "wage", + "1099", ] - """The type of earning.""" - - -class PayStatementEmployeeDeductionAttributesMetadata(TypedDict, total=False): - metadata: Dict[str, Optional[object]] - """The metadata to be attached to the entity by existing rules. - - It is a key-value pairs where the values can be of any type (string, number, - boolean, object, array, etc.). - """ - - -class PayStatementEmployeeDeductionAttributes(TypedDict, total=False): - metadata: PayStatementEmployeeDeductionAttributesMetadata class PayStatementEmployeeDeduction(TypedDict, total=False): - amount: Optional[int] - """The deduction amount in cents.""" - - attributes: Optional[PayStatementEmployeeDeductionAttributes] - - currency: Optional[str] - """The deduction currency.""" - - name: Optional[str] - """The deduction name from the pay statement.""" - - pre_tax: Optional[bool] - """Boolean indicating if the deduction is pre-tax.""" - - type: Optional[BenefitType] - """Type of benefit.""" - - -class PayStatementEmployerContributionAttributesMetadata(TypedDict, total=False): - metadata: Dict[str, Optional[object]] - """The metadata to be attached to the entity by existing rules. - - It is a key-value pairs where the values can be of any type (string, number, - boolean, object, array, etc.). - """ - - -class PayStatementEmployerContributionAttributes(TypedDict, total=False): - metadata: PayStatementEmployerContributionAttributesMetadata + amount: int + + name: str + + pre_tax: bool + + type: Literal[ + "457", + "401k", + "401k_roth", + "401k_loan", + "403b", + "403b_roth", + "457_roth", + "commuter", + "custom_post_tax", + "custom_pre_tax", + "fsa_dependent_care", + "fsa_medical", + "hsa_post", + "hsa_pre", + "s125_dental", + "s125_medical", + "s125_vision", + "simple", + "simple_ira", + ] class PayStatementEmployerContribution(TypedDict, total=False): - amount: Optional[int] - """The contribution amount in cents.""" - - attributes: Optional[PayStatementEmployerContributionAttributes] - - currency: Optional[str] - """The contribution currency.""" - - name: Optional[str] - """The contribution name from the pay statement.""" - - type: Optional[BenefitType] - """Type of benefit.""" - - -class PayStatementTaxAttributesMetadata(TypedDict, total=False): - metadata: Dict[str, Optional[object]] - """The metadata to be attached to the entity by existing rules. - - It is a key-value pairs where the values can be of any type (string, number, - boolean, object, array, etc.). - """ - - -class PayStatementTaxAttributes(TypedDict, total=False): - metadata: PayStatementTaxAttributesMetadata + amount: int + + name: str + + type: Literal[ + "457", + "401k", + "401k_roth", + "401k_loan", + "403b", + "403b_roth", + "457_roth", + "commuter", + "custom_post_tax", + "custom_pre_tax", + "fsa_dependent_care", + "fsa_medical", + "hsa_post", + "hsa_pre", + "s125_dental", + "s125_medical", + "s125_vision", + "simple", + "simple_ira", + ] class PayStatementTax(TypedDict, total=False): - amount: Optional[int] - """The tax amount in cents.""" - - attributes: Optional[PayStatementTaxAttributes] - - currency: Optional[str] - """The currency code.""" + amount: int - employer: Optional[bool] - """`true` if the amount is paid by the employers.""" + employer: bool - name: Optional[str] - """The exact name of tax from the pay statement.""" + name: str - type: Optional[Literal["state", "federal", "local", "fica"]] - """The type of taxes.""" + type: Literal["federal", "fica", "local", "state"] class PayStatement(TypedDict, total=False): - earnings: Optional[Iterable[Optional[PayStatementEarning]]] - """The array of earnings objects associated with this pay statement""" + individual_id: Required[str] - employee_deductions: Optional[Iterable[Optional[PayStatementEmployeeDeduction]]] - """The array of deductions objects associated with this pay statement.""" + earnings: Iterable[PayStatementEarning] - employer_contributions: Optional[Iterable[Optional[PayStatementEmployerContribution]]] + employee_deductions: Iterable[PayStatementEmployeeDeduction] - gross_pay: Optional[MoneyParam] + employer_contributions: Iterable[PayStatementEmployerContribution] - individual_id: str - """A stable Finch `id` (UUID v4) for an individual in the company""" + gross_pay: int - net_pay: Optional[MoneyParam] + net_pay: int payment_method: Optional[Literal["check", "direct_deposit", "other"]] - """The payment method.""" - taxes: Optional[Iterable[Optional[PayStatementTax]]] - """The array of taxes objects associated with this pay statement.""" + taxes: Iterable[PayStatementTax] - total_hours: Optional[float] - """The number of hours worked for this pay period""" + total_hours: float - type: Optional[Literal["regular_payroll", "off_cycle_payroll", "one_time_payment"]] - """The type of the payment associated with the pay statement.""" + type: Optional[Literal["off_cycle_payroll", "one_time_payment", "regular_payroll"]] diff --git a/tests/api_resources/connect/test_sessions.py b/tests/api_resources/connect/test_sessions.py index cc53a9b7..f437d7e6 100644 --- a/tests/api_resources/connect/test_sessions.py +++ b/tests/api_resources/connect/test_sessions.py @@ -20,6 +20,7 @@ class TestSessions: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_new(self, client: Finch) -> None: session = client.connect.sessions.new( @@ -29,6 +30,7 @@ def test_method_new(self, client: Finch) -> None: ) assert_matches_type(SessionNewResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_new_with_all_params(self, client: Finch) -> None: session = client.connect.sessions.new( @@ -47,6 +49,7 @@ def test_method_new_with_all_params(self, client: Finch) -> None: ) assert_matches_type(SessionNewResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_raw_response_new(self, client: Finch) -> None: response = client.connect.sessions.with_raw_response.new( @@ -60,6 +63,7 @@ def test_raw_response_new(self, client: Finch) -> None: session = response.parse() assert_matches_type(SessionNewResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_streaming_response_new(self, client: Finch) -> None: with client.connect.sessions.with_streaming_response.new( @@ -75,6 +79,7 @@ def test_streaming_response_new(self, client: Finch) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_reauthenticate(self, client: Finch) -> None: session = client.connect.sessions.reauthenticate( @@ -82,6 +87,7 @@ def test_method_reauthenticate(self, client: Finch) -> None: ) assert_matches_type(SessionReauthenticateResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_reauthenticate_with_all_params(self, client: Finch) -> None: session = client.connect.sessions.reauthenticate( @@ -92,6 +98,7 @@ def test_method_reauthenticate_with_all_params(self, client: Finch) -> None: ) assert_matches_type(SessionReauthenticateResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_raw_response_reauthenticate(self, client: Finch) -> None: response = client.connect.sessions.with_raw_response.reauthenticate( @@ -103,6 +110,7 @@ def test_raw_response_reauthenticate(self, client: Finch) -> None: session = response.parse() assert_matches_type(SessionReauthenticateResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_streaming_response_reauthenticate(self, client: Finch) -> None: with client.connect.sessions.with_streaming_response.reauthenticate( @@ -118,8 +126,11 @@ def test_streaming_response_reauthenticate(self, client: Finch) -> None: class TestAsyncSessions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_new(self, async_client: AsyncFinch) -> None: session = await async_client.connect.sessions.new( @@ -129,6 +140,7 @@ async def test_method_new(self, async_client: AsyncFinch) -> None: ) assert_matches_type(SessionNewResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_new_with_all_params(self, async_client: AsyncFinch) -> None: session = await async_client.connect.sessions.new( @@ -147,6 +159,7 @@ async def test_method_new_with_all_params(self, async_client: AsyncFinch) -> Non ) assert_matches_type(SessionNewResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_raw_response_new(self, async_client: AsyncFinch) -> None: response = await async_client.connect.sessions.with_raw_response.new( @@ -160,6 +173,7 @@ async def test_raw_response_new(self, async_client: AsyncFinch) -> None: session = response.parse() assert_matches_type(SessionNewResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_streaming_response_new(self, async_client: AsyncFinch) -> None: async with async_client.connect.sessions.with_streaming_response.new( @@ -175,6 +189,7 @@ async def test_streaming_response_new(self, async_client: AsyncFinch) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_reauthenticate(self, async_client: AsyncFinch) -> None: session = await async_client.connect.sessions.reauthenticate( @@ -182,6 +197,7 @@ async def test_method_reauthenticate(self, async_client: AsyncFinch) -> None: ) assert_matches_type(SessionReauthenticateResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_reauthenticate_with_all_params(self, async_client: AsyncFinch) -> None: session = await async_client.connect.sessions.reauthenticate( @@ -192,6 +208,7 @@ async def test_method_reauthenticate_with_all_params(self, async_client: AsyncFi ) assert_matches_type(SessionReauthenticateResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_raw_response_reauthenticate(self, async_client: AsyncFinch) -> None: response = await async_client.connect.sessions.with_raw_response.reauthenticate( @@ -203,6 +220,7 @@ async def test_raw_response_reauthenticate(self, async_client: AsyncFinch) -> No session = response.parse() assert_matches_type(SessionReauthenticateResponse, session, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_streaming_response_reauthenticate(self, async_client: AsyncFinch) -> None: async with async_client.connect.sessions.with_streaming_response.reauthenticate( diff --git a/tests/api_resources/hris/benefits/test_individuals.py b/tests/api_resources/hris/benefits/test_individuals.py index 865bfff0..13ee6a10 100644 --- a/tests/api_resources/hris/benefits/test_individuals.py +++ b/tests/api_resources/hris/benefits/test_individuals.py @@ -220,7 +220,9 @@ def test_path_params_unenroll_many(self, client: Finch) -> None: class TestAsyncIndividuals: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_enroll_many(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/company/pay_statement_item/test_rules.py b/tests/api_resources/hris/company/pay_statement_item/test_rules.py index 5cf919d9..340a7d99 100644 --- a/tests/api_resources/hris/company/pay_statement_item/test_rules.py +++ b/tests/api_resources/hris/company/pay_statement_item/test_rules.py @@ -176,7 +176,9 @@ def test_path_params_delete(self, client: Finch) -> None: class TestAsyncRules: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/company/test_pay_statement_item.py b/tests/api_resources/hris/company/test_pay_statement_item.py index 35f0aa08..c270ce8e 100644 --- a/tests/api_resources/hris/company/test_pay_statement_item.py +++ b/tests/api_resources/hris/company/test_pay_statement_item.py @@ -57,7 +57,9 @@ def test_streaming_response_list(self, client: Finch) -> None: class TestAsyncPayStatementItem: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/test_benefits.py b/tests/api_resources/hris/test_benefits.py index 9a55c13c..7961a951 100644 --- a/tests/api_resources/hris/test_benefits.py +++ b/tests/api_resources/hris/test_benefits.py @@ -12,9 +12,9 @@ from finch.pagination import SyncSinglePage, AsyncSinglePage from finch.types.hris import ( CompanyBenefit, + SupportedBenefit, UpdateCompanyBenefitResponse, CreateCompanyBenefitsResponse, - BenefitListSupportedBenefitsResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -42,7 +42,7 @@ def test_method_create_with_all_params(self, client: Finch) -> None: }, description="description", frequency="one_time", - type="401k", + type="457", ) assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) @@ -178,7 +178,7 @@ def test_streaming_response_list(self, client: Finch) -> None: @parametrize def test_method_list_supported_benefits(self, client: Finch) -> None: benefit = client.hris.benefits.list_supported_benefits() - assert_matches_type(SyncSinglePage[BenefitListSupportedBenefitsResponse], benefit, path=["response"]) + assert_matches_type(SyncSinglePage[SupportedBenefit], benefit, path=["response"]) @parametrize def test_raw_response_list_supported_benefits(self, client: Finch) -> None: @@ -187,7 +187,7 @@ def test_raw_response_list_supported_benefits(self, client: Finch) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() - assert_matches_type(SyncSinglePage[BenefitListSupportedBenefitsResponse], benefit, path=["response"]) + assert_matches_type(SyncSinglePage[SupportedBenefit], benefit, path=["response"]) @parametrize def test_streaming_response_list_supported_benefits(self, client: Finch) -> None: @@ -196,13 +196,15 @@ def test_streaming_response_list_supported_benefits(self, client: Finch) -> None assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() - assert_matches_type(SyncSinglePage[BenefitListSupportedBenefitsResponse], benefit, path=["response"]) + assert_matches_type(SyncSinglePage[SupportedBenefit], benefit, path=["response"]) assert cast(Any, response.is_closed) is True class TestAsyncBenefits: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: @@ -223,7 +225,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> }, description="description", frequency="one_time", - type="401k", + type="457", ) assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) @@ -359,7 +361,7 @@ async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: @parametrize async def test_method_list_supported_benefits(self, async_client: AsyncFinch) -> None: benefit = await async_client.hris.benefits.list_supported_benefits() - assert_matches_type(AsyncSinglePage[BenefitListSupportedBenefitsResponse], benefit, path=["response"]) + assert_matches_type(AsyncSinglePage[SupportedBenefit], benefit, path=["response"]) @parametrize async def test_raw_response_list_supported_benefits(self, async_client: AsyncFinch) -> None: @@ -368,7 +370,7 @@ async def test_raw_response_list_supported_benefits(self, async_client: AsyncFin assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() - assert_matches_type(AsyncSinglePage[BenefitListSupportedBenefitsResponse], benefit, path=["response"]) + assert_matches_type(AsyncSinglePage[SupportedBenefit], benefit, path=["response"]) @parametrize async def test_streaming_response_list_supported_benefits(self, async_client: AsyncFinch) -> None: @@ -377,6 +379,6 @@ async def test_streaming_response_list_supported_benefits(self, async_client: As assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = await response.parse() - assert_matches_type(AsyncSinglePage[BenefitListSupportedBenefitsResponse], benefit, path=["response"]) + assert_matches_type(AsyncSinglePage[SupportedBenefit], benefit, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/hris/test_company.py b/tests/api_resources/hris/test_company.py index 8ea9dba0..fb206fda 100644 --- a/tests/api_resources/hris/test_company.py +++ b/tests/api_resources/hris/test_company.py @@ -44,7 +44,9 @@ def test_streaming_response_retrieve(self, client: Finch) -> None: class TestAsyncCompany: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/test_directory.py b/tests/api_resources/hris/test_directory.py index 5822d710..37505168 100644 --- a/tests/api_resources/hris/test_directory.py +++ b/tests/api_resources/hris/test_directory.py @@ -94,7 +94,9 @@ def test_streaming_response_list_individuals(self, client: Finch) -> None: class TestAsyncDirectory: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/test_documents.py b/tests/api_resources/hris/test_documents.py index 7833d4f3..2b9d7077 100644 --- a/tests/api_resources/hris/test_documents.py +++ b/tests/api_resources/hris/test_documents.py @@ -92,7 +92,9 @@ def test_path_params_retreive(self, client: Finch) -> None: class TestAsyncDocuments: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/test_employments.py b/tests/api_resources/hris/test_employments.py index 6ce52e42..f6514da2 100644 --- a/tests/api_resources/hris/test_employments.py +++ b/tests/api_resources/hris/test_employments.py @@ -51,7 +51,9 @@ def test_streaming_response_retrieve_many(self, client: Finch) -> None: class TestAsyncEmployments: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve_many(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/test_individuals.py b/tests/api_resources/hris/test_individuals.py index 99a6f081..9b16cad2 100644 --- a/tests/api_resources/hris/test_individuals.py +++ b/tests/api_resources/hris/test_individuals.py @@ -53,7 +53,9 @@ def test_streaming_response_retrieve_many(self, client: Finch) -> None: class TestAsyncIndividuals: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve_many(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/test_pay_statements.py b/tests/api_resources/hris/test_pay_statements.py index 961bdba3..1631e42c 100644 --- a/tests/api_resources/hris/test_pay_statements.py +++ b/tests/api_resources/hris/test_pay_statements.py @@ -51,7 +51,9 @@ def test_streaming_response_retrieve_many(self, client: Finch) -> None: class TestAsyncPayStatements: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve_many(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/hris/test_payments.py b/tests/api_resources/hris/test_payments.py index 70b183ea..8a2ba820 100644 --- a/tests/api_resources/hris/test_payments.py +++ b/tests/api_resources/hris/test_payments.py @@ -55,7 +55,9 @@ def test_streaming_response_list(self, client: Finch) -> None: class TestAsyncPayments: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/jobs/test_automated.py b/tests/api_resources/jobs/test_automated.py index c0dc5e49..746efc27 100644 --- a/tests/api_resources/jobs/test_automated.py +++ b/tests/api_resources/jobs/test_automated.py @@ -159,7 +159,9 @@ def test_streaming_response_list(self, client: Finch) -> None: class TestAsyncAutomated: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create_overload_1(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/jobs/test_manual.py b/tests/api_resources/jobs/test_manual.py index df63695c..1cd30215 100644 --- a/tests/api_resources/jobs/test_manual.py +++ b/tests/api_resources/jobs/test_manual.py @@ -57,7 +57,9 @@ def test_path_params_retrieve(self, client: Finch) -> None: class TestAsyncManual: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/payroll/test_pay_groups.py b/tests/api_resources/payroll/test_pay_groups.py index 2800c44f..ebf7dbd4 100644 --- a/tests/api_resources/payroll/test_pay_groups.py +++ b/tests/api_resources/payroll/test_pay_groups.py @@ -91,7 +91,9 @@ def test_streaming_response_list(self, client: Finch) -> None: class TestAsyncPayGroups: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/sandbox/connections/test_accounts.py b/tests/api_resources/sandbox/connections/test_accounts.py index 5ff83d69..c10e33a2 100644 --- a/tests/api_resources/sandbox/connections/test_accounts.py +++ b/tests/api_resources/sandbox/connections/test_accounts.py @@ -20,6 +20,7 @@ class TestAccounts: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_create(self, client: Finch) -> None: account = client.sandbox.connections.accounts.create( @@ -28,6 +29,7 @@ def test_method_create(self, client: Finch) -> None: ) assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_create_with_all_params(self, client: Finch) -> None: account = client.sandbox.connections.accounts.create( @@ -38,6 +40,7 @@ def test_method_create_with_all_params(self, client: Finch) -> None: ) assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_raw_response_create(self, client: Finch) -> None: response = client.sandbox.connections.accounts.with_raw_response.create( @@ -50,6 +53,7 @@ def test_raw_response_create(self, client: Finch) -> None: account = response.parse() assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_streaming_response_create(self, client: Finch) -> None: with client.sandbox.connections.accounts.with_streaming_response.create( @@ -98,8 +102,11 @@ def test_streaming_response_update(self, client: Finch) -> None: class TestAsyncAccounts: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: account = await async_client.sandbox.connections.accounts.create( @@ -108,6 +115,7 @@ async def test_method_create(self, async_client: AsyncFinch) -> None: ) assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: account = await async_client.sandbox.connections.accounts.create( @@ -118,6 +126,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> ) assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_raw_response_create(self, async_client: AsyncFinch) -> None: response = await async_client.sandbox.connections.accounts.with_raw_response.create( @@ -130,6 +139,7 @@ async def test_raw_response_create(self, async_client: AsyncFinch) -> None: account = response.parse() assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: async with async_client.sandbox.connections.accounts.with_streaming_response.create( diff --git a/tests/api_resources/sandbox/jobs/test_configuration.py b/tests/api_resources/sandbox/jobs/test_configuration.py index b7d8ac2c..c2f21d0f 100644 --- a/tests/api_resources/sandbox/jobs/test_configuration.py +++ b/tests/api_resources/sandbox/jobs/test_configuration.py @@ -78,7 +78,9 @@ def test_streaming_response_update(self, client: Finch) -> None: class TestAsyncConfiguration: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_retrieve(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/sandbox/test_company.py b/tests/api_resources/sandbox/test_company.py index 12b02136..5485bf01 100644 --- a/tests/api_resources/sandbox/test_company.py +++ b/tests/api_resources/sandbox/test_company.py @@ -139,7 +139,9 @@ def test_streaming_response_update(self, client: Finch) -> None: class TestAsyncCompany: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_update(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/sandbox/test_connections.py b/tests/api_resources/sandbox/test_connections.py index 1a31e49b..d240af9f 100644 --- a/tests/api_resources/sandbox/test_connections.py +++ b/tests/api_resources/sandbox/test_connections.py @@ -17,6 +17,7 @@ class TestConnections: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_create(self, client: Finch) -> None: connection = client.sandbox.connections.create( @@ -24,6 +25,7 @@ def test_method_create(self, client: Finch) -> None: ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_method_create_with_all_params(self, client: Finch) -> None: connection = client.sandbox.connections.create( @@ -34,6 +36,7 @@ def test_method_create_with_all_params(self, client: Finch) -> None: ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_raw_response_create(self, client: Finch) -> None: response = client.sandbox.connections.with_raw_response.create( @@ -45,6 +48,7 @@ def test_raw_response_create(self, client: Finch) -> None: connection = response.parse() assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize def test_streaming_response_create(self, client: Finch) -> None: with client.sandbox.connections.with_streaming_response.create( @@ -60,8 +64,11 @@ def test_streaming_response_create(self, client: Finch) -> None: class TestAsyncConnections: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: connection = await async_client.sandbox.connections.create( @@ -69,6 +76,7 @@ async def test_method_create(self, async_client: AsyncFinch) -> None: ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: connection = await async_client.sandbox.connections.create( @@ -79,6 +87,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_raw_response_create(self, async_client: AsyncFinch) -> None: response = await async_client.sandbox.connections.with_raw_response.create( @@ -90,6 +99,7 @@ async def test_raw_response_create(self, async_client: AsyncFinch) -> None: connection = response.parse() assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + @pytest.mark.skip(reason="prism tests are broken") @parametrize async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: async with async_client.sandbox.connections.with_streaming_response.create( diff --git a/tests/api_resources/sandbox/test_directory.py b/tests/api_resources/sandbox/test_directory.py index 20154dec..ffc78bec 100644 --- a/tests/api_resources/sandbox/test_directory.py +++ b/tests/api_resources/sandbox/test_directory.py @@ -9,6 +9,7 @@ from finch import Finch, AsyncFinch from tests.utils import assert_matches_type +from finch._utils import parse_date from finch.types.sandbox import DirectoryCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -55,14 +56,14 @@ def test_method_create_with_all_params(self, client: Finch) -> None: "income": { "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", }, "income_history": [ { "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", } ], @@ -129,7 +130,9 @@ def test_streaming_response_create(self, client: Finch) -> None: class TestAsyncDirectory: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: @@ -169,14 +172,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> "income": { "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", }, "income_history": [ { "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", } ], diff --git a/tests/api_resources/sandbox/test_employment.py b/tests/api_resources/sandbox/test_employment.py index 40e5ffc5..c9aecbc0 100644 --- a/tests/api_resources/sandbox/test_employment.py +++ b/tests/api_resources/sandbox/test_employment.py @@ -9,6 +9,7 @@ from finch import Finch, AsyncFinch from tests.utils import assert_matches_type +from finch._utils import parse_date from finch.types.sandbox import EmploymentUpdateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -46,14 +47,14 @@ def test_method_update_with_all_params(self, client: Finch) -> None: income={ "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", }, income_history=[ { "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", } ], @@ -111,7 +112,9 @@ def test_path_params_update(self, client: Finch) -> None: class TestAsyncEmployment: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_update(self, async_client: AsyncFinch) -> None: @@ -142,14 +145,14 @@ async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> income={ "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", }, income_history=[ { "amount": 0, "currency": "currency", - "effective_date": "effective_date", + "effective_date": parse_date("2019-12-27"), "unit": "yearly", } ], diff --git a/tests/api_resources/sandbox/test_individual.py b/tests/api_resources/sandbox/test_individual.py index 31b04012..524a08a3 100644 --- a/tests/api_resources/sandbox/test_individual.py +++ b/tests/api_resources/sandbox/test_individual.py @@ -95,7 +95,9 @@ def test_path_params_update(self, client: Finch) -> None: class TestAsyncIndividual: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_update(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/sandbox/test_jobs.py b/tests/api_resources/sandbox/test_jobs.py index fd990987..b8fa4c0a 100644 --- a/tests/api_resources/sandbox/test_jobs.py +++ b/tests/api_resources/sandbox/test_jobs.py @@ -50,7 +50,9 @@ def test_streaming_response_create(self, client: Finch) -> None: class TestAsyncJobs: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/sandbox/test_payment.py b/tests/api_resources/sandbox/test_payment.py index 49482a0f..9b563814 100644 --- a/tests/api_resources/sandbox/test_payment.py +++ b/tests/api_resources/sandbox/test_payment.py @@ -9,6 +9,7 @@ from finch import Finch, AsyncFinch from tests.utils import assert_matches_type +from finch._utils import parse_date from finch.types.sandbox import PaymentCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -25,63 +26,49 @@ def test_method_create(self, client: Finch) -> None: @parametrize def test_method_create_with_all_params(self, client: Finch) -> None: payment = client.sandbox.payment.create( - end_date="end_date", + end_date=parse_date("2019-12-27"), pay_statements=[ { + "individual_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", "earnings": [ { "amount": 0, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "currency", "hours": 0, - "name": "name", - "type": "salary", + "name": "x", + "type": "bonus", } ], "employee_deductions": [ { - "amount": 2000, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "usd", - "name": "401k test", + "amount": 0, + "name": "x", "pre_tax": True, - "type": "401k", + "type": "457", } ], "employer_contributions": [ { "amount": 0, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "currency", - "name": "name", - "type": "401k", + "name": "x", + "type": "457", } ], - "gross_pay": { - "amount": 0, - "currency": "currency", - }, - "individual_id": "b2338cfb-472f-4f72-9faa-e028c083144a", - "net_pay": { - "amount": 0, - "currency": "currency", - }, + "gross_pay": 1, + "net_pay": 9007199254740991, "payment_method": "check", "taxes": [ { "amount": 0, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "currency", "employer": True, - "name": "name", - "type": "state", + "name": "x", + "type": "federal", } ], - "total_hours": 0, - "type": "regular_payroll", + "total_hours": 1, + "type": "off_cycle_payroll", } ], - start_date="start_date", + start_date=parse_date("2019-12-27"), ) assert_matches_type(PaymentCreateResponse, payment, path=["response"]) @@ -107,7 +94,9 @@ def test_streaming_response_create(self, client: Finch) -> None: class TestAsyncPayment: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncFinch) -> None: @@ -117,63 +106,49 @@ async def test_method_create(self, async_client: AsyncFinch) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: payment = await async_client.sandbox.payment.create( - end_date="end_date", + end_date=parse_date("2019-12-27"), pay_statements=[ { + "individual_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", "earnings": [ { "amount": 0, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "currency", "hours": 0, - "name": "name", - "type": "salary", + "name": "x", + "type": "bonus", } ], "employee_deductions": [ { - "amount": 2000, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "usd", - "name": "401k test", + "amount": 0, + "name": "x", "pre_tax": True, - "type": "401k", + "type": "457", } ], "employer_contributions": [ { "amount": 0, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "currency", - "name": "name", - "type": "401k", + "name": "x", + "type": "457", } ], - "gross_pay": { - "amount": 0, - "currency": "currency", - }, - "individual_id": "b2338cfb-472f-4f72-9faa-e028c083144a", - "net_pay": { - "amount": 0, - "currency": "currency", - }, + "gross_pay": 1, + "net_pay": 9007199254740991, "payment_method": "check", "taxes": [ { "amount": 0, - "attributes": {"metadata": {"metadata": {"foo": {}}}}, - "currency": "currency", "employer": True, - "name": "name", - "type": "state", + "name": "x", + "type": "federal", } ], - "total_hours": 0, - "type": "regular_payroll", + "total_hours": 1, + "type": "off_cycle_payroll", } ], - start_date="start_date", + start_date=parse_date("2019-12-27"), ) assert_matches_type(PaymentCreateResponse, payment, path=["response"]) diff --git a/tests/api_resources/test_account.py b/tests/api_resources/test_account.py index 2464a527..9280254f 100644 --- a/tests/api_resources/test_account.py +++ b/tests/api_resources/test_account.py @@ -69,7 +69,9 @@ def test_streaming_response_introspect(self, client: Finch) -> None: class TestAsyncAccount: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_disconnect(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/test_providers.py b/tests/api_resources/test_providers.py index 380fdf9c..7228af21 100644 --- a/tests/api_resources/test_providers.py +++ b/tests/api_resources/test_providers.py @@ -45,7 +45,9 @@ def test_streaming_response_list(self, client: Finch) -> None: class TestAsyncProviders: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_list(self, async_client: AsyncFinch) -> None: diff --git a/tests/api_resources/test_request_forwarding.py b/tests/api_resources/test_request_forwarding.py index ea0c6319..be7ec45f 100644 --- a/tests/api_resources/test_request_forwarding.py +++ b/tests/api_resources/test_request_forwarding.py @@ -67,7 +67,9 @@ def test_streaming_response_forward(self, client: Finch) -> None: class TestAsyncRequestForwarding: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_forward(self, async_client: AsyncFinch) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 2aaf6585..6631269d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from finch import Finch, AsyncFinch +from finch import Finch, AsyncFinch, DefaultAioHttpClient +from finch._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -25,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -43,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[Finch]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncFinch]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncFinch( + base_url=base_url, access_token=access_token, _strict_response_validation=strict, http_client=http_client + ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 309fcbac..24a87f3f 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -24,8 +24,15 @@ from finch import Finch, AsyncFinch, APIResponseValidationError from finch._types import Omit from finch._models import BaseModel, FinalRequestOptions -from finch._exceptions import APIResponseValidationError -from finch._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options +from finch._exceptions import APIStatusError, APIResponseValidationError +from finch._base_client import ( + DEFAULT_TIMEOUT, + HTTPX_DEFAULT_TIMEOUT, + BaseClient, + DefaultHttpxClient, + DefaultAsyncHttpxClient, + make_request_options, +) from .utils import update_env @@ -179,6 +186,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -829,6 +837,55 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" + class TestAsyncFinch: client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) @@ -968,6 +1025,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -1672,3 +1730,52 @@ async def test_main() -> None: raise AssertionError("calling get_platform using asyncify resulted in a hung process") time.sleep(0.1) + + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"