diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml deleted file mode 100644 index 1f1c959f..00000000 --- a/.github/workflows/create-releases.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Create releases -on: - schedule: - - cron: '0 5 * * *' # every day at 5am UTC - push: - branches: - - main - -jobs: - release: - name: release - if: github.ref == 'refs/heads/main' && github.repository == 'Finch-API/finch-api-python' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - uses: stainless-api/trigger-release-please@v1 - id: release - with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} - - - name: Install Rye - if: ${{ steps.release.outputs.releases_created }} - run: | - curl -sSf https://rye-up.com/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: 0.15.2 - RYE_INSTALL_OPTION: "--yes" - - - name: Publish to PyPI - if: ${{ steps.release.outputs.releases_created }} - run: | - bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.FINCH_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/handle-release-pr-title-edit.yml b/.github/workflows/handle-release-pr-title-edit.yml deleted file mode 100644 index 9a2f3c6e..00000000 --- a/.github/workflows/handle-release-pr-title-edit.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Handle release PR title edits -on: - pull_request: - types: - - edited - - unlabeled - -jobs: - update_pr_content: - name: Update pull request content - if: | - ((github.event.action == 'edited' && github.event.changes.title.from != github.event.pull_request.title) || - (github.event.action == 'unlabeled' && github.event.label.name == 'autorelease: custom version')) && - startsWith(github.event.pull_request.head.ref, 'release-please--') && - github.event.pull_request.state == 'open' && - github.event.sender.login != 'stainless-bot' && - github.repository == 'Finch-API/finch-api-python' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: stainless-api/trigger-release-please@v1 - with: - repo: ${{ github.event.repository.full_name }} - stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 2963b321..121b037d 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,9 +1,13 @@ -# workflow for re-running publishing to PyPI in case it fails for some reason -# you can run this workflow by navigating to https://www.github.com/Finch-API/finch-api-python/actions/workflows/publish-pypi.yml +# This workflow is triggered when a GitHub release is created. +# It can also be run manually to re-publish to PyPI in case it failed for some reason. +# You can run this workflow by navigating to https://www.github.com/Finch-API/finch-api-python/actions/workflows/publish-pypi.yml name: Publish PyPI on: workflow_dispatch: + release: + types: [published] + jobs: publish: name: publish diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 3db5f551..1bed94ad 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -16,5 +16,4 @@ jobs: run: | bash ./bin/check-release-environment env: - STAINLESS_API_KEY: ${{ secrets.STAINLESS_API_KEY }} PYPI_TOKEN: ${{ secrets.FINCH_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a7130553..d52d2b97 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.12.0" + ".": "0.13.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1849642f..410d0781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 0.13.0 (2024-01-30) + +Full Changelog: [v0.12.0...v0.13.0](https://github.com/Finch-API/finch-api-python/compare/v0.12.0...v0.13.0) + +### Features + +* **api:** add employer_contributions field ([#270](https://github.com/Finch-API/finch-api-python/issues/270)) ([f1d513b](https://github.com/Finch-API/finch-api-python/commit/f1d513bb0883aca5ced29e4a59b44542abb680ad)) +* **client:** add support for streaming raw responses ([#261](https://github.com/Finch-API/finch-api-python/issues/261)) ([60bc727](https://github.com/Finch-API/finch-api-python/commit/60bc727188ed804cb48bfc2fc24d26da4d79a27c)) +* **client:** enable follow redirects by default ([#273](https://github.com/Finch-API/finch-api-python/issues/273)) ([72b922e](https://github.com/Finch-API/finch-api-python/commit/72b922ea3c73eb7a6d12b6634a6b7b265be4f692)) +* **client:** support parsing custom response types ([#277](https://github.com/Finch-API/finch-api-python/issues/277)) ([c36ff77](https://github.com/Finch-API/finch-api-python/commit/c36ff776522876c4688a205869a3e22e7aa6c976)) + + +### Bug Fixes + +* **api:** fix authentication_type enum ([#276](https://github.com/Finch-API/finch-api-python/issues/276)) ([85c0e90](https://github.com/Finch-API/finch-api-python/commit/85c0e90321b1f30868476fd2f4ae368cde5135bc)) +* **api:** update `employer_size` parameter to `employee_size` ([#278](https://github.com/Finch-API/finch-api-python/issues/278)) ([526eeab](https://github.com/Finch-API/finch-api-python/commit/526eeab21a6101d0e96f7cdabec0ce3edf621812)) +* **client:** ensure path params are non-empty ([#263](https://github.com/Finch-API/finch-api-python/issues/263)) ([c969f5b](https://github.com/Finch-API/finch-api-python/commit/c969f5b39878d410514728489a19725eba9fefaa)) + + +### Chores + +* add write_to_file binary helper method ([#265](https://github.com/Finch-API/finch-api-python/issues/265)) ([152fafc](https://github.com/Finch-API/finch-api-python/commit/152fafc6f7931407bd9669b1cca01b50ab381624)) +* **ci:** rely on Stainless GitHub App for releases ([#269](https://github.com/Finch-API/finch-api-python/issues/269)) ([864900e](https://github.com/Finch-API/finch-api-python/commit/864900e0e66bbc997a2bbcd42b2ebd99a2f6a782)) +* **internal:** add internal helpers ([#272](https://github.com/Finch-API/finch-api-python/issues/272)) ([c377864](https://github.com/Finch-API/finch-api-python/commit/c37786436fcb348933c7b186d9e2e4cd953e1dee)) +* **internal:** enable ruff type checking misuse lint rule ([#275](https://github.com/Finch-API/finch-api-python/issues/275)) ([7c8a13b](https://github.com/Finch-API/finch-api-python/commit/7c8a13bfe250c150a9aca72748f1b3d898ac29ae)) +* **internal:** fix typing util function ([#266](https://github.com/Finch-API/finch-api-python/issues/266)) ([1c716b1](https://github.com/Finch-API/finch-api-python/commit/1c716b1c143ccaf8ae5a43a6cb98f1a1f313ae70)) +* **internal:** share client instances between all tests ([#271](https://github.com/Finch-API/finch-api-python/issues/271)) ([23c95da](https://github.com/Finch-API/finch-api-python/commit/23c95da41afbb7315c7a8f00be52f5c53c369bd6)) +* **internal:** speculative retry-after-ms support ([#267](https://github.com/Finch-API/finch-api-python/issues/267)) ([1549d50](https://github.com/Finch-API/finch-api-python/commit/1549d5007158207b9d3c399e5d0a739b10f80184)) +* **internal:** support multipart data with overlapping keys ([#274](https://github.com/Finch-API/finch-api-python/issues/274)) ([cd412ed](https://github.com/Finch-API/finch-api-python/commit/cd412ed95b3925924f31ca86fdb882005685488c)) +* **internal:** updates to proxy helper ([#264](https://github.com/Finch-API/finch-api-python/issues/264)) ([6d1df20](https://github.com/Finch-API/finch-api-python/commit/6d1df207d81021c3e11cbec9a8d8a158f178895c)) +* lazy load raw resource class properties ([#268](https://github.com/Finch-API/finch-api-python/issues/268)) ([1f75530](https://github.com/Finch-API/finch-api-python/commit/1f755305f8842d6fa47464c4d18a935d5a47ea8b)) + ## 0.12.0 (2024-01-12) Full Changelog: [v0.11.0...v0.12.0](https://github.com/Finch-API/finch-api-python/compare/v0.11.0...v0.12.0) diff --git a/README.md b/README.md index 2e258279..a4e29fb6 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,7 @@ if response.my_field is None: ### Accessing raw response data (e.g. headers) -The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call. +The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., ```py from finch import Finch @@ -308,7 +308,32 @@ directory = response.parse() # get the object that `hris.directory.list()` woul print(directory.id) ``` -These methods return an [`APIResponse`](https://github.com/Finch-API/finch-api-python/tree/main/src/finch/_response.py) object. +These methods return an [`LegacyAPIResponse`](https://github.com/Finch-API/finch-api-python/tree/main/src/finch/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. + +For the sync client this will mostly be the same with the exception +of `content` & `text` will be methods instead of properties. In the +async client, all methods will be async. + +A migration script will be provided & the migration in general should +be smooth. + +#### `.with_streaming_response` + +The above interface eagerly reads the full response body when you make the request, which may not always be what you want. + +To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. + +As such, `.with_streaming_response` methods return a different [`APIResponse`](https://github.com/Finch-API/finch-api-python/tree/main/src/finch/_response.py) object, and the async client returns an [`AsyncAPIResponse`](https://github.com/Finch-API/finch-api-python/tree/main/src/finch/_response.py) object. + +```python +with client.hris.directory.with_streaming_response.list() as response: + print(response.headers.get("X-My-Header")) + + for line in response.iter_lines(): + print(line) +``` + +The context manager is required so that the response will reliably be closed. ### Configuring the HTTP client diff --git a/bin/check-release-environment b/bin/check-release-environment index d5d4ba97..5d113358 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,10 +2,6 @@ errors=() -if [ -z "${STAINLESS_API_KEY}" ]; then - errors+=("The STAINLESS_API_KEY secret has not been set. Please contact Stainless for an API key & set it in your organization secrets on GitHub.") -fi - 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.") fi diff --git a/pyproject.toml b/pyproject.toml index fd13bc7d..786b58db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "finch-api" -version = "0.12.0" +version = "0.13.0" description = "The official Python library for the Finch API" readme = "README.md" license = "Apache-2.0" @@ -144,6 +144,8 @@ select = [ # print statements "T201", "T203", + # misuse of typing.TYPE_CHECKING + "TCH004" ] ignore = [ # mutable defaults diff --git a/src/finch/__init__.py b/src/finch/__init__.py index 4fb56959..bdd86516 100644 --- a/src/finch/__init__.py +++ b/src/finch/__init__.py @@ -4,7 +4,9 @@ from ._types import NoneType, Transport, ProxiesTypes from ._utils import file_from_path from ._client import Finch, Client, Stream, Timeout, Transport, AsyncFinch, AsyncClient, AsyncStream, RequestOptions +from ._models import BaseModel from ._version import __title__, __version__ +from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse from ._exceptions import ( APIError, FinchError, @@ -53,6 +55,7 @@ "Finch", "AsyncFinch", "file_from_path", + "BaseModel", ] _setup_logging() diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py index c2c2db5f..d7e5127d 100644 --- a/src/finch/_base_client.py +++ b/src/finch/_base_client.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os import json import time import uuid @@ -31,7 +30,7 @@ overload, ) from functools import lru_cache -from typing_extensions import Literal, override +from typing_extensions import Literal, override, get_origin import anyio import httpx @@ -61,18 +60,24 @@ AsyncTransport, RequestOptions, ModelBuilderProtocol, - BinaryResponseContent, ) -from ._utils import is_dict, is_given, is_mapping +from ._utils import is_dict, is_list, is_given, is_mapping from ._compat import model_copy, model_dump from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type -from ._response import APIResponse +from ._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + extract_response_type, +) from ._constants import ( DEFAULT_LIMITS, DEFAULT_TIMEOUT, + MAX_RETRY_DELAY, DEFAULT_MAX_RETRIES, + INITIAL_RETRY_DELAY, RAW_RESPONSE_HEADER, - STREAMED_RAW_RESPONSE_HEADER, + OVERRIDE_CAST_TO_HEADER, ) from ._streaming import Stream, AsyncStream from ._exceptions import ( @@ -81,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._legacy_response import LegacyAPIResponse log: logging.Logger = logging.getLogger(__name__) @@ -445,14 +451,18 @@ def _build_request( headers = self._build_headers(options) params = _merge_mappings(self._custom_query, options.params) + content_type = headers.get("Content-Type") # If the given Content-Type header is multipart/form-data then it # has to be removed so that httpx can generate the header with # additional information for us as it has to be in this form # for the server to be able to correctly parse the request: # multipart/form-data; boundary=---abc-- - if headers.get("Content-Type") == "multipart/form-data": - headers.pop("Content-Type") + if content_type is not None and content_type.startswith("multipart/form-data"): + if "boundary" not in content_type: + # only remove the header if the boundary hasn't been explicitly set + # as the caller doesn't want httpx to come up with their own boundary + headers.pop("Content-Type") # As we are now sending multipart/form-data instead of application/json # we need to tell httpx to use it, https://www.python-httpx.org/advanced/#multipart-file-encoding @@ -488,33 +498,46 @@ def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, o ) serialized: dict[str, object] = {} for key, value in items: - if key in serialized: - raise ValueError(f"Duplicate key encountered: {key}; This behaviour is not supported") - serialized[key] = value + existing = serialized.get(key) + + if not existing: + serialized[key] = value + continue + + # If a value has already been set for this key then that + # means we're sending data like `array[]=[1, 2, 3]` and we + # need to tell httpx that we want to send multiple values with + # the same key which is done by using a list or a tuple. + # + # Note: 2d arrays should never result in the same key at both + # levels so it's safe to assume that if the value is a list, + # it was because we changed it to be a list. + if is_list(existing): + existing.append(value) + else: + serialized[key] = [existing, value] + return serialized - def _process_response( - self, - *, - cast_to: Type[ResponseT], - options: FinalRequestOptions, - response: httpx.Response, - stream: bool, - stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, - ) -> ResponseT: - api_response = APIResponse( - raw=response, - client=self, - cast_to=cast_to, - stream=stream, - stream_cls=stream_cls, - options=options, - ) + def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]: + if not is_given(options.headers): + return cast_to - if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": - return cast(ResponseT, api_response) + # make a copy of the headers so we don't mutate user-input + headers = dict(options.headers) - return api_response.parse() + # we internally support defining a temporary header to override the + # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response` + # see _response.py for implementation details + override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, NOT_GIVEN) + if is_given(override_cast_to): + options.headers = headers + return cast(Type[ResponseT], override_cast_to) + + return cast_to + + def _should_stream_response_body(self, request: httpx.Request) -> bool: + return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return] def _process_response_data( self, @@ -540,12 +563,6 @@ def _process_response_data( except pydantic.ValidationError as err: raise APIResponseValidationError(response=response, body=data) from err - def _should_stream_response_body(self, *, request: httpx.Request) -> bool: - if request.headers.get(STREAMED_RAW_RESPONSE_HEADER) == "true": - return True - - return False - @property def qs(self) -> Querystring: return Querystring() @@ -595,6 +612,40 @@ def base_url(self, url: URL | str) -> None: def platform_headers(self) -> Dict[str, str]: return platform_headers(self._version) + def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None: + """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified. + + About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After + See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax + """ + if response_headers is None: + return None + + # First, try the non-standard `retry-after-ms` header for milliseconds, + # which is more precise than integer-seconds `retry-after` + try: + retry_ms_header = response_headers.get("retry-after-ms", None) + return float(retry_ms_header) / 1000 + except (TypeError, ValueError): + pass + + # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats). + retry_header = response_headers.get("retry-after") + try: + # note: the spec indicates that this should only ever be an integer + # but if someone sends a float there's no reason for us to not respect it + return float(retry_header) + except (TypeError, ValueError): + pass + + # Last, try parsing `retry-after` as a date. + retry_date_tuple = email.utils.parsedate_tz(retry_header) + if retry_date_tuple is None: + return None + + retry_date = email.utils.mktime_tz(retry_date_tuple) + return float(retry_date - time.time()) + def _calculate_retry_timeout( self, remaining_retries: int, @@ -602,38 +653,16 @@ def _calculate_retry_timeout( response_headers: Optional[httpx.Headers] = None, ) -> float: max_retries = options.get_max_retries(self.max_retries) - try: - # About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - # - # ". See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax for - # details. - if response_headers is not None: - retry_header = response_headers.get("retry-after") - try: - retry_after = float(retry_header) - except Exception: - retry_date_tuple = email.utils.parsedate_tz(retry_header) - if retry_date_tuple is None: - retry_after = -1 - else: - retry_date = email.utils.mktime_tz(retry_date_tuple) - retry_after = int(retry_date - time.time()) - else: - retry_after = -1 - - except Exception: - retry_after = -1 # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. - if 0 < retry_after <= 60: + retry_after = self._parse_retry_after_header(response_headers) + if retry_after is not None and 0 < retry_after <= 60: return retry_after - initial_retry_delay = 0.5 - max_retry_delay = 8.0 nb_retries = max_retries - remaining_retries # Apply exponential backoff, but not more than the max. - sleep_seconds = min(initial_retry_delay * pow(2.0, nb_retries), max_retry_delay) + sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) # Apply some jitter, plus-or-minus half a second. jitter = 1 - 0.25 * random() @@ -768,6 +797,7 @@ def __init__( proxies=proxies, transport=transport, limits=limits, + follow_redirects=True, ) def is_closed(self) -> bool: @@ -873,6 +903,7 @@ def _request( stream: bool, stream_cls: type[_StreamT] | None, ) -> ResponseT | _StreamT: + cast_to = self._maybe_override_cast_to(cast_to, options) self._prepare_options(options) retries = self._remaining_retries(remaining_retries, options) @@ -987,6 +1018,63 @@ def _retry_request( stream_cls=stream_cls, ) + def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + ) -> ResponseT: + if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": + return cast( + ResponseT, + LegacyAPIResponse( + raw=response, + client=self, + cast_to=cast_to, + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + origin = get_origin(cast_to) or cast_to + + if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if not issubclass(origin, APIResponse): + raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + ResponseT, + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = APIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return api_response.parse() + def _request_api_list( self, model: Type[object], @@ -1251,6 +1339,7 @@ def __init__( proxies=proxies, transport=transport, limits=limits, + follow_redirects=True, ) def is_closed(self) -> bool: @@ -1353,6 +1442,7 @@ async def _request( stream_cls: type[_AsyncStreamT] | None, remaining_retries: int | None, ) -> ResponseT | _AsyncStreamT: + cast_to = self._maybe_override_cast_to(cast_to, options) await self._prepare_options(options) retries = self._remaining_retries(remaining_retries, options) @@ -1428,7 +1518,7 @@ async def _request( log.debug("Re-raising status error") raise self._make_status_error_from_response(err.response) from None - return self._process_response( + return await self._process_response( cast_to=cast_to, options=options, response=response, @@ -1465,6 +1555,63 @@ async def _retry_request( stream_cls=stream_cls, ) + async def _process_response( + self, + *, + cast_to: Type[ResponseT], + options: FinalRequestOptions, + response: httpx.Response, + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + ) -> ResponseT: + if response.request.headers.get(RAW_RESPONSE_HEADER) == "true": + return cast( + ResponseT, + LegacyAPIResponse( + raw=response, + client=self, + cast_to=cast_to, + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + origin = get_origin(cast_to) or cast_to + + if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if not issubclass(origin, AsyncAPIResponse): + raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") + + response_cls = cast("type[BaseAPIResponse[Any]]", cast_to) + return cast( + "ResponseT", + response_cls( + raw=response, + client=self, + cast_to=extract_response_type(response_cls), + stream=stream, + stream_cls=stream_cls, + options=options, + ), + ) + + if cast_to == httpx.Response: + return cast(ResponseT, response) + + api_response = AsyncAPIResponse( + raw=response, + client=self, + cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast] + stream=stream, + stream_cls=stream_cls, + options=options, + ) + if bool(response.request.headers.get(RAW_RESPONSE_HEADER)): + return cast(ResponseT, api_response) + + return await api_response.parse() + def _request_api_list( self, model: Type[_T], @@ -1783,105 +1930,3 @@ def _merge_mappings( """ merged = {**obj1, **obj2} return {key: value for key, value in merged.items() if not isinstance(value, Omit)} - - -class HttpxBinaryResponseContent(BinaryResponseContent): - response: httpx.Response - - def __init__(self, response: httpx.Response) -> None: - self.response = response - - @property - @override - def content(self) -> bytes: - return self.response.content - - @property - @override - def text(self) -> str: - return self.response.text - - @property - @override - def encoding(self) -> Optional[str]: - return self.response.encoding - - @property - @override - def charset_encoding(self) -> Optional[str]: - return self.response.charset_encoding - - @override - def json(self, **kwargs: Any) -> Any: - return self.response.json(**kwargs) - - @override - def read(self) -> bytes: - return self.response.read() - - @override - def iter_bytes(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - return self.response.iter_bytes(chunk_size) - - @override - def iter_text(self, chunk_size: Optional[int] = None) -> Iterator[str]: - return self.response.iter_text(chunk_size) - - @override - def iter_lines(self) -> Iterator[str]: - return self.response.iter_lines() - - @override - def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - return self.response.iter_raw(chunk_size) - - @override - def stream_to_file( - self, - file: str | os.PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - with open(file, mode="wb") as f: - for data in self.response.iter_bytes(chunk_size): - f.write(data) - - @override - def close(self) -> None: - return self.response.close() - - @override - async def aread(self) -> bytes: - return await self.response.aread() - - @override - async def aiter_bytes(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - return self.response.aiter_bytes(chunk_size) - - @override - async def aiter_text(self, chunk_size: Optional[int] = None) -> AsyncIterator[str]: - return self.response.aiter_text(chunk_size) - - @override - async def aiter_lines(self) -> AsyncIterator[str]: - return self.response.aiter_lines() - - @override - async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - return self.response.aiter_raw(chunk_size) - - @override - async def astream_to_file( - self, - file: str | os.PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - path = anyio.Path(file) - async with await path.open(mode="wb") as f: - async for data in self.response.aiter_bytes(chunk_size): - await f.write(data) - - @override - async def aclose(self) -> None: - return await self.response.aclose() diff --git a/src/finch/_client.py b/src/finch/_client.py index a2235b30..f49ef73e 100644 --- a/src/finch/_client.py +++ b/src/finch/_client.py @@ -61,6 +61,7 @@ class Finch(SyncAPIClient): jobs: resources.Jobs sandbox: resources.Sandbox with_raw_response: FinchWithRawResponse + with_streaming_response: FinchWithStreamedResponse # client options access_token: str | None @@ -161,6 +162,7 @@ def __init__( self.jobs = resources.Jobs(self) self.sandbox = resources.Sandbox(self) self.with_raw_response = FinchWithRawResponse(self) + self.with_streaming_response = FinchWithStreamedResponse(self) @property @override @@ -411,6 +413,7 @@ class AsyncFinch(AsyncAPIClient): jobs: resources.AsyncJobs sandbox: resources.AsyncSandbox with_raw_response: AsyncFinchWithRawResponse + with_streaming_response: AsyncFinchWithStreamedResponse # client options access_token: str | None @@ -511,6 +514,7 @@ def __init__( self.jobs = resources.AsyncJobs(self) self.sandbox = resources.AsyncSandbox(self) self.with_raw_response = AsyncFinchWithRawResponse(self) + self.with_streaming_response = AsyncFinchWithStreamedResponse(self) @property @override @@ -773,6 +777,28 @@ def __init__(self, client: AsyncFinch) -> None: self.sandbox = resources.AsyncSandboxWithRawResponse(client.sandbox) +class FinchWithStreamedResponse: + def __init__(self, client: Finch) -> None: + self.access_tokens = resources.AccessTokensWithStreamingResponse(client.access_tokens) + self.hris = resources.HRISWithStreamingResponse(client.hris) + self.providers = resources.ProvidersWithStreamingResponse(client.providers) + self.account = resources.AccountWithStreamingResponse(client.account) + self.request_forwarding = resources.RequestForwardingWithStreamingResponse(client.request_forwarding) + self.jobs = resources.JobsWithStreamingResponse(client.jobs) + self.sandbox = resources.SandboxWithStreamingResponse(client.sandbox) + + +class AsyncFinchWithStreamedResponse: + def __init__(self, client: AsyncFinch) -> None: + self.access_tokens = resources.AsyncAccessTokensWithStreamingResponse(client.access_tokens) + self.hris = resources.AsyncHRISWithStreamingResponse(client.hris) + self.providers = resources.AsyncProvidersWithStreamingResponse(client.providers) + self.account = resources.AsyncAccountWithStreamingResponse(client.account) + self.request_forwarding = resources.AsyncRequestForwardingWithStreamingResponse(client.request_forwarding) + self.jobs = resources.AsyncJobsWithStreamingResponse(client.jobs) + self.sandbox = resources.AsyncSandboxWithStreamingResponse(client.sandbox) + + Client = Finch AsyncClient = AsyncFinch diff --git a/src/finch/_compat.py b/src/finch/_compat.py index 3cda3990..74c7639b 100644 --- a/src/finch/_compat.py +++ b/src/finch/_compat.py @@ -1,13 +1,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Union, TypeVar, cast +from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload from datetime import date, datetime +from typing_extensions import Self import pydantic from pydantic.fields import FieldInfo from ._types import StrBytesIntFloat +_T = TypeVar("_T") _ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel) # --------------- Pydantic v2 compatibility --------------- @@ -178,8 +180,43 @@ class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): # cached properties if TYPE_CHECKING: cached_property = property + + # we define a separate type (copied from typeshed) + # that represents that `cached_property` is `set`able + # at runtime, which differs from `@property`. + # + # this is a separate type as editors likely special case + # `@property` and we don't want to cause issues just to have + # more helpful internal types. + + class typed_cached_property(Generic[_T]): + func: Callable[[Any], _T] + attrname: str | None + + def __init__(self, func: Callable[[Any], _T]) -> None: + ... + + @overload + def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: + ... + + @overload + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: + ... + + def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self: + raise NotImplementedError() + + def __set_name__(self, owner: type[Any], name: str) -> None: + ... + + # __set__ is not defined at runtime, but @cached_property is designed to be settable + def __set__(self, instance: object, value: _T) -> None: + ... else: try: from functools import cached_property as cached_property except ImportError: from cached_property import cached_property as cached_property + + typed_cached_property = cached_property diff --git a/src/finch/_constants.py b/src/finch/_constants.py index 39b46eb0..bf15141a 100644 --- a/src/finch/_constants.py +++ b/src/finch/_constants.py @@ -3,9 +3,12 @@ import httpx RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response" -STREAMED_RAW_RESPONSE_HEADER = "X-Stainless-Streamed-Raw-Response" +OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to" # default timeout is 1 minute DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0) DEFAULT_MAX_RETRIES = 2 DEFAULT_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) + +INITIAL_RETRY_DELAY = 0.5 +MAX_RETRY_DELAY = 8.0 diff --git a/src/finch/_legacy_response.py b/src/finch/_legacy_response.py new file mode 100644 index 00000000..5b170cc4 --- /dev/null +++ b/src/finch/_legacy_response.py @@ -0,0 +1,439 @@ +from __future__ import annotations + +import os +import inspect +import logging +import datetime +import functools +from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, Iterator, AsyncIterator, cast, overload +from typing_extensions import Awaitable, ParamSpec, override, deprecated, get_origin + +import anyio +import httpx +import pydantic + +from ._types import NoneType +from ._utils import is_given +from ._models import BaseModel, is_basemodel +from ._constants import RAW_RESPONSE_HEADER +from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type +from ._exceptions import APIResponseValidationError + +if TYPE_CHECKING: + from ._models import FinalRequestOptions + from ._base_client import BaseClient + + +P = ParamSpec("P") +R = TypeVar("R") +_T = TypeVar("_T") + +log: logging.Logger = logging.getLogger(__name__) + + +class LegacyAPIResponse(Generic[R]): + """This is a legacy class as it will be replaced by `APIResponse` + and `AsyncAPIResponse` in the `_response.py` file in the next major + release. + + For the sync client this will mostly be the same with the exception + of `content` & `text` will be methods instead of properties. In the + async client, all methods will be async. + + A migration script will be provided & the migration in general should + be smooth. + """ + + _cast_to: type[R] + _client: BaseClient[Any, Any] + _parsed_by_type: dict[type[Any], Any] + _stream: bool + _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None + _options: FinalRequestOptions + + http_response: httpx.Response + + def __init__( + self, + *, + raw: httpx.Response, + cast_to: type[R], + client: BaseClient[Any, Any], + stream: bool, + stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None, + options: FinalRequestOptions, + ) -> None: + self._cast_to = cast_to + self._client = client + self._parsed_by_type = {} + self._stream = stream + self._stream_cls = stream_cls + self._options = options + self.http_response = raw + + @overload + def parse(self, *, to: type[_T]) -> _T: + ... + + @overload + def parse(self) -> R: + ... + + def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + NOTE: For the async client: this will become a coroutine in the next major version. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from finch import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + @property + def headers(self) -> httpx.Headers: + return self.http_response.headers + + @property + def http_request(self) -> httpx.Request: + return self.http_response.request + + @property + def status_code(self) -> int: + return self.http_response.status_code + + @property + def url(self) -> httpx.URL: + return self.http_response.url + + @property + def method(self) -> str: + return self.http_request.method + + @property + def content(self) -> bytes: + """Return the binary response content. + + NOTE: this will be removed in favour of `.read()` in the + next major version. + """ + return self.http_response.content + + @property + def text(self) -> str: + """Return the decoded response content. + + NOTE: this will be turned into a method in the next major version. + """ + return self.http_response.text + + @property + def http_version(self) -> str: + return self.http_response.http_version + + @property + def is_closed(self) -> bool: + return self.http_response.is_closed + + @property + def elapsed(self) -> datetime.timedelta: + """The time taken for the complete request/response cycle to complete.""" + return self.http_response.elapsed + + def _parse(self, *, to: type[_T] | None = None) -> R | _T: + if self._stream: + if to: + if not is_stream_class_type(to): + raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}") + + return cast( + _T, + to( + cast_to=extract_stream_chunk_type( + to, + failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]", + ), + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + if self._stream_cls: + return cast( + R, + self._stream_cls( + cast_to=extract_stream_chunk_type(self._stream_cls), + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls) + if stream_cls is None: + raise MissingStreamClassError() + + return cast( + R, + stream_cls( + cast_to=self._cast_to, + response=self.http_response, + client=cast(Any, self._client), + ), + ) + + cast_to = to if to is not None else self._cast_to + if cast_to is NoneType: + return cast(R, None) + + response = self.http_response + if cast_to == str: + return cast(R, response.text) + + origin = get_origin(cast_to) or cast_to + + if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent): + return cast(R, cast_to(response)) # type: ignore + + if origin == LegacyAPIResponse: + raise RuntimeError("Unexpected state - cast_to is `APIResponse`") + + if inspect.isclass(origin) and issubclass(origin, httpx.Response): + # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response + # and pass that class to our request functions. We cannot change the variance to be either + # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct + # the response class ourselves but that is something that should be supported directly in httpx + # as it would be easy to incorrectly construct the Response object due to the multitude of arguments. + if cast_to != httpx.Response: + raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") + return cast(R, response) + + if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + raise TypeError("Pydantic models must subclass our base model type, e.g. `from finch import BaseModel`") + + if ( + cast_to is not object + and not origin is list + and not origin is dict + and not origin is Union + and not issubclass(origin, BaseModel) + ): + raise RuntimeError( + f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." + ) + + # split is required to handle cases where additional information is included + # in the response, e.g. application/json; charset=utf-8 + content_type, *_ = response.headers.get("content-type", "*").split(";") + if content_type != "application/json": + if is_basemodel(cast_to): + try: + data = response.json() + except Exception as exc: + log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc) + else: + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + if self._client._strict_response_validation: + raise APIResponseValidationError( + response=response, + message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.", + body=response.text, + ) + + # If the API responds with content that isn't JSON then we just return + # the (decoded) text without performing any parsing so that you can still + # handle the response however you need to. + return response.text # type: ignore + + data = response.json() + + return self._client._process_response_data( + data=data, + cast_to=cast_to, # type: ignore + response=response, + ) + + @override + def __repr__(self) -> str: + return f"" + + +class MissingStreamClassError(TypeError): + def __init__(self) -> None: + super().__init__( + "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `finch._streaming` for reference", + ) + + +def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, LegacyAPIResponse[R]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "true" + + kwargs["extra_headers"] = extra_headers + + return cast(LegacyAPIResponse[R], func(*args, **kwargs)) + + return wrapped + + +def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[LegacyAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + async def wrapped(*args: P.args, **kwargs: P.kwargs) -> LegacyAPIResponse[R]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "true" + + kwargs["extra_headers"] = extra_headers + + return cast(LegacyAPIResponse[R], await func(*args, **kwargs)) + + return wrapped + + +class HttpxBinaryResponseContent: + response: httpx.Response + + def __init__(self, response: httpx.Response) -> None: + self.response = response + + @property + def content(self) -> bytes: + return self.response.content + + @property + def text(self) -> str: + return self.response.text + + @property + def encoding(self) -> str | None: + return self.response.encoding + + @property + def charset_encoding(self) -> str | None: + return self.response.charset_encoding + + def json(self, **kwargs: Any) -> Any: + return self.response.json(**kwargs) + + def read(self) -> bytes: + return self.response.read() + + def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: + return self.response.iter_bytes(chunk_size) + + def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: + return self.response.iter_text(chunk_size) + + def iter_lines(self) -> Iterator[str]: + return self.response.iter_lines() + + def iter_raw(self, chunk_size: int | None = None) -> Iterator[bytes]: + return self.response.iter_raw(chunk_size) + + def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `client.with_streaming_response.foo().stream_to_file('my_filename.txt')` + """ + with open(file, mode="wb") as f: + for data in self.response.iter_bytes(): + f.write(data) + + @deprecated( + "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead" + ) + def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + with open(file, mode="wb") as f: + for data in self.response.iter_bytes(chunk_size): + f.write(data) + + def close(self) -> None: + return self.response.close() + + async def aread(self) -> bytes: + return await self.response.aread() + + async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + return self.response.aiter_bytes(chunk_size) + + async def aiter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: + return self.response.aiter_text(chunk_size) + + async def aiter_lines(self) -> AsyncIterator[str]: + return self.response.aiter_lines() + + async def aiter_raw(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + return self.response.aiter_raw(chunk_size) + + @deprecated( + "Due to a bug, this method doesn't actually stream the response content, `.with_streaming_response.method()` should be used instead" + ) + async def astream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.response.aiter_bytes(chunk_size): + await f.write(data) + + async def aclose(self) -> None: + return await self.response.aclose() diff --git a/src/finch/_response.py b/src/finch/_response.py index 36af2199..967dcfc7 100644 --- a/src/finch/_response.py +++ b/src/finch/_response.py @@ -1,36 +1,55 @@ from __future__ import annotations +import os import inspect import logging import datetime import functools -from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Union, + Generic, + TypeVar, + Callable, + Iterator, + AsyncIterator, + cast, + overload, +) from typing_extensions import Awaitable, ParamSpec, override, get_origin +import anyio import httpx +import pydantic -from ._types import NoneType, BinaryResponseContent +from ._types import NoneType from ._utils import is_given, extract_type_var_from_base from ._models import BaseModel, is_basemodel -from ._constants import RAW_RESPONSE_HEADER -from ._exceptions import APIResponseValidationError +from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER +from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type +from ._exceptions import FinchError, APIResponseValidationError if TYPE_CHECKING: from ._models import FinalRequestOptions - from ._base_client import Stream, BaseClient, AsyncStream + from ._base_client import BaseClient P = ParamSpec("P") R = TypeVar("R") +_T = TypeVar("_T") +_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]") +_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]") log: logging.Logger = logging.getLogger(__name__) -class APIResponse(Generic[R]): +class BaseAPIResponse(Generic[R]): _cast_to: type[R] _client: BaseClient[Any, Any] - _parsed: R | None - _stream: bool + _parsed_by_type: dict[type[Any], Any] + _is_sse_stream: bool _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None _options: FinalRequestOptions @@ -48,29 +67,19 @@ def __init__( ) -> None: self._cast_to = cast_to self._client = client - self._parsed = None - self._stream = stream + self._parsed_by_type = {} + self._is_sse_stream = stream self._stream_cls = stream_cls self._options = options self.http_response = raw - def parse(self) -> R: - if self._parsed is not None: - return self._parsed - - parsed = self._parse() - if is_given(self._options.post_parser): - parsed = self._options.post_parser(parsed) - - self._parsed = parsed - return parsed - @property def headers(self) -> httpx.Headers: return self.http_response.headers @property def http_request(self) -> httpx.Request: + """Returns the httpx Request instance associated with the current response.""" return self.http_response.request @property @@ -79,20 +88,13 @@ def status_code(self) -> int: @property def url(self) -> httpx.URL: + """Returns the URL for which the request was made.""" return self.http_response.url @property def method(self) -> str: return self.http_request.method - @property - def content(self) -> bytes: - return self.http_response.content - - @property - def text(self) -> str: - return self.http_response.text - @property def http_version(self) -> str: return self.http_response.http_version @@ -102,13 +104,45 @@ def elapsed(self) -> datetime.timedelta: """The time taken for the complete request/response cycle to complete.""" return self.http_response.elapsed - def _parse(self) -> R: - if self._stream: + @property + def is_closed(self) -> bool: + """Whether or not the response body has been closed. + + If this is False then there is response data that has not been read yet. + You must either fully consume the response body or call `.close()` + before discarding the response to prevent resource leaks. + """ + return self.http_response.is_closed + + @override + def __repr__(self) -> str: + return ( + f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>" + ) + + def _parse(self, *, to: type[_T] | None = None) -> R | _T: + if self._is_sse_stream: + if to: + if not is_stream_class_type(to): + raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}") + + return cast( + _T, + to( + cast_to=extract_stream_chunk_type( + to, + failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]", + ), + response=self.http_response, + client=cast(Any, self._client), + ), + ) + if self._stream_cls: return cast( R, self._stream_cls( - cast_to=_extract_stream_chunk_type(self._stream_cls), + cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), ), @@ -127,7 +161,7 @@ def _parse(self) -> R: ), ) - cast_to = self._cast_to + cast_to = to if to is not None else self._cast_to if cast_to is NoneType: return cast(R, None) @@ -135,9 +169,13 @@ def _parse(self) -> R: if cast_to == str: return cast(R, response.text) + if cast_to == bytes: + return cast(R, response.content) + origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BinaryResponseContent): + # handle the legacy binary response case + if inspect.isclass(cast_to) and cast_to.__name__ == "HttpxBinaryResponseContent": return cast(R, cast_to(response)) # type: ignore if origin == APIResponse: @@ -153,14 +191,9 @@ def _parse(self) -> R: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - # The check here is necessary as we are subverting the the type system - # with casts as the relationship between TypeVars and Types are very strict - # which means we must return *exactly* what was input or transform it in a - # way that retains the TypeVar state. As we cannot do that in this function - # then we have to resort to using `cast`. At the time of writing, we know this - # to be safe as we have handled all the types that could be bound to the - # `ResponseT` TypeVar, however if that TypeVar is ever updated in the future, then - # this function would become unsafe but a type checker would not report an error. + if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + raise TypeError("Pydantic models must subclass our base model type, e.g. `from finch import BaseModel`") + if ( cast_to is not object and not origin is list @@ -169,12 +202,12 @@ def _parse(self) -> R: and not issubclass(origin, BaseModel) ): raise RuntimeError( - f"Invalid state, expected {cast_to} to be a subclass type of {BaseModel}, {dict}, {list} or {Union}." + f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}." ) # split is required to handle cases where additional information is included # in the response, e.g. application/json; charset=utf-8 - content_type, *_ = response.headers.get("content-type").split(";") + content_type, *_ = response.headers.get("content-type", "*").split(";") if content_type != "application/json": if is_basemodel(cast_to): try: @@ -208,9 +241,293 @@ def _parse(self) -> R: response=response, ) - @override - def __repr__(self) -> str: - return f"" + +class APIResponse(BaseAPIResponse[R]): + @overload + def parse(self, *, to: type[_T]) -> _T: + ... + + @overload + def parse(self) -> R: + ... + + def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from finch import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return self.http_response.read() + except httpx.StreamConsumed as exc: + # The default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message. + raise StreamAlreadyConsumed() from exc + + def text(self) -> str: + """Read and decode the response content into a string.""" + self.read() + return self.http_response.text + + def json(self) -> object: + """Read and decode the JSON response content.""" + self.read() + return self.http_response.json() + + def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + self.http_response.close() + + def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + for chunk in self.http_response.iter_bytes(chunk_size): + yield chunk + + def iter_text(self, chunk_size: int | None = None) -> Iterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + for chunk in self.http_response.iter_text(chunk_size): + yield chunk + + def iter_lines(self) -> Iterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + for chunk in self.http_response.iter_lines(): + yield chunk + + +class AsyncAPIResponse(BaseAPIResponse[R]): + @overload + async def parse(self, *, to: type[_T]) -> _T: + ... + + @overload + async def parse(self) -> R: + ... + + async def parse(self, *, to: type[_T] | None = None) -> R | _T: + """Returns the rich python representation of this response's data. + + For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`. + + You can customise the type that the response is parsed into through + the `to` argument, e.g. + + ```py + from finch import BaseModel + + + class MyModel(BaseModel): + foo: str + + + obj = response.parse(to=MyModel) + print(obj.foo) + ``` + + We support parsing: + - `BaseModel` + - `dict` + - `list` + - `Union` + - `str` + - `httpx.Response` + """ + cache_key = to if to is not None else self._cast_to + cached = self._parsed_by_type.get(cache_key) + if cached is not None: + return cached # type: ignore[no-any-return] + + if not self._is_sse_stream: + await self.read() + + parsed = self._parse(to=to) + if is_given(self._options.post_parser): + parsed = self._options.post_parser(parsed) + + self._parsed_by_type[cache_key] = parsed + return parsed + + async def read(self) -> bytes: + """Read and return the binary response content.""" + try: + return await self.http_response.aread() + except httpx.StreamConsumed as exc: + # the default error raised by httpx isn't very + # helpful in our case so we re-raise it with + # a different error message + raise StreamAlreadyConsumed() from exc + + async def text(self) -> str: + """Read and decode the response content into a string.""" + await self.read() + return self.http_response.text + + async def json(self) -> object: + """Read and decode the JSON response content.""" + await self.read() + return self.http_response.json() + + async def close(self) -> None: + """Close the response and release the connection. + + Automatically called if the response body is read to completion. + """ + await self.http_response.aclose() + + async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]: + """ + A byte-iterator over the decoded response content. + + This automatically handles gzip, deflate and brotli encoded responses. + """ + async for chunk in self.http_response.aiter_bytes(chunk_size): + yield chunk + + async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]: + """A str-iterator over the decoded response content + that handles both gzip, deflate, etc but also detects the content's + string encoding. + """ + async for chunk in self.http_response.aiter_text(chunk_size): + yield chunk + + async def iter_lines(self) -> AsyncIterator[str]: + """Like `iter_text()` but will only yield chunks for each line""" + async for chunk in self.http_response.aiter_lines(): + yield chunk + + +class BinaryAPIResponse(APIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(): + f.write(data) + + +class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]): + """Subclass of APIResponse providing helpers for dealing with binary data. + + Note: If you want to stream the response data instead of eagerly reading it + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + + async def write_to_file( + self, + file: str | os.PathLike[str], + ) -> None: + """Write the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + + Note: if you want to stream the data to the file instead of writing + all at once then you should use `.with_streaming_response` when making + the API request, e.g. `.with_streaming_response.get_binary_response()` + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(): + await f.write(data) + + +class StreamedBinaryAPIResponse(APIResponse[bytes]): + def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + with open(file, mode="wb") as f: + for data in self.iter_bytes(chunk_size): + f.write(data) + + +class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]): + async def stream_to_file( + self, + file: str | os.PathLike[str], + *, + chunk_size: int | None = None, + ) -> None: + """Streams the output to the given file. + + Accepts a filename or any path-like object, e.g. pathlib.Path + """ + path = anyio.Path(file) + async with await path.open(mode="wb") as f: + async for data in self.iter_bytes(chunk_size): + await f.write(data) class MissingStreamClassError(TypeError): @@ -220,14 +537,176 @@ def __init__(self) -> None: ) -def _extract_stream_chunk_type(stream_cls: type) -> type: - from ._base_client import Stream, AsyncStream +class StreamAlreadyConsumed(FinchError): + """ + Attempted to read or stream content, but the content has already + been streamed. + + This can happen if you use a method like `.iter_lines()` and then attempt + to read th entire response body afterwards, e.g. + + ```py + response = await client.post(...) + async for line in response.iter_lines(): + ... # do something with `line` + + content = await response.read() + # ^ error + ``` + + If you want this behaviour you'll need to either manually accumulate the response + content or call `await response.read()` before iterating over the stream. + """ + + def __init__(self) -> None: + message = ( + "Attempted to read or stream some content, but the content has " + "already been streamed. " + "This could be due to attempting to stream the response " + "content more than once." + "\n\n" + "You can fix this by manually accumulating the response content while streaming " + "or by calling `.read()` before starting to stream." + ) + super().__init__(message) + + +class ResponseContextManager(Generic[_APIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, request_func: Callable[[], _APIResponseT]) -> None: + self._request_func = request_func + self.__response: _APIResponseT | None = None + + def __enter__(self) -> _APIResponseT: + self.__response = self._request_func() + return self.__response + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + self.__response.close() + + +class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]): + """Context manager for ensuring that a request is not made + until it is entered and that the response will always be closed + when the context manager exits + """ + + def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None: + self._api_request = api_request + self.__response: _AsyncAPIResponseT | None = None + + async def __aenter__(self) -> _AsyncAPIResponseT: + self.__response = await self._api_request + return self.__response + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self.__response is not None: + await self.__response.close() + + +def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request)) + + return wrapped + + +def async_to_streamed_response_wrapper( + func: Callable[P, Awaitable[R]], +) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]: + """Higher order function that takes one of our bound API methods and wraps it + to support streaming and returning the raw `APIResponse` object directly. + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request)) + + return wrapped - return extract_type_var_from_base( - stream_cls, - index=0, - generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), - ) + +def to_custom_streamed_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, ResponseContextManager[_APIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = functools.partial(func, *args, **kwargs) + + return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request)) + + return wrapped + + +def async_to_custom_streamed_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support streaming and returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "stream" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + make_request = func(*args, **kwargs) + + return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request)) + + return wrapped def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]: @@ -238,7 +717,7 @@ def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]] @functools.wraps(func) def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "true" + extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers @@ -247,18 +726,82 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: return wrapped -def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[APIResponse[R]]]: +def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]: """Higher order function that takes one of our bound API methods and wraps it to support returning the raw `APIResponse` object directly. """ @functools.wraps(func) - async def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]: + async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]: extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} - extra_headers[RAW_RESPONSE_HEADER] = "true" + extra_headers[RAW_RESPONSE_HEADER] = "raw" kwargs["extra_headers"] = extra_headers - return cast(APIResponse[R], await func(*args, **kwargs)) + return cast(AsyncAPIResponse[R], await func(*args, **kwargs)) return wrapped + + +def to_custom_raw_response_wrapper( + func: Callable[P, object], + response_cls: type[_APIResponseT], +) -> Callable[P, _APIResponseT]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(_APIResponseT, func(*args, **kwargs)) + + return wrapped + + +def async_to_custom_raw_response_wrapper( + func: Callable[P, Awaitable[object]], + response_cls: type[_AsyncAPIResponseT], +) -> Callable[P, Awaitable[_AsyncAPIResponseT]]: + """Higher order function that takes one of our bound API methods and an `APIResponse` class + and wraps the method to support returning the given response class directly. + + Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])` + """ + + @functools.wraps(func) + def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]: + extra_headers = {**(cast(Any, kwargs.get("extra_headers")) or {})} + extra_headers[RAW_RESPONSE_HEADER] = "raw" + extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls + + kwargs["extra_headers"] = extra_headers + + return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs)) + + return wrapped + + +def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type: + """Given a type like `APIResponse[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyResponse(APIResponse[bytes]): + ... + + extract_response_type(MyResponse) -> bytes + ``` + """ + return extract_type_var_from_base( + typ, + generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)), + index=0, + ) diff --git a/src/finch/_streaming.py b/src/finch/_streaming.py index 7bb568dd..4dfad86a 100644 --- a/src/finch/_streaming.py +++ b/src/finch/_streaming.py @@ -2,12 +2,15 @@ from __future__ import annotations import json +import inspect from types import TracebackType from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast -from typing_extensions import Self, override +from typing_extensions import Self, TypeGuard, override, get_origin import httpx +from ._utils import extract_type_var_from_base + if TYPE_CHECKING: from ._client import Finch, AsyncFinch @@ -254,3 +257,34 @@ def decode(self, line: str) -> ServerSentEvent | None: pass # Field is ignored. return None + + +def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]: + """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`""" + origin = get_origin(typ) or typ + return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream)) + + +def extract_stream_chunk_type( + stream_cls: type, + *, + failure_message: str | None = None, +) -> type: + """Given a type like `Stream[T]`, returns the generic type variable `T`. + + This also handles the case where a concrete subclass is given, e.g. + ```py + class MyStream(Stream[bytes]): + ... + + extract_stream_chunk_type(MyStream) -> bytes + ``` + """ + from ._base_client import Stream, AsyncStream + + return extract_type_var_from_base( + stream_cls, + index=0, + generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)), + failure_message=failure_message, + ) diff --git a/src/finch/_types.py b/src/finch/_types.py index 04fa5bf8..1ccf4a5e 100644 --- a/src/finch/_types.py +++ b/src/finch/_types.py @@ -1,7 +1,6 @@ from __future__ import annotations from os import PathLike -from abc import ABC, abstractmethod from typing import ( IO, TYPE_CHECKING, @@ -14,10 +13,8 @@ Mapping, TypeVar, Callable, - Iterator, Optional, Sequence, - AsyncIterator, ) from typing_extensions import Literal, Protocol, TypeAlias, TypedDict, override, runtime_checkable @@ -27,6 +24,8 @@ if TYPE_CHECKING: from ._models import BaseModel + from ._response import APIResponse, AsyncAPIResponse + from ._legacy_response import HttpxBinaryResponseContent Transport = BaseTransport AsyncTransport = AsyncBaseTransport @@ -37,162 +36,6 @@ _T = TypeVar("_T") -class BinaryResponseContent(ABC): - @abstractmethod - def __init__( - self, - response: Any, - ) -> None: - ... - - @property - @abstractmethod - def content(self) -> bytes: - pass - - @property - @abstractmethod - def text(self) -> str: - pass - - @property - @abstractmethod - def encoding(self) -> Optional[str]: - """ - Return an encoding to use for decoding the byte content into text. - The priority for determining this is given by... - - * `.encoding = <>` has been set explicitly. - * The encoding as specified by the charset parameter in the Content-Type header. - * The encoding as determined by `default_encoding`, which may either be - a string like "utf-8" indicating the encoding to use, or may be a callable - which enables charset autodetection. - """ - pass - - @property - @abstractmethod - def charset_encoding(self) -> Optional[str]: - """ - Return the encoding, as specified by the Content-Type header. - """ - pass - - @abstractmethod - def json(self, **kwargs: Any) -> Any: - pass - - @abstractmethod - def read(self) -> bytes: - """ - Read and return the response content. - """ - pass - - @abstractmethod - def iter_bytes(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - """ - A byte-iterator over the decoded response content. - This allows us to handle gzip, deflate, and brotli encoded responses. - """ - pass - - @abstractmethod - def iter_text(self, chunk_size: Optional[int] = None) -> Iterator[str]: - """ - A str-iterator over the decoded response content - that handles both gzip, deflate, etc but also detects the content's - string encoding. - """ - pass - - @abstractmethod - def iter_lines(self) -> Iterator[str]: - pass - - @abstractmethod - def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - """ - A byte-iterator over the raw response content. - """ - pass - - @abstractmethod - def stream_to_file( - self, - file: str | PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - """ - Stream the output to the given file. - """ - pass - - @abstractmethod - def close(self) -> None: - """ - Close the response and release the connection. - Automatically called if the response body is read to completion. - """ - pass - - @abstractmethod - async def aread(self) -> bytes: - """ - Read and return the response content. - """ - pass - - @abstractmethod - async def aiter_bytes(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - """ - A byte-iterator over the decoded response content. - This allows us to handle gzip, deflate, and brotli encoded responses. - """ - pass - - @abstractmethod - async def aiter_text(self, chunk_size: Optional[int] = None) -> AsyncIterator[str]: - """ - A str-iterator over the decoded response content - that handles both gzip, deflate, etc but also detects the content's - string encoding. - """ - pass - - @abstractmethod - async def aiter_lines(self) -> AsyncIterator[str]: - pass - - @abstractmethod - async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]: - """ - A byte-iterator over the raw response content. - """ - pass - - @abstractmethod - async def astream_to_file( - self, - file: str | PathLike[str], - *, - chunk_size: int | None = None, - ) -> None: - """ - Stream the output to the given file. - """ - pass - - @abstractmethod - async def aclose(self) -> None: - """ - Close the response and release the connection. - Automatically called if the response body is read to completion. - """ - pass - - # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]] @@ -343,7 +186,9 @@ def get(self, __key: str) -> str | None: Dict[str, Any], Response, ModelBuilderProtocol, - BinaryResponseContent, + "APIResponse[Any]", + "AsyncAPIResponse[Any]", + "HttpxBinaryResponseContent", ], ) @@ -359,6 +204,7 @@ def get(self, __key: str) -> str | None: @runtime_checkable class InheritsGeneric(Protocol): """Represents a type that has inherited from `Generic` + The `__orig_bases__` property can be used to determine the resolved type variable for a given base class. """ diff --git a/src/finch/_utils/__init__.py b/src/finch/_utils/__init__.py index 2dcfc122..0fb811a9 100644 --- a/src/finch/_utils/__init__.py +++ b/src/finch/_utils/__init__.py @@ -1,3 +1,4 @@ +from ._sync import asyncify as asyncify from ._proxy import LazyProxy as LazyProxy from ._utils import ( flatten as flatten, diff --git a/src/finch/_utils/_proxy.py b/src/finch/_utils/_proxy.py index 3c9e790a..6f05efcd 100644 --- a/src/finch/_utils/_proxy.py +++ b/src/finch/_utils/_proxy.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from typing import Generic, TypeVar, Iterable, cast -from typing_extensions import ClassVar, override +from typing_extensions import override T = TypeVar("T") @@ -13,11 +13,6 @@ class LazyProxy(Generic[T], ABC): This includes forwarding attribute access and othe methods. """ - should_cache: ClassVar[bool] = False - - def __init__(self) -> None: - self.__proxied: T | None = None - # Note: we have to special case proxies that themselves return proxies # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz` @@ -57,18 +52,7 @@ def __class__(self) -> type: return proxied.__class__ def __get_proxied__(self) -> T: - if not self.should_cache: - return self.__load__() - - proxied = self.__proxied - if proxied is not None: - return proxied - - self.__proxied = proxied = self.__load__() - return proxied - - def __set_proxied__(self, value: T) -> None: - self.__proxied = value + return self.__load__() def __as_proxied__(self) -> T: """Helper method that returns the current proxy, typed as the loaded object""" diff --git a/src/finch/_utils/_sync.py b/src/finch/_utils/_sync.py new file mode 100644 index 00000000..595924e5 --- /dev/null +++ b/src/finch/_utils/_sync.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import functools +from typing import TypeVar, Callable, Awaitable +from typing_extensions import ParamSpec + +import anyio +import anyio.to_thread + +T_Retval = TypeVar("T_Retval") +T_ParamSpec = ParamSpec("T_ParamSpec") + + +# copied from `asyncer`, https://github.com/tiangolo/asyncer +def asyncify( + function: Callable[T_ParamSpec, T_Retval], + *, + cancellable: bool = False, + limiter: anyio.CapacityLimiter | None = None, +) -> Callable[T_ParamSpec, Awaitable[T_Retval]]: + """ + Take a blocking function and create an async one that receives the same + positional and keyword arguments, and that when called, calls the original function + in a worker thread using `anyio.to_thread.run_sync()`. Internally, + `asyncer.asyncify()` uses the same `anyio.to_thread.run_sync()`, but it supports + keyword arguments additional to positional arguments and it adds better support for + autocompletion and inline errors for the arguments of the function called and the + return value. + + If the `cancellable` option is enabled and the task waiting for its completion is + cancelled, the thread will still run its course but its return value (or any raised + exception) will be ignored. + + Use it like this: + + ```Python + def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str: + # Do work + return "Some result" + + + result = await to_thread.asyncify(do_work)("spam", "ham", kwarg1="a", kwarg2="b") + print(result) + ``` + + ## Arguments + + `function`: a blocking regular callable (e.g. a function) + `cancellable`: `True` to allow cancellation of the operation + `limiter`: capacity limiter to use to limit the total amount of threads running + (if omitted, the default limiter is used) + + ## Return + + An async function that takes the same positional and keyword arguments as the + original one, that when called runs the same original function in a thread worker + and returns the result. + """ + + async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval: + partial_f = functools.partial(function, *args, **kwargs) + return await anyio.to_thread.run_sync(partial_f, cancellable=cancellable, limiter=limiter) + + return wrapper diff --git a/src/finch/_utils/_typing.py b/src/finch/_utils/_typing.py index b5e2c2e3..c1d1ebb9 100644 --- a/src/finch/_utils/_typing.py +++ b/src/finch/_utils/_typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, cast +from typing import Any, TypeVar, cast from typing_extensions import Required, Annotated, get_args, get_origin from .._types import InheritsGeneric @@ -23,6 +23,12 @@ def is_required_type(typ: type) -> bool: return get_origin(typ) == Required +def is_typevar(typ: type) -> bool: + # type ignore is required because type checkers + # think this expression will always return False + return type(typ) == TypeVar # type: ignore + + # Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]] def strip_annotated_type(typ: type) -> type: if is_required_type(typ) or is_annotated_type(typ): @@ -39,7 +45,13 @@ def extract_type_arg(typ: type, index: int) -> type: raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err -def extract_type_var_from_base(typ: type, *, generic_bases: tuple[type, ...], index: int) -> type: +def extract_type_var_from_base( + typ: type, + *, + generic_bases: tuple[type, ...], + index: int, + failure_message: str | None = None, +) -> type: """Given a type like `Foo[T]`, returns the generic type variable `T`. This also handles the case where a concrete subclass is given, e.g. @@ -49,6 +61,15 @@ class MyResponse(Foo[bytes]): extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes ``` + + And where a generic subclass is given: + ```py + _T = TypeVar('_T') + class MyResponse(Foo[_T]): + ... + + extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes + ``` """ cls = cast(object, get_origin(typ) or typ) if cls in generic_bases: @@ -75,6 +96,18 @@ class MyResponse(Foo[bytes]): f"Does {cls} inherit from one of {generic_bases} ?" ) - return extract_type_arg(target_base_class, index) - - raise RuntimeError(f"Could not resolve inner type variable at index {index} for {typ}") + extracted = extract_type_arg(target_base_class, index) + if is_typevar(extracted): + # If the extracted type argument is itself a type variable + # then that means the subclass itself is generic, so we have + # to resolve the type argument from the class itself, not + # the base class. + # + # Note: if there is more than 1 type argument, the subclass could + # change the ordering of the type arguments, this is not currently + # supported. + return extract_type_arg(typ, index) + + return extracted + + raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}") diff --git a/src/finch/_version.py b/src/finch/_version.py index 418bd1f8..59b2bc6f 100644 --- a/src/finch/_version.py +++ b/src/finch/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. __title__ = "finch" -__version__ = "0.12.0" # x-release-please-version +__version__ = "0.13.0" # x-release-please-version diff --git a/src/finch/resources/__init__.py b/src/finch/resources/__init__.py index b3c215be..13ad3da5 100644 --- a/src/finch/resources/__init__.py +++ b/src/finch/resources/__init__.py @@ -1,22 +1,61 @@ # File generated from our OpenAPI spec by Stainless. -from .hris import HRIS, AsyncHRIS, HRISWithRawResponse, AsyncHRISWithRawResponse -from .jobs import Jobs, AsyncJobs, JobsWithRawResponse, AsyncJobsWithRawResponse -from .account import Account, AsyncAccount, AccountWithRawResponse, AsyncAccountWithRawResponse -from .sandbox import Sandbox, AsyncSandbox, SandboxWithRawResponse, AsyncSandboxWithRawResponse +from .hris import ( + HRIS, + AsyncHRIS, + HRISWithRawResponse, + AsyncHRISWithRawResponse, + HRISWithStreamingResponse, + AsyncHRISWithStreamingResponse, +) +from .jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) +from .account import ( + Account, + AsyncAccount, + AccountWithRawResponse, + AsyncAccountWithRawResponse, + AccountWithStreamingResponse, + AsyncAccountWithStreamingResponse, +) +from .sandbox import ( + Sandbox, + AsyncSandbox, + SandboxWithRawResponse, + AsyncSandboxWithRawResponse, + SandboxWithStreamingResponse, + AsyncSandboxWithStreamingResponse, +) from .webhooks import Webhooks, AsyncWebhooks -from .providers import Providers, AsyncProviders, ProvidersWithRawResponse, AsyncProvidersWithRawResponse +from .providers import ( + Providers, + AsyncProviders, + ProvidersWithRawResponse, + AsyncProvidersWithRawResponse, + ProvidersWithStreamingResponse, + AsyncProvidersWithStreamingResponse, +) from .access_tokens import ( AccessTokens, AsyncAccessTokens, AccessTokensWithRawResponse, AsyncAccessTokensWithRawResponse, + AccessTokensWithStreamingResponse, + AsyncAccessTokensWithStreamingResponse, ) from .request_forwarding import ( RequestForwarding, AsyncRequestForwarding, RequestForwardingWithRawResponse, AsyncRequestForwardingWithRawResponse, + RequestForwardingWithStreamingResponse, + AsyncRequestForwardingWithStreamingResponse, ) __all__ = [ @@ -24,30 +63,44 @@ "AsyncAccessTokens", "AccessTokensWithRawResponse", "AsyncAccessTokensWithRawResponse", + "AccessTokensWithStreamingResponse", + "AsyncAccessTokensWithStreamingResponse", "HRIS", "AsyncHRIS", "HRISWithRawResponse", "AsyncHRISWithRawResponse", + "HRISWithStreamingResponse", + "AsyncHRISWithStreamingResponse", "Providers", "AsyncProviders", "ProvidersWithRawResponse", "AsyncProvidersWithRawResponse", + "ProvidersWithStreamingResponse", + "AsyncProvidersWithStreamingResponse", "Account", "AsyncAccount", "AccountWithRawResponse", "AsyncAccountWithRawResponse", + "AccountWithStreamingResponse", + "AsyncAccountWithStreamingResponse", "Webhooks", "AsyncWebhooks", "RequestForwarding", "AsyncRequestForwarding", "RequestForwardingWithRawResponse", "AsyncRequestForwardingWithRawResponse", + "RequestForwardingWithStreamingResponse", + "AsyncRequestForwardingWithStreamingResponse", "Jobs", "AsyncJobs", "JobsWithRawResponse", "AsyncJobsWithRawResponse", + "JobsWithStreamingResponse", + "AsyncJobsWithStreamingResponse", "Sandbox", "AsyncSandbox", "SandboxWithRawResponse", "AsyncSandboxWithRawResponse", + "SandboxWithStreamingResponse", + "AsyncSandboxWithStreamingResponse", ] diff --git a/src/finch/resources/access_tokens.py b/src/finch/resources/access_tokens.py index 161da255..68668295 100644 --- a/src/finch/resources/access_tokens.py +++ b/src/finch/resources/access_tokens.py @@ -4,12 +4,13 @@ import httpx +from .. import _legacy_response from ..types import CreateAccessTokenResponse, access_token_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._base_client import ( make_request_options, ) @@ -22,6 +23,10 @@ class AccessTokens(SyncAPIResource): def with_raw_response(self) -> AccessTokensWithRawResponse: return AccessTokensWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AccessTokensWithStreamingResponse: + return AccessTokensWithStreamingResponse(self) + def create( self, *, @@ -71,6 +76,10 @@ class AsyncAccessTokens(AsyncAPIResource): def with_raw_response(self) -> AsyncAccessTokensWithRawResponse: return AsyncAccessTokensWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncAccessTokensWithStreamingResponse: + return AsyncAccessTokensWithStreamingResponse(self) + async def create( self, *, @@ -117,13 +126,35 @@ async def create( class AccessTokensWithRawResponse: def __init__(self, access_tokens: AccessTokens) -> None: - self.create = to_raw_response_wrapper( + self._access_tokens = access_tokens + + self.create = _legacy_response.to_raw_response_wrapper( access_tokens.create, ) class AsyncAccessTokensWithRawResponse: def __init__(self, access_tokens: AsyncAccessTokens) -> None: - self.create = async_to_raw_response_wrapper( + self._access_tokens = access_tokens + + self.create = _legacy_response.async_to_raw_response_wrapper( + access_tokens.create, + ) + + +class AccessTokensWithStreamingResponse: + def __init__(self, access_tokens: AccessTokens) -> None: + self._access_tokens = access_tokens + + self.create = to_streamed_response_wrapper( + access_tokens.create, + ) + + +class AsyncAccessTokensWithStreamingResponse: + def __init__(self, access_tokens: AsyncAccessTokens) -> None: + self._access_tokens = access_tokens + + self.create = async_to_streamed_response_wrapper( access_tokens.create, ) diff --git a/src/finch/resources/account.py b/src/finch/resources/account.py index 04f8eb14..4b69754c 100644 --- a/src/finch/resources/account.py +++ b/src/finch/resources/account.py @@ -4,11 +4,12 @@ import httpx +from .. import _legacy_response from ..types import Introspection, DisconnectResponse from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._base_client import ( make_request_options, ) @@ -21,6 +22,10 @@ class Account(SyncAPIResource): def with_raw_response(self) -> AccountWithRawResponse: return AccountWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AccountWithStreamingResponse: + return AccountWithStreamingResponse(self) + def disconnect( self, *, @@ -69,6 +74,10 @@ class AsyncAccount(AsyncAPIResource): def with_raw_response(self) -> AsyncAccountWithRawResponse: return AsyncAccountWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncAccountWithStreamingResponse: + return AsyncAccountWithStreamingResponse(self) + async def disconnect( self, *, @@ -114,19 +123,47 @@ async def introspect( class AccountWithRawResponse: def __init__(self, account: Account) -> None: - self.disconnect = to_raw_response_wrapper( + self._account = account + + self.disconnect = _legacy_response.to_raw_response_wrapper( account.disconnect, ) - self.introspect = to_raw_response_wrapper( + self.introspect = _legacy_response.to_raw_response_wrapper( account.introspect, ) class AsyncAccountWithRawResponse: def __init__(self, account: AsyncAccount) -> None: - self.disconnect = async_to_raw_response_wrapper( + self._account = account + + self.disconnect = _legacy_response.async_to_raw_response_wrapper( + account.disconnect, + ) + self.introspect = _legacy_response.async_to_raw_response_wrapper( + account.introspect, + ) + + +class AccountWithStreamingResponse: + def __init__(self, account: Account) -> None: + self._account = account + + self.disconnect = to_streamed_response_wrapper( + account.disconnect, + ) + self.introspect = to_streamed_response_wrapper( + account.introspect, + ) + + +class AsyncAccountWithStreamingResponse: + def __init__(self, account: AsyncAccount) -> None: + self._account = account + + self.disconnect = async_to_streamed_response_wrapper( account.disconnect, ) - self.introspect = async_to_raw_response_wrapper( + self.introspect = async_to_streamed_response_wrapper( account.introspect, ) diff --git a/src/finch/resources/hris/__init__.py b/src/finch/resources/hris/__init__.py index 8f292e8f..3eea83c6 100644 --- a/src/finch/resources/hris/__init__.py +++ b/src/finch/resources/hris/__init__.py @@ -1,22 +1,68 @@ # File generated from our OpenAPI spec by Stainless. -from .hris import HRIS, AsyncHRIS, HRISWithRawResponse, AsyncHRISWithRawResponse +from .hris import ( + HRIS, + AsyncHRIS, + HRISWithRawResponse, + AsyncHRISWithRawResponse, + HRISWithStreamingResponse, + AsyncHRISWithStreamingResponse, +) from .company import ( CompanyResource, AsyncCompanyResource, CompanyResourceWithRawResponse, AsyncCompanyResourceWithRawResponse, + CompanyResourceWithStreamingResponse, + AsyncCompanyResourceWithStreamingResponse, +) +from .benefits import ( + Benefits, + AsyncBenefits, + BenefitsWithRawResponse, + AsyncBenefitsWithRawResponse, + BenefitsWithStreamingResponse, + AsyncBenefitsWithStreamingResponse, +) +from .payments import ( + Payments, + AsyncPayments, + PaymentsWithRawResponse, + AsyncPaymentsWithRawResponse, + PaymentsWithStreamingResponse, + AsyncPaymentsWithStreamingResponse, +) +from .directory import ( + Directory, + AsyncDirectory, + DirectoryWithRawResponse, + AsyncDirectoryWithRawResponse, + DirectoryWithStreamingResponse, + AsyncDirectoryWithStreamingResponse, +) +from .employments import ( + Employments, + AsyncEmployments, + EmploymentsWithRawResponse, + AsyncEmploymentsWithRawResponse, + EmploymentsWithStreamingResponse, + AsyncEmploymentsWithStreamingResponse, +) +from .individuals import ( + Individuals, + AsyncIndividuals, + IndividualsWithRawResponse, + AsyncIndividualsWithRawResponse, + IndividualsWithStreamingResponse, + AsyncIndividualsWithStreamingResponse, ) -from .benefits import Benefits, AsyncBenefits, BenefitsWithRawResponse, AsyncBenefitsWithRawResponse -from .payments import Payments, AsyncPayments, PaymentsWithRawResponse, AsyncPaymentsWithRawResponse -from .directory import Directory, AsyncDirectory, DirectoryWithRawResponse, AsyncDirectoryWithRawResponse -from .employments import Employments, AsyncEmployments, EmploymentsWithRawResponse, AsyncEmploymentsWithRawResponse -from .individuals import Individuals, AsyncIndividuals, IndividualsWithRawResponse, AsyncIndividualsWithRawResponse from .pay_statements import ( PayStatements, AsyncPayStatements, PayStatementsWithRawResponse, AsyncPayStatementsWithRawResponse, + PayStatementsWithStreamingResponse, + AsyncPayStatementsWithStreamingResponse, ) __all__ = [ @@ -24,32 +70,48 @@ "AsyncCompanyResource", "CompanyResourceWithRawResponse", "AsyncCompanyResourceWithRawResponse", + "CompanyResourceWithStreamingResponse", + "AsyncCompanyResourceWithStreamingResponse", "Directory", "AsyncDirectory", "DirectoryWithRawResponse", "AsyncDirectoryWithRawResponse", + "DirectoryWithStreamingResponse", + "AsyncDirectoryWithStreamingResponse", "Individuals", "AsyncIndividuals", "IndividualsWithRawResponse", "AsyncIndividualsWithRawResponse", + "IndividualsWithStreamingResponse", + "AsyncIndividualsWithStreamingResponse", "Employments", "AsyncEmployments", "EmploymentsWithRawResponse", "AsyncEmploymentsWithRawResponse", + "EmploymentsWithStreamingResponse", + "AsyncEmploymentsWithStreamingResponse", "Payments", "AsyncPayments", "PaymentsWithRawResponse", "AsyncPaymentsWithRawResponse", + "PaymentsWithStreamingResponse", + "AsyncPaymentsWithStreamingResponse", "PayStatements", "AsyncPayStatements", "PayStatementsWithRawResponse", "AsyncPayStatementsWithRawResponse", + "PayStatementsWithStreamingResponse", + "AsyncPayStatementsWithStreamingResponse", "Benefits", "AsyncBenefits", "BenefitsWithRawResponse", "AsyncBenefitsWithRawResponse", + "BenefitsWithStreamingResponse", + "AsyncBenefitsWithStreamingResponse", "HRIS", "AsyncHRIS", "HRISWithRawResponse", "AsyncHRISWithRawResponse", + "HRISWithStreamingResponse", + "AsyncHRISWithStreamingResponse", ] diff --git a/src/finch/resources/hris/benefits/__init__.py b/src/finch/resources/hris/benefits/__init__.py index 9a5b9063..07a79df6 100644 --- a/src/finch/resources/hris/benefits/__init__.py +++ b/src/finch/resources/hris/benefits/__init__.py @@ -1,15 +1,33 @@ # File generated from our OpenAPI spec by Stainless. -from .benefits import Benefits, AsyncBenefits, BenefitsWithRawResponse, AsyncBenefitsWithRawResponse -from .individuals import Individuals, AsyncIndividuals, IndividualsWithRawResponse, AsyncIndividualsWithRawResponse +from .benefits import ( + Benefits, + AsyncBenefits, + BenefitsWithRawResponse, + AsyncBenefitsWithRawResponse, + BenefitsWithStreamingResponse, + AsyncBenefitsWithStreamingResponse, +) +from .individuals import ( + Individuals, + AsyncIndividuals, + IndividualsWithRawResponse, + AsyncIndividualsWithRawResponse, + IndividualsWithStreamingResponse, + AsyncIndividualsWithStreamingResponse, +) __all__ = [ "Individuals", "AsyncIndividuals", "IndividualsWithRawResponse", "AsyncIndividualsWithRawResponse", + "IndividualsWithStreamingResponse", + "AsyncIndividualsWithStreamingResponse", "Benefits", "AsyncBenefits", "BenefitsWithRawResponse", "AsyncBenefitsWithRawResponse", + "BenefitsWithStreamingResponse", + "AsyncBenefitsWithStreamingResponse", ] diff --git a/src/finch/resources/hris/benefits/benefits.py b/src/finch/resources/hris/benefits/benefits.py index 9d5f1555..5e712445 100644 --- a/src/finch/resources/hris/benefits/benefits.py +++ b/src/finch/resources/hris/benefits/benefits.py @@ -6,12 +6,20 @@ import httpx +from .... import _legacy_response from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from ...._compat import cached_property -from .individuals import Individuals, AsyncIndividuals, IndividualsWithRawResponse, AsyncIndividualsWithRawResponse +from .individuals import ( + Individuals, + AsyncIndividuals, + IndividualsWithRawResponse, + AsyncIndividualsWithRawResponse, + IndividualsWithStreamingResponse, + AsyncIndividualsWithStreamingResponse, +) from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ....pagination import SyncSinglePage, AsyncSinglePage from ....types.hris import ( BenefitType, @@ -40,6 +48,10 @@ def individuals(self) -> Individuals: def with_raw_response(self) -> BenefitsWithRawResponse: return BenefitsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> BenefitsWithStreamingResponse: + return BenefitsWithStreamingResponse(self) + def create( self, *, @@ -111,6 +123,8 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get( f"/employer/benefits/{benefit_id}", options=make_request_options( @@ -147,6 +161,8 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._post( f"/employer/benefits/{benefit_id}", body=maybe_transform({"description": description}, benefit_update_params.BenefitUpdateParams), @@ -215,6 +231,10 @@ def individuals(self) -> AsyncIndividuals: def with_raw_response(self) -> AsyncBenefitsWithRawResponse: return AsyncBenefitsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncBenefitsWithStreamingResponse: + return AsyncBenefitsWithStreamingResponse(self) + async def create( self, *, @@ -286,6 +306,8 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return await self._get( f"/employer/benefits/{benefit_id}", options=make_request_options( @@ -322,6 +344,8 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return await self._post( f"/employer/benefits/{benefit_id}", body=maybe_transform({"description": description}, benefit_update_params.BenefitUpdateParams), @@ -383,41 +407,99 @@ def list_supported_benefits( class BenefitsWithRawResponse: def __init__(self, benefits: Benefits) -> None: - self.individuals = IndividualsWithRawResponse(benefits.individuals) + self._benefits = benefits - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( benefits.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( benefits.retrieve, ) - self.update = to_raw_response_wrapper( + self.update = _legacy_response.to_raw_response_wrapper( benefits.update, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( benefits.list, ) - self.list_supported_benefits = to_raw_response_wrapper( + self.list_supported_benefits = _legacy_response.to_raw_response_wrapper( benefits.list_supported_benefits, ) + @cached_property + def individuals(self) -> IndividualsWithRawResponse: + return IndividualsWithRawResponse(self._benefits.individuals) + class AsyncBenefitsWithRawResponse: def __init__(self, benefits: AsyncBenefits) -> None: - self.individuals = AsyncIndividualsWithRawResponse(benefits.individuals) + self._benefits = benefits - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( benefits.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = _legacy_response.async_to_raw_response_wrapper( benefits.retrieve, ) - self.update = async_to_raw_response_wrapper( + self.update = _legacy_response.async_to_raw_response_wrapper( benefits.update, ) - self.list = async_to_raw_response_wrapper( + self.list = _legacy_response.async_to_raw_response_wrapper( benefits.list, ) - self.list_supported_benefits = async_to_raw_response_wrapper( + self.list_supported_benefits = _legacy_response.async_to_raw_response_wrapper( benefits.list_supported_benefits, ) + + @cached_property + def individuals(self) -> AsyncIndividualsWithRawResponse: + return AsyncIndividualsWithRawResponse(self._benefits.individuals) + + +class BenefitsWithStreamingResponse: + def __init__(self, benefits: Benefits) -> None: + self._benefits = benefits + + self.create = to_streamed_response_wrapper( + benefits.create, + ) + self.retrieve = to_streamed_response_wrapper( + benefits.retrieve, + ) + self.update = to_streamed_response_wrapper( + benefits.update, + ) + self.list = to_streamed_response_wrapper( + benefits.list, + ) + self.list_supported_benefits = to_streamed_response_wrapper( + benefits.list_supported_benefits, + ) + + @cached_property + def individuals(self) -> IndividualsWithStreamingResponse: + return IndividualsWithStreamingResponse(self._benefits.individuals) + + +class AsyncBenefitsWithStreamingResponse: + def __init__(self, benefits: AsyncBenefits) -> None: + self._benefits = benefits + + self.create = async_to_streamed_response_wrapper( + benefits.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + benefits.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + benefits.update, + ) + self.list = async_to_streamed_response_wrapper( + benefits.list, + ) + self.list_supported_benefits = async_to_streamed_response_wrapper( + benefits.list_supported_benefits, + ) + + @cached_property + def individuals(self) -> AsyncIndividualsWithStreamingResponse: + return AsyncIndividualsWithStreamingResponse(self._benefits.individuals) diff --git a/src/finch/resources/hris/benefits/individuals.py b/src/finch/resources/hris/benefits/individuals.py index 4b6a83bb..16664bac 100644 --- a/src/finch/resources/hris/benefits/individuals.py +++ b/src/finch/resources/hris/benefits/individuals.py @@ -6,11 +6,12 @@ import httpx +from .... import _legacy_response from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ....pagination import SyncSinglePage, AsyncSinglePage from ...._base_client import ( AsyncPaginator, @@ -34,6 +35,10 @@ class Individuals(SyncAPIResource): def with_raw_response(self) -> IndividualsWithRawResponse: return IndividualsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> IndividualsWithStreamingResponse: + return IndividualsWithStreamingResponse(self) + def enroll_many( self, benefit_id: str, @@ -69,6 +74,8 @@ def enroll_many( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=SyncSinglePage[EnrolledIndividual], @@ -105,6 +112,8 @@ def enrolled_ids( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get( f"/employer/benefits/{benefit_id}/enrolled", options=make_request_options( @@ -142,6 +151,8 @@ def retrieve_many_benefits( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=SyncSinglePage[IndividualBenefit], @@ -186,6 +197,8 @@ def unenroll_many( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=SyncSinglePage[UnenrolledIndividual], @@ -205,6 +218,10 @@ class AsyncIndividuals(AsyncAPIResource): def with_raw_response(self) -> AsyncIndividualsWithRawResponse: return AsyncIndividualsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncIndividualsWithStreamingResponse: + return AsyncIndividualsWithStreamingResponse(self) + def enroll_many( self, benefit_id: str, @@ -240,6 +257,8 @@ def enroll_many( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=AsyncSinglePage[EnrolledIndividual], @@ -276,6 +295,8 @@ async def enrolled_ids( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return await self._get( f"/employer/benefits/{benefit_id}/enrolled", options=make_request_options( @@ -313,6 +334,8 @@ def retrieve_many_benefits( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=AsyncSinglePage[IndividualBenefit], @@ -357,6 +380,8 @@ def unenroll_many( timeout: Override the client-level default timeout for this request, in seconds """ + if not benefit_id: + raise ValueError(f"Expected a non-empty value for `benefit_id` but received {benefit_id!r}") return self._get_api_list( f"/employer/benefits/{benefit_id}/individuals", page=AsyncSinglePage[UnenrolledIndividual], @@ -373,31 +398,71 @@ def unenroll_many( class IndividualsWithRawResponse: def __init__(self, individuals: Individuals) -> None: - self.enroll_many = to_raw_response_wrapper( + self._individuals = individuals + + self.enroll_many = _legacy_response.to_raw_response_wrapper( individuals.enroll_many, ) - self.enrolled_ids = to_raw_response_wrapper( + self.enrolled_ids = _legacy_response.to_raw_response_wrapper( individuals.enrolled_ids, ) - self.retrieve_many_benefits = to_raw_response_wrapper( + self.retrieve_many_benefits = _legacy_response.to_raw_response_wrapper( individuals.retrieve_many_benefits, ) - self.unenroll_many = to_raw_response_wrapper( + self.unenroll_many = _legacy_response.to_raw_response_wrapper( individuals.unenroll_many, ) class AsyncIndividualsWithRawResponse: def __init__(self, individuals: AsyncIndividuals) -> None: - self.enroll_many = async_to_raw_response_wrapper( + self._individuals = individuals + + self.enroll_many = _legacy_response.async_to_raw_response_wrapper( + individuals.enroll_many, + ) + self.enrolled_ids = _legacy_response.async_to_raw_response_wrapper( + individuals.enrolled_ids, + ) + self.retrieve_many_benefits = _legacy_response.async_to_raw_response_wrapper( + individuals.retrieve_many_benefits, + ) + self.unenroll_many = _legacy_response.async_to_raw_response_wrapper( + individuals.unenroll_many, + ) + + +class IndividualsWithStreamingResponse: + def __init__(self, individuals: Individuals) -> None: + self._individuals = individuals + + self.enroll_many = to_streamed_response_wrapper( + individuals.enroll_many, + ) + self.enrolled_ids = to_streamed_response_wrapper( + individuals.enrolled_ids, + ) + self.retrieve_many_benefits = to_streamed_response_wrapper( + individuals.retrieve_many_benefits, + ) + self.unenroll_many = to_streamed_response_wrapper( + individuals.unenroll_many, + ) + + +class AsyncIndividualsWithStreamingResponse: + def __init__(self, individuals: AsyncIndividuals) -> None: + self._individuals = individuals + + self.enroll_many = async_to_streamed_response_wrapper( individuals.enroll_many, ) - self.enrolled_ids = async_to_raw_response_wrapper( + self.enrolled_ids = async_to_streamed_response_wrapper( individuals.enrolled_ids, ) - self.retrieve_many_benefits = async_to_raw_response_wrapper( + self.retrieve_many_benefits = async_to_streamed_response_wrapper( individuals.retrieve_many_benefits, ) - self.unenroll_many = async_to_raw_response_wrapper( + self.unenroll_many = async_to_streamed_response_wrapper( individuals.unenroll_many, ) diff --git a/src/finch/resources/hris/company.py b/src/finch/resources/hris/company.py index 2a73d3b3..9180ef32 100644 --- a/src/finch/resources/hris/company.py +++ b/src/finch/resources/hris/company.py @@ -4,10 +4,11 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...types.hris import Company from ..._base_client import ( make_request_options, @@ -21,6 +22,10 @@ class CompanyResource(SyncAPIResource): def with_raw_response(self) -> CompanyResourceWithRawResponse: return CompanyResourceWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> CompanyResourceWithStreamingResponse: + return CompanyResourceWithStreamingResponse(self) + def retrieve( self, *, @@ -46,6 +51,10 @@ class AsyncCompanyResource(AsyncAPIResource): def with_raw_response(self) -> AsyncCompanyResourceWithRawResponse: return AsyncCompanyResourceWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncCompanyResourceWithStreamingResponse: + return AsyncCompanyResourceWithStreamingResponse(self) + async def retrieve( self, *, @@ -68,13 +77,35 @@ async def retrieve( class CompanyResourceWithRawResponse: def __init__(self, company: CompanyResource) -> None: - self.retrieve = to_raw_response_wrapper( + self._company = company + + self.retrieve = _legacy_response.to_raw_response_wrapper( company.retrieve, ) class AsyncCompanyResourceWithRawResponse: def __init__(self, company: AsyncCompanyResource) -> None: - self.retrieve = async_to_raw_response_wrapper( + self._company = company + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + company.retrieve, + ) + + +class CompanyResourceWithStreamingResponse: + def __init__(self, company: CompanyResource) -> None: + self._company = company + + self.retrieve = to_streamed_response_wrapper( + company.retrieve, + ) + + +class AsyncCompanyResourceWithStreamingResponse: + def __init__(self, company: AsyncCompanyResource) -> None: + self._company = company + + self.retrieve = async_to_streamed_response_wrapper( company.retrieve, ) diff --git a/src/finch/resources/hris/directory.py b/src/finch/resources/hris/directory.py index 5c4e3930..4a9ac9b6 100644 --- a/src/finch/resources/hris/directory.py +++ b/src/finch/resources/hris/directory.py @@ -6,11 +6,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncIndividualsPage, AsyncIndividualsPage from ...types.hris import IndividualInDirectory, directory_list_params from ..._base_client import ( @@ -26,6 +27,10 @@ class Directory(SyncAPIResource): def with_raw_response(self) -> DirectoryWithRawResponse: return DirectoryWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> DirectoryWithStreamingResponse: + return DirectoryWithStreamingResponse(self) + def list( self, *, @@ -117,6 +122,10 @@ class AsyncDirectory(AsyncAPIResource): def with_raw_response(self) -> AsyncDirectoryWithRawResponse: return AsyncDirectoryWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncDirectoryWithStreamingResponse: + return AsyncDirectoryWithStreamingResponse(self) + def list( self, *, @@ -205,19 +214,55 @@ def list_individuals( class DirectoryWithRawResponse: def __init__(self, directory: Directory) -> None: - self.list = to_raw_response_wrapper( + self._directory = directory + + self.list = _legacy_response.to_raw_response_wrapper( directory.list, ) - self.list_individuals = to_raw_response_wrapper( # pyright: ignore[reportDeprecated] - directory.list_individuals # pyright: ignore[reportDeprecated], + self.list_individuals = ( # pyright: ignore[reportDeprecated] + _legacy_response.to_raw_response_wrapper( + directory.list_individuals # pyright: ignore[reportDeprecated], + ) ) class AsyncDirectoryWithRawResponse: def __init__(self, directory: AsyncDirectory) -> None: - self.list = async_to_raw_response_wrapper( + self._directory = directory + + self.list = _legacy_response.async_to_raw_response_wrapper( + directory.list, + ) + self.list_individuals = ( # pyright: ignore[reportDeprecated] + _legacy_response.async_to_raw_response_wrapper( + directory.list_individuals # pyright: ignore[reportDeprecated], + ) + ) + + +class DirectoryWithStreamingResponse: + def __init__(self, directory: Directory) -> None: + self._directory = directory + + self.list = to_streamed_response_wrapper( + directory.list, + ) + self.list_individuals = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + directory.list_individuals # pyright: ignore[reportDeprecated], + ) + ) + + +class AsyncDirectoryWithStreamingResponse: + def __init__(self, directory: AsyncDirectory) -> None: + self._directory = directory + + self.list = async_to_streamed_response_wrapper( directory.list, ) - self.list_individuals = async_to_raw_response_wrapper( # pyright: ignore[reportDeprecated] - directory.list_individuals # pyright: ignore[reportDeprecated], + self.list_individuals = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + directory.list_individuals # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/finch/resources/hris/employments.py b/src/finch/resources/hris/employments.py index d44b32a1..3b1f9cf3 100644 --- a/src/finch/resources/hris/employments.py +++ b/src/finch/resources/hris/employments.py @@ -6,11 +6,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncResponsesPage, AsyncResponsesPage from ...types.hris import EmploymentDataResponse, employment_retrieve_many_params from ..._base_client import ( @@ -26,6 +27,10 @@ class Employments(SyncAPIResource): def with_raw_response(self) -> EmploymentsWithRawResponse: return EmploymentsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> EmploymentsWithStreamingResponse: + return EmploymentsWithStreamingResponse(self) + def retrieve_many( self, *, @@ -72,6 +77,10 @@ class AsyncEmployments(AsyncAPIResource): def with_raw_response(self) -> AsyncEmploymentsWithRawResponse: return AsyncEmploymentsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncEmploymentsWithStreamingResponse: + return AsyncEmploymentsWithStreamingResponse(self) + def retrieve_many( self, *, @@ -115,13 +124,35 @@ def retrieve_many( class EmploymentsWithRawResponse: def __init__(self, employments: Employments) -> None: - self.retrieve_many = to_raw_response_wrapper( + self._employments = employments + + self.retrieve_many = _legacy_response.to_raw_response_wrapper( employments.retrieve_many, ) class AsyncEmploymentsWithRawResponse: def __init__(self, employments: AsyncEmployments) -> None: - self.retrieve_many = async_to_raw_response_wrapper( + self._employments = employments + + self.retrieve_many = _legacy_response.async_to_raw_response_wrapper( + employments.retrieve_many, + ) + + +class EmploymentsWithStreamingResponse: + def __init__(self, employments: Employments) -> None: + self._employments = employments + + self.retrieve_many = to_streamed_response_wrapper( + employments.retrieve_many, + ) + + +class AsyncEmploymentsWithStreamingResponse: + def __init__(self, employments: AsyncEmployments) -> None: + self._employments = employments + + self.retrieve_many = async_to_streamed_response_wrapper( employments.retrieve_many, ) diff --git a/src/finch/resources/hris/hris.py b/src/finch/resources/hris/hris.py index e02ebae1..8fd5751b 100644 --- a/src/finch/resources/hris/hris.py +++ b/src/finch/resources/hris/hris.py @@ -7,19 +7,58 @@ AsyncCompanyResource, CompanyResourceWithRawResponse, AsyncCompanyResourceWithRawResponse, + CompanyResourceWithStreamingResponse, + AsyncCompanyResourceWithStreamingResponse, +) +from .benefits import ( + Benefits, + AsyncBenefits, + BenefitsWithRawResponse, + AsyncBenefitsWithRawResponse, + BenefitsWithStreamingResponse, + AsyncBenefitsWithStreamingResponse, +) +from .payments import ( + Payments, + AsyncPayments, + PaymentsWithRawResponse, + AsyncPaymentsWithRawResponse, + PaymentsWithStreamingResponse, + AsyncPaymentsWithStreamingResponse, ) -from .benefits import Benefits, AsyncBenefits, BenefitsWithRawResponse, AsyncBenefitsWithRawResponse -from .payments import Payments, AsyncPayments, PaymentsWithRawResponse, AsyncPaymentsWithRawResponse from ..._compat import cached_property -from .directory import Directory, AsyncDirectory, DirectoryWithRawResponse, AsyncDirectoryWithRawResponse +from .directory import ( + Directory, + AsyncDirectory, + DirectoryWithRawResponse, + AsyncDirectoryWithRawResponse, + DirectoryWithStreamingResponse, + AsyncDirectoryWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource -from .employments import Employments, AsyncEmployments, EmploymentsWithRawResponse, AsyncEmploymentsWithRawResponse -from .individuals import Individuals, AsyncIndividuals, IndividualsWithRawResponse, AsyncIndividualsWithRawResponse +from .employments import ( + Employments, + AsyncEmployments, + EmploymentsWithRawResponse, + AsyncEmploymentsWithRawResponse, + EmploymentsWithStreamingResponse, + AsyncEmploymentsWithStreamingResponse, +) +from .individuals import ( + Individuals, + AsyncIndividuals, + IndividualsWithRawResponse, + AsyncIndividualsWithRawResponse, + IndividualsWithStreamingResponse, + AsyncIndividualsWithStreamingResponse, +) from .pay_statements import ( PayStatements, AsyncPayStatements, PayStatementsWithRawResponse, AsyncPayStatementsWithRawResponse, + PayStatementsWithStreamingResponse, + AsyncPayStatementsWithStreamingResponse, ) from .benefits.benefits import Benefits, AsyncBenefits @@ -59,6 +98,10 @@ def benefits(self) -> Benefits: def with_raw_response(self) -> HRISWithRawResponse: return HRISWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> HRISWithStreamingResponse: + return HRISWithStreamingResponse(self) + class AsyncHRIS(AsyncAPIResource): @cached_property @@ -93,24 +136,138 @@ def benefits(self) -> AsyncBenefits: def with_raw_response(self) -> AsyncHRISWithRawResponse: return AsyncHRISWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncHRISWithStreamingResponse: + return AsyncHRISWithStreamingResponse(self) + class HRISWithRawResponse: def __init__(self, hris: HRIS) -> None: - self.company = CompanyResourceWithRawResponse(hris.company) - self.directory = DirectoryWithRawResponse(hris.directory) - self.individuals = IndividualsWithRawResponse(hris.individuals) - self.employments = EmploymentsWithRawResponse(hris.employments) - self.payments = PaymentsWithRawResponse(hris.payments) - self.pay_statements = PayStatementsWithRawResponse(hris.pay_statements) - self.benefits = BenefitsWithRawResponse(hris.benefits) + self._hris = hris + + @cached_property + def company(self) -> CompanyResourceWithRawResponse: + return CompanyResourceWithRawResponse(self._hris.company) + + @cached_property + def directory(self) -> DirectoryWithRawResponse: + return DirectoryWithRawResponse(self._hris.directory) + + @cached_property + def individuals(self) -> IndividualsWithRawResponse: + return IndividualsWithRawResponse(self._hris.individuals) + + @cached_property + def employments(self) -> EmploymentsWithRawResponse: + return EmploymentsWithRawResponse(self._hris.employments) + + @cached_property + def payments(self) -> PaymentsWithRawResponse: + return PaymentsWithRawResponse(self._hris.payments) + + @cached_property + def pay_statements(self) -> PayStatementsWithRawResponse: + return PayStatementsWithRawResponse(self._hris.pay_statements) + + @cached_property + def benefits(self) -> BenefitsWithRawResponse: + return BenefitsWithRawResponse(self._hris.benefits) class AsyncHRISWithRawResponse: def __init__(self, hris: AsyncHRIS) -> None: - self.company = AsyncCompanyResourceWithRawResponse(hris.company) - self.directory = AsyncDirectoryWithRawResponse(hris.directory) - self.individuals = AsyncIndividualsWithRawResponse(hris.individuals) - self.employments = AsyncEmploymentsWithRawResponse(hris.employments) - self.payments = AsyncPaymentsWithRawResponse(hris.payments) - self.pay_statements = AsyncPayStatementsWithRawResponse(hris.pay_statements) - self.benefits = AsyncBenefitsWithRawResponse(hris.benefits) + self._hris = hris + + @cached_property + def company(self) -> AsyncCompanyResourceWithRawResponse: + return AsyncCompanyResourceWithRawResponse(self._hris.company) + + @cached_property + def directory(self) -> AsyncDirectoryWithRawResponse: + return AsyncDirectoryWithRawResponse(self._hris.directory) + + @cached_property + def individuals(self) -> AsyncIndividualsWithRawResponse: + return AsyncIndividualsWithRawResponse(self._hris.individuals) + + @cached_property + def employments(self) -> AsyncEmploymentsWithRawResponse: + return AsyncEmploymentsWithRawResponse(self._hris.employments) + + @cached_property + def payments(self) -> AsyncPaymentsWithRawResponse: + return AsyncPaymentsWithRawResponse(self._hris.payments) + + @cached_property + def pay_statements(self) -> AsyncPayStatementsWithRawResponse: + return AsyncPayStatementsWithRawResponse(self._hris.pay_statements) + + @cached_property + def benefits(self) -> AsyncBenefitsWithRawResponse: + return AsyncBenefitsWithRawResponse(self._hris.benefits) + + +class HRISWithStreamingResponse: + def __init__(self, hris: HRIS) -> None: + self._hris = hris + + @cached_property + def company(self) -> CompanyResourceWithStreamingResponse: + return CompanyResourceWithStreamingResponse(self._hris.company) + + @cached_property + def directory(self) -> DirectoryWithStreamingResponse: + return DirectoryWithStreamingResponse(self._hris.directory) + + @cached_property + def individuals(self) -> IndividualsWithStreamingResponse: + return IndividualsWithStreamingResponse(self._hris.individuals) + + @cached_property + def employments(self) -> EmploymentsWithStreamingResponse: + return EmploymentsWithStreamingResponse(self._hris.employments) + + @cached_property + def payments(self) -> PaymentsWithStreamingResponse: + return PaymentsWithStreamingResponse(self._hris.payments) + + @cached_property + def pay_statements(self) -> PayStatementsWithStreamingResponse: + return PayStatementsWithStreamingResponse(self._hris.pay_statements) + + @cached_property + def benefits(self) -> BenefitsWithStreamingResponse: + return BenefitsWithStreamingResponse(self._hris.benefits) + + +class AsyncHRISWithStreamingResponse: + def __init__(self, hris: AsyncHRIS) -> None: + self._hris = hris + + @cached_property + def company(self) -> AsyncCompanyResourceWithStreamingResponse: + return AsyncCompanyResourceWithStreamingResponse(self._hris.company) + + @cached_property + def directory(self) -> AsyncDirectoryWithStreamingResponse: + return AsyncDirectoryWithStreamingResponse(self._hris.directory) + + @cached_property + def individuals(self) -> AsyncIndividualsWithStreamingResponse: + return AsyncIndividualsWithStreamingResponse(self._hris.individuals) + + @cached_property + def employments(self) -> AsyncEmploymentsWithStreamingResponse: + return AsyncEmploymentsWithStreamingResponse(self._hris.employments) + + @cached_property + def payments(self) -> AsyncPaymentsWithStreamingResponse: + return AsyncPaymentsWithStreamingResponse(self._hris.payments) + + @cached_property + def pay_statements(self) -> AsyncPayStatementsWithStreamingResponse: + return AsyncPayStatementsWithStreamingResponse(self._hris.pay_statements) + + @cached_property + def benefits(self) -> AsyncBenefitsWithStreamingResponse: + return AsyncBenefitsWithStreamingResponse(self._hris.benefits) diff --git a/src/finch/resources/hris/individuals.py b/src/finch/resources/hris/individuals.py index 4eea801e..93f37737 100644 --- a/src/finch/resources/hris/individuals.py +++ b/src/finch/resources/hris/individuals.py @@ -6,11 +6,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncResponsesPage, AsyncResponsesPage from ...types.hris import IndividualResponse, individual_retrieve_many_params from ..._base_client import ( @@ -26,6 +27,10 @@ class Individuals(SyncAPIResource): def with_raw_response(self) -> IndividualsWithRawResponse: return IndividualsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> IndividualsWithStreamingResponse: + return IndividualsWithStreamingResponse(self) + def retrieve_many( self, *, @@ -73,6 +78,10 @@ class AsyncIndividuals(AsyncAPIResource): def with_raw_response(self) -> AsyncIndividualsWithRawResponse: return AsyncIndividualsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncIndividualsWithStreamingResponse: + return AsyncIndividualsWithStreamingResponse(self) + def retrieve_many( self, *, @@ -117,13 +126,35 @@ def retrieve_many( class IndividualsWithRawResponse: def __init__(self, individuals: Individuals) -> None: - self.retrieve_many = to_raw_response_wrapper( + self._individuals = individuals + + self.retrieve_many = _legacy_response.to_raw_response_wrapper( individuals.retrieve_many, ) class AsyncIndividualsWithRawResponse: def __init__(self, individuals: AsyncIndividuals) -> None: - self.retrieve_many = async_to_raw_response_wrapper( + self._individuals = individuals + + self.retrieve_many = _legacy_response.async_to_raw_response_wrapper( + individuals.retrieve_many, + ) + + +class IndividualsWithStreamingResponse: + def __init__(self, individuals: Individuals) -> None: + self._individuals = individuals + + self.retrieve_many = to_streamed_response_wrapper( + individuals.retrieve_many, + ) + + +class AsyncIndividualsWithStreamingResponse: + def __init__(self, individuals: AsyncIndividuals) -> None: + self._individuals = individuals + + self.retrieve_many = async_to_streamed_response_wrapper( individuals.retrieve_many, ) diff --git a/src/finch/resources/hris/pay_statements.py b/src/finch/resources/hris/pay_statements.py index 46eea68f..4ede0e4e 100644 --- a/src/finch/resources/hris/pay_statements.py +++ b/src/finch/resources/hris/pay_statements.py @@ -6,11 +6,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncResponsesPage, AsyncResponsesPage from ...types.hris import PayStatementResponse, pay_statement_retrieve_many_params from ..._base_client import ( @@ -26,6 +27,10 @@ class PayStatements(SyncAPIResource): def with_raw_response(self) -> PayStatementsWithRawResponse: return PayStatementsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> PayStatementsWithStreamingResponse: + return PayStatementsWithStreamingResponse(self) + def retrieve_many( self, *, @@ -73,6 +78,10 @@ class AsyncPayStatements(AsyncAPIResource): def with_raw_response(self) -> AsyncPayStatementsWithRawResponse: return AsyncPayStatementsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncPayStatementsWithStreamingResponse: + return AsyncPayStatementsWithStreamingResponse(self) + def retrieve_many( self, *, @@ -117,13 +126,35 @@ def retrieve_many( class PayStatementsWithRawResponse: def __init__(self, pay_statements: PayStatements) -> None: - self.retrieve_many = to_raw_response_wrapper( + self._pay_statements = pay_statements + + self.retrieve_many = _legacy_response.to_raw_response_wrapper( pay_statements.retrieve_many, ) class AsyncPayStatementsWithRawResponse: def __init__(self, pay_statements: AsyncPayStatements) -> None: - self.retrieve_many = async_to_raw_response_wrapper( + self._pay_statements = pay_statements + + self.retrieve_many = _legacy_response.async_to_raw_response_wrapper( + pay_statements.retrieve_many, + ) + + +class PayStatementsWithStreamingResponse: + def __init__(self, pay_statements: PayStatements) -> None: + self._pay_statements = pay_statements + + self.retrieve_many = to_streamed_response_wrapper( + pay_statements.retrieve_many, + ) + + +class AsyncPayStatementsWithStreamingResponse: + def __init__(self, pay_statements: AsyncPayStatements) -> None: + self._pay_statements = pay_statements + + self.retrieve_many = async_to_streamed_response_wrapper( pay_statements.retrieve_many, ) diff --git a/src/finch/resources/hris/payments.py b/src/finch/resources/hris/payments.py index 50ed186d..66496a99 100644 --- a/src/finch/resources/hris/payments.py +++ b/src/finch/resources/hris/payments.py @@ -7,11 +7,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncSinglePage, AsyncSinglePage from ...types.hris import Payment, payment_list_params from ..._base_client import ( @@ -27,6 +28,10 @@ class Payments(SyncAPIResource): def with_raw_response(self) -> PaymentsWithRawResponse: return PaymentsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> PaymentsWithStreamingResponse: + return PaymentsWithStreamingResponse(self) + def list( self, *, @@ -82,6 +87,10 @@ class AsyncPayments(AsyncAPIResource): def with_raw_response(self) -> AsyncPaymentsWithRawResponse: return AsyncPaymentsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncPaymentsWithStreamingResponse: + return AsyncPaymentsWithStreamingResponse(self) + def list( self, *, @@ -134,13 +143,35 @@ def list( class PaymentsWithRawResponse: def __init__(self, payments: Payments) -> None: - self.list = to_raw_response_wrapper( + self._payments = payments + + self.list = _legacy_response.to_raw_response_wrapper( payments.list, ) class AsyncPaymentsWithRawResponse: def __init__(self, payments: AsyncPayments) -> None: - self.list = async_to_raw_response_wrapper( + self._payments = payments + + self.list = _legacy_response.async_to_raw_response_wrapper( + payments.list, + ) + + +class PaymentsWithStreamingResponse: + def __init__(self, payments: Payments) -> None: + self._payments = payments + + self.list = to_streamed_response_wrapper( + payments.list, + ) + + +class AsyncPaymentsWithStreamingResponse: + def __init__(self, payments: AsyncPayments) -> None: + self._payments = payments + + self.list = async_to_streamed_response_wrapper( payments.list, ) diff --git a/src/finch/resources/jobs/__init__.py b/src/finch/resources/jobs/__init__.py index 112d3130..261436d7 100644 --- a/src/finch/resources/jobs/__init__.py +++ b/src/finch/resources/jobs/__init__.py @@ -1,20 +1,47 @@ # File generated from our OpenAPI spec by Stainless. -from .jobs import Jobs, AsyncJobs, JobsWithRawResponse, AsyncJobsWithRawResponse -from .manual import Manual, AsyncManual, ManualWithRawResponse, AsyncManualWithRawResponse -from .automated import Automated, AsyncAutomated, AutomatedWithRawResponse, AsyncAutomatedWithRawResponse +from .jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) +from .manual import ( + Manual, + AsyncManual, + ManualWithRawResponse, + AsyncManualWithRawResponse, + ManualWithStreamingResponse, + AsyncManualWithStreamingResponse, +) +from .automated import ( + Automated, + AsyncAutomated, + AutomatedWithRawResponse, + AsyncAutomatedWithRawResponse, + AutomatedWithStreamingResponse, + AsyncAutomatedWithStreamingResponse, +) __all__ = [ "Automated", "AsyncAutomated", "AutomatedWithRawResponse", "AsyncAutomatedWithRawResponse", + "AutomatedWithStreamingResponse", + "AsyncAutomatedWithStreamingResponse", "Manual", "AsyncManual", "ManualWithRawResponse", "AsyncManualWithRawResponse", + "ManualWithStreamingResponse", + "AsyncManualWithStreamingResponse", "Jobs", "AsyncJobs", "JobsWithRawResponse", "AsyncJobsWithRawResponse", + "JobsWithStreamingResponse", + "AsyncJobsWithStreamingResponse", ] diff --git a/src/finch/resources/jobs/automated.py b/src/finch/resources/jobs/automated.py index 990f9033..13b460ff 100644 --- a/src/finch/resources/jobs/automated.py +++ b/src/finch/resources/jobs/automated.py @@ -6,11 +6,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...pagination import SyncPage, AsyncPage from ...types.jobs import AutomatedAsyncJob, AutomatedCreateResponse, automated_list_params, automated_create_params from ..._base_client import ( @@ -26,6 +27,10 @@ class Automated(SyncAPIResource): def with_raw_response(self) -> AutomatedWithRawResponse: return AutomatedWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AutomatedWithStreamingResponse: + return AutomatedWithStreamingResponse(self) + def create( self, *, @@ -93,6 +98,8 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") return self._get( f"/jobs/automated/{job_id}", options=make_request_options( @@ -157,6 +164,10 @@ class AsyncAutomated(AsyncAPIResource): def with_raw_response(self) -> AsyncAutomatedWithRawResponse: return AsyncAutomatedWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncAutomatedWithStreamingResponse: + return AsyncAutomatedWithStreamingResponse(self) + async def create( self, *, @@ -224,6 +235,8 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") return await self._get( f"/jobs/automated/{job_id}", options=make_request_options( @@ -285,25 +298,59 @@ def list( class AutomatedWithRawResponse: def __init__(self, automated: Automated) -> None: - self.create = to_raw_response_wrapper( + self._automated = automated + + self.create = _legacy_response.to_raw_response_wrapper( automated.create, ) - self.retrieve = to_raw_response_wrapper( + self.retrieve = _legacy_response.to_raw_response_wrapper( automated.retrieve, ) - self.list = to_raw_response_wrapper( + self.list = _legacy_response.to_raw_response_wrapper( automated.list, ) class AsyncAutomatedWithRawResponse: def __init__(self, automated: AsyncAutomated) -> None: - self.create = async_to_raw_response_wrapper( + self._automated = automated + + self.create = _legacy_response.async_to_raw_response_wrapper( + automated.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + automated.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + automated.list, + ) + + +class AutomatedWithStreamingResponse: + def __init__(self, automated: Automated) -> None: + self._automated = automated + + self.create = to_streamed_response_wrapper( + automated.create, + ) + self.retrieve = to_streamed_response_wrapper( + automated.retrieve, + ) + self.list = to_streamed_response_wrapper( + automated.list, + ) + + +class AsyncAutomatedWithStreamingResponse: + def __init__(self, automated: AsyncAutomated) -> None: + self._automated = automated + + self.create = async_to_streamed_response_wrapper( automated.create, ) - self.retrieve = async_to_raw_response_wrapper( + self.retrieve = async_to_streamed_response_wrapper( automated.retrieve, ) - self.list = async_to_raw_response_wrapper( + self.list = async_to_streamed_response_wrapper( automated.list, ) diff --git a/src/finch/resources/jobs/jobs.py b/src/finch/resources/jobs/jobs.py index 314cd630..ec354abd 100644 --- a/src/finch/resources/jobs/jobs.py +++ b/src/finch/resources/jobs/jobs.py @@ -2,9 +2,23 @@ from __future__ import annotations -from .manual import Manual, AsyncManual, ManualWithRawResponse, AsyncManualWithRawResponse +from .manual import ( + Manual, + AsyncManual, + ManualWithRawResponse, + AsyncManualWithRawResponse, + ManualWithStreamingResponse, + AsyncManualWithStreamingResponse, +) from ..._compat import cached_property -from .automated import Automated, AsyncAutomated, AutomatedWithRawResponse, AsyncAutomatedWithRawResponse +from .automated import ( + Automated, + AsyncAutomated, + AutomatedWithRawResponse, + AsyncAutomatedWithRawResponse, + AutomatedWithStreamingResponse, + AsyncAutomatedWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource __all__ = ["Jobs", "AsyncJobs"] @@ -23,6 +37,10 @@ def manual(self) -> Manual: def with_raw_response(self) -> JobsWithRawResponse: return JobsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> JobsWithStreamingResponse: + return JobsWithStreamingResponse(self) + class AsyncJobs(AsyncAPIResource): @cached_property @@ -37,14 +55,58 @@ def manual(self) -> AsyncManual: def with_raw_response(self) -> AsyncJobsWithRawResponse: return AsyncJobsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncJobsWithStreamingResponse: + return AsyncJobsWithStreamingResponse(self) + class JobsWithRawResponse: def __init__(self, jobs: Jobs) -> None: - self.automated = AutomatedWithRawResponse(jobs.automated) - self.manual = ManualWithRawResponse(jobs.manual) + self._jobs = jobs + + @cached_property + def automated(self) -> AutomatedWithRawResponse: + return AutomatedWithRawResponse(self._jobs.automated) + + @cached_property + def manual(self) -> ManualWithRawResponse: + return ManualWithRawResponse(self._jobs.manual) class AsyncJobsWithRawResponse: def __init__(self, jobs: AsyncJobs) -> None: - self.automated = AsyncAutomatedWithRawResponse(jobs.automated) - self.manual = AsyncManualWithRawResponse(jobs.manual) + self._jobs = jobs + + @cached_property + def automated(self) -> AsyncAutomatedWithRawResponse: + return AsyncAutomatedWithRawResponse(self._jobs.automated) + + @cached_property + def manual(self) -> AsyncManualWithRawResponse: + return AsyncManualWithRawResponse(self._jobs.manual) + + +class JobsWithStreamingResponse: + def __init__(self, jobs: Jobs) -> None: + self._jobs = jobs + + @cached_property + def automated(self) -> AutomatedWithStreamingResponse: + return AutomatedWithStreamingResponse(self._jobs.automated) + + @cached_property + def manual(self) -> ManualWithStreamingResponse: + return ManualWithStreamingResponse(self._jobs.manual) + + +class AsyncJobsWithStreamingResponse: + def __init__(self, jobs: AsyncJobs) -> None: + self._jobs = jobs + + @cached_property + def automated(self) -> AsyncAutomatedWithStreamingResponse: + return AsyncAutomatedWithStreamingResponse(self._jobs.automated) + + @cached_property + def manual(self) -> AsyncManualWithStreamingResponse: + return AsyncManualWithStreamingResponse(self._jobs.manual) diff --git a/src/finch/resources/jobs/manual.py b/src/finch/resources/jobs/manual.py index 565513a4..03195ea2 100644 --- a/src/finch/resources/jobs/manual.py +++ b/src/finch/resources/jobs/manual.py @@ -4,10 +4,11 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...types.jobs import ManualAsyncJob from ..._base_client import ( make_request_options, @@ -21,6 +22,10 @@ class Manual(SyncAPIResource): def with_raw_response(self) -> ManualWithRawResponse: return ManualWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ManualWithStreamingResponse: + return ManualWithStreamingResponse(self) + def retrieve( self, job_id: str, @@ -46,6 +51,8 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") return self._get( f"/jobs/manual/{job_id}", options=make_request_options( @@ -60,6 +67,10 @@ class AsyncManual(AsyncAPIResource): def with_raw_response(self) -> AsyncManualWithRawResponse: return AsyncManualWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncManualWithStreamingResponse: + return AsyncManualWithStreamingResponse(self) + async def retrieve( self, job_id: str, @@ -85,6 +96,8 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ + if not job_id: + raise ValueError(f"Expected a non-empty value for `job_id` but received {job_id!r}") return await self._get( f"/jobs/manual/{job_id}", options=make_request_options( @@ -96,13 +109,35 @@ async def retrieve( class ManualWithRawResponse: def __init__(self, manual: Manual) -> None: - self.retrieve = to_raw_response_wrapper( + self._manual = manual + + self.retrieve = _legacy_response.to_raw_response_wrapper( manual.retrieve, ) class AsyncManualWithRawResponse: def __init__(self, manual: AsyncManual) -> None: - self.retrieve = async_to_raw_response_wrapper( + self._manual = manual + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + manual.retrieve, + ) + + +class ManualWithStreamingResponse: + def __init__(self, manual: Manual) -> None: + self._manual = manual + + self.retrieve = to_streamed_response_wrapper( + manual.retrieve, + ) + + +class AsyncManualWithStreamingResponse: + def __init__(self, manual: AsyncManual) -> None: + self._manual = manual + + self.retrieve = async_to_streamed_response_wrapper( manual.retrieve, ) diff --git a/src/finch/resources/providers.py b/src/finch/resources/providers.py index bac6a203..63a671f4 100644 --- a/src/finch/resources/providers.py +++ b/src/finch/resources/providers.py @@ -4,11 +4,12 @@ import httpx +from .. import _legacy_response from ..types import Provider from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..pagination import SyncSinglePage, AsyncSinglePage from .._base_client import ( AsyncPaginator, @@ -23,6 +24,10 @@ class Providers(SyncAPIResource): def with_raw_response(self) -> ProvidersWithRawResponse: return ProvidersWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ProvidersWithStreamingResponse: + return ProvidersWithStreamingResponse(self) + def list( self, *, @@ -49,6 +54,10 @@ class AsyncProviders(AsyncAPIResource): def with_raw_response(self) -> AsyncProvidersWithRawResponse: return AsyncProvidersWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncProvidersWithStreamingResponse: + return AsyncProvidersWithStreamingResponse(self) + def list( self, *, @@ -72,13 +81,35 @@ def list( class ProvidersWithRawResponse: def __init__(self, providers: Providers) -> None: - self.list = to_raw_response_wrapper( + self._providers = providers + + self.list = _legacy_response.to_raw_response_wrapper( providers.list, ) class AsyncProvidersWithRawResponse: def __init__(self, providers: AsyncProviders) -> None: - self.list = async_to_raw_response_wrapper( + self._providers = providers + + self.list = _legacy_response.async_to_raw_response_wrapper( + providers.list, + ) + + +class ProvidersWithStreamingResponse: + def __init__(self, providers: Providers) -> None: + self._providers = providers + + self.list = to_streamed_response_wrapper( + providers.list, + ) + + +class AsyncProvidersWithStreamingResponse: + def __init__(self, providers: AsyncProviders) -> None: + self._providers = providers + + self.list = async_to_streamed_response_wrapper( providers.list, ) diff --git a/src/finch/resources/request_forwarding.py b/src/finch/resources/request_forwarding.py index 90bd0f13..09595451 100644 --- a/src/finch/resources/request_forwarding.py +++ b/src/finch/resources/request_forwarding.py @@ -6,12 +6,13 @@ import httpx +from .. import _legacy_response from ..types import RequestForwardingForwardResponse, request_forwarding_forward_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from .._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from .._base_client import ( make_request_options, ) @@ -24,6 +25,10 @@ class RequestForwarding(SyncAPIResource): def with_raw_response(self) -> RequestForwardingWithRawResponse: return RequestForwardingWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> RequestForwardingWithStreamingResponse: + return RequestForwardingWithStreamingResponse(self) + def forward( self, *, @@ -96,6 +101,10 @@ class AsyncRequestForwarding(AsyncAPIResource): def with_raw_response(self) -> AsyncRequestForwardingWithRawResponse: return AsyncRequestForwardingWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncRequestForwardingWithStreamingResponse: + return AsyncRequestForwardingWithStreamingResponse(self) + async def forward( self, *, @@ -165,13 +174,35 @@ async def forward( class RequestForwardingWithRawResponse: def __init__(self, request_forwarding: RequestForwarding) -> None: - self.forward = to_raw_response_wrapper( + self._request_forwarding = request_forwarding + + self.forward = _legacy_response.to_raw_response_wrapper( request_forwarding.forward, ) class AsyncRequestForwardingWithRawResponse: def __init__(self, request_forwarding: AsyncRequestForwarding) -> None: - self.forward = async_to_raw_response_wrapper( + self._request_forwarding = request_forwarding + + self.forward = _legacy_response.async_to_raw_response_wrapper( + request_forwarding.forward, + ) + + +class RequestForwardingWithStreamingResponse: + def __init__(self, request_forwarding: RequestForwarding) -> None: + self._request_forwarding = request_forwarding + + self.forward = to_streamed_response_wrapper( + request_forwarding.forward, + ) + + +class AsyncRequestForwardingWithStreamingResponse: + def __init__(self, request_forwarding: AsyncRequestForwarding) -> None: + self._request_forwarding = request_forwarding + + self.forward = async_to_streamed_response_wrapper( request_forwarding.forward, ) diff --git a/src/finch/resources/sandbox/__init__.py b/src/finch/resources/sandbox/__init__.py index 41f27f60..0c8d6436 100644 --- a/src/finch/resources/sandbox/__init__.py +++ b/src/finch/resources/sandbox/__init__.py @@ -1,45 +1,117 @@ # File generated from our OpenAPI spec by Stainless. -from .jobs import Jobs, AsyncJobs, JobsWithRawResponse, AsyncJobsWithRawResponse -from .company import Company, AsyncCompany, CompanyWithRawResponse, AsyncCompanyWithRawResponse -from .payment import Payment, AsyncPayment, PaymentWithRawResponse, AsyncPaymentWithRawResponse -from .sandbox import Sandbox, AsyncSandbox, SandboxWithRawResponse, AsyncSandboxWithRawResponse -from .directory import Directory, AsyncDirectory, DirectoryWithRawResponse, AsyncDirectoryWithRawResponse -from .employment import Employment, AsyncEmployment, EmploymentWithRawResponse, AsyncEmploymentWithRawResponse -from .individual import Individual, AsyncIndividual, IndividualWithRawResponse, AsyncIndividualWithRawResponse -from .connections import Connections, AsyncConnections, ConnectionsWithRawResponse, AsyncConnectionsWithRawResponse +from .jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) +from .company import ( + Company, + AsyncCompany, + CompanyWithRawResponse, + AsyncCompanyWithRawResponse, + CompanyWithStreamingResponse, + AsyncCompanyWithStreamingResponse, +) +from .payment import ( + Payment, + AsyncPayment, + PaymentWithRawResponse, + AsyncPaymentWithRawResponse, + PaymentWithStreamingResponse, + AsyncPaymentWithStreamingResponse, +) +from .sandbox import ( + Sandbox, + AsyncSandbox, + SandboxWithRawResponse, + AsyncSandboxWithRawResponse, + SandboxWithStreamingResponse, + AsyncSandboxWithStreamingResponse, +) +from .directory import ( + Directory, + AsyncDirectory, + DirectoryWithRawResponse, + AsyncDirectoryWithRawResponse, + DirectoryWithStreamingResponse, + AsyncDirectoryWithStreamingResponse, +) +from .employment import ( + Employment, + AsyncEmployment, + EmploymentWithRawResponse, + AsyncEmploymentWithRawResponse, + EmploymentWithStreamingResponse, + AsyncEmploymentWithStreamingResponse, +) +from .individual import ( + Individual, + AsyncIndividual, + IndividualWithRawResponse, + AsyncIndividualWithRawResponse, + IndividualWithStreamingResponse, + AsyncIndividualWithStreamingResponse, +) +from .connections import ( + Connections, + AsyncConnections, + ConnectionsWithRawResponse, + AsyncConnectionsWithRawResponse, + ConnectionsWithStreamingResponse, + AsyncConnectionsWithStreamingResponse, +) __all__ = [ "Connections", "AsyncConnections", "ConnectionsWithRawResponse", "AsyncConnectionsWithRawResponse", + "ConnectionsWithStreamingResponse", + "AsyncConnectionsWithStreamingResponse", "Company", "AsyncCompany", "CompanyWithRawResponse", "AsyncCompanyWithRawResponse", + "CompanyWithStreamingResponse", + "AsyncCompanyWithStreamingResponse", "Directory", "AsyncDirectory", "DirectoryWithRawResponse", "AsyncDirectoryWithRawResponse", + "DirectoryWithStreamingResponse", + "AsyncDirectoryWithStreamingResponse", "Individual", "AsyncIndividual", "IndividualWithRawResponse", "AsyncIndividualWithRawResponse", + "IndividualWithStreamingResponse", + "AsyncIndividualWithStreamingResponse", "Employment", "AsyncEmployment", "EmploymentWithRawResponse", "AsyncEmploymentWithRawResponse", + "EmploymentWithStreamingResponse", + "AsyncEmploymentWithStreamingResponse", "Payment", "AsyncPayment", "PaymentWithRawResponse", "AsyncPaymentWithRawResponse", + "PaymentWithStreamingResponse", + "AsyncPaymentWithStreamingResponse", "Jobs", "AsyncJobs", "JobsWithRawResponse", "AsyncJobsWithRawResponse", + "JobsWithStreamingResponse", + "AsyncJobsWithStreamingResponse", "Sandbox", "AsyncSandbox", "SandboxWithRawResponse", "AsyncSandboxWithRawResponse", + "SandboxWithStreamingResponse", + "AsyncSandboxWithStreamingResponse", ] diff --git a/src/finch/resources/sandbox/company.py b/src/finch/resources/sandbox/company.py index 82bb0239..8be8b220 100644 --- a/src/finch/resources/sandbox/company.py +++ b/src/finch/resources/sandbox/company.py @@ -6,12 +6,13 @@ import httpx +from ... import _legacy_response from ...types import LocationParam from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..._base_client import ( make_request_options, ) @@ -25,6 +26,10 @@ class Company(SyncAPIResource): def with_raw_response(self) -> CompanyWithRawResponse: return CompanyWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> CompanyWithStreamingResponse: + return CompanyWithStreamingResponse(self) + def update( self, *, @@ -96,6 +101,10 @@ class AsyncCompany(AsyncAPIResource): def with_raw_response(self) -> AsyncCompanyWithRawResponse: return AsyncCompanyWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncCompanyWithStreamingResponse: + return AsyncCompanyWithStreamingResponse(self) + async def update( self, *, @@ -164,13 +173,35 @@ async def update( class CompanyWithRawResponse: def __init__(self, company: Company) -> None: - self.update = to_raw_response_wrapper( + self._company = company + + self.update = _legacy_response.to_raw_response_wrapper( company.update, ) class AsyncCompanyWithRawResponse: def __init__(self, company: AsyncCompany) -> None: - self.update = async_to_raw_response_wrapper( + self._company = company + + self.update = _legacy_response.async_to_raw_response_wrapper( + company.update, + ) + + +class CompanyWithStreamingResponse: + def __init__(self, company: Company) -> None: + self._company = company + + self.update = to_streamed_response_wrapper( + company.update, + ) + + +class AsyncCompanyWithStreamingResponse: + def __init__(self, company: AsyncCompany) -> None: + self._company = company + + self.update = async_to_streamed_response_wrapper( company.update, ) diff --git a/src/finch/resources/sandbox/connections/__init__.py b/src/finch/resources/sandbox/connections/__init__.py index 747635e9..053ae309 100644 --- a/src/finch/resources/sandbox/connections/__init__.py +++ b/src/finch/resources/sandbox/connections/__init__.py @@ -1,15 +1,33 @@ # File generated from our OpenAPI spec by Stainless. -from .accounts import Accounts, AsyncAccounts, AccountsWithRawResponse, AsyncAccountsWithRawResponse -from .connections import Connections, AsyncConnections, ConnectionsWithRawResponse, AsyncConnectionsWithRawResponse +from .accounts import ( + Accounts, + AsyncAccounts, + AccountsWithRawResponse, + AsyncAccountsWithRawResponse, + AccountsWithStreamingResponse, + AsyncAccountsWithStreamingResponse, +) +from .connections import ( + Connections, + AsyncConnections, + ConnectionsWithRawResponse, + AsyncConnectionsWithRawResponse, + ConnectionsWithStreamingResponse, + AsyncConnectionsWithStreamingResponse, +) __all__ = [ "Accounts", "AsyncAccounts", "AccountsWithRawResponse", "AsyncAccountsWithRawResponse", + "AccountsWithStreamingResponse", + "AsyncAccountsWithStreamingResponse", "Connections", "AsyncConnections", "ConnectionsWithRawResponse", "AsyncConnectionsWithRawResponse", + "ConnectionsWithStreamingResponse", + "AsyncConnectionsWithStreamingResponse", ] diff --git a/src/finch/resources/sandbox/connections/accounts.py b/src/finch/resources/sandbox/connections/accounts.py index 98008ee3..5de0e2b6 100644 --- a/src/finch/resources/sandbox/connections/accounts.py +++ b/src/finch/resources/sandbox/connections/accounts.py @@ -7,11 +7,12 @@ import httpx +from .... import _legacy_response from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...._base_client import ( make_request_options, ) @@ -31,12 +32,16 @@ class Accounts(SyncAPIResource): def with_raw_response(self) -> AccountsWithRawResponse: return AccountsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AccountsWithStreamingResponse: + return AccountsWithStreamingResponse(self) + def create( self, *, company_id: str, provider_id: str, - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, products: List[str] | 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. @@ -117,12 +122,16 @@ class AsyncAccounts(AsyncAPIResource): def with_raw_response(self) -> AsyncAccountsWithRawResponse: return AsyncAccountsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncAccountsWithStreamingResponse: + return AsyncAccountsWithStreamingResponse(self) + async def create( self, *, company_id: str, provider_id: str, - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, products: List[str] | 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. @@ -200,19 +209,47 @@ async def update( class AccountsWithRawResponse: def __init__(self, accounts: Accounts) -> None: - self.create = to_raw_response_wrapper( + self._accounts = accounts + + self.create = _legacy_response.to_raw_response_wrapper( accounts.create, ) - self.update = to_raw_response_wrapper( + self.update = _legacy_response.to_raw_response_wrapper( accounts.update, ) class AsyncAccountsWithRawResponse: def __init__(self, accounts: AsyncAccounts) -> None: - self.create = async_to_raw_response_wrapper( + self._accounts = accounts + + self.create = _legacy_response.async_to_raw_response_wrapper( + accounts.create, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + accounts.update, + ) + + +class AccountsWithStreamingResponse: + def __init__(self, accounts: Accounts) -> None: + self._accounts = accounts + + self.create = to_streamed_response_wrapper( + accounts.create, + ) + self.update = to_streamed_response_wrapper( + accounts.update, + ) + + +class AsyncAccountsWithStreamingResponse: + def __init__(self, accounts: AsyncAccounts) -> None: + self._accounts = accounts + + self.create = async_to_streamed_response_wrapper( accounts.create, ) - self.update = async_to_raw_response_wrapper( + self.update = async_to_streamed_response_wrapper( accounts.update, ) diff --git a/src/finch/resources/sandbox/connections/connections.py b/src/finch/resources/sandbox/connections/connections.py index b3b522bc..eaecbcbe 100644 --- a/src/finch/resources/sandbox/connections/connections.py +++ b/src/finch/resources/sandbox/connections/connections.py @@ -7,12 +7,20 @@ import httpx -from .accounts import Accounts, AsyncAccounts, AccountsWithRawResponse, AsyncAccountsWithRawResponse +from .... import _legacy_response +from .accounts import ( + Accounts, + AsyncAccounts, + AccountsWithRawResponse, + AsyncAccountsWithRawResponse, + AccountsWithStreamingResponse, + AsyncAccountsWithStreamingResponse, +) from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...._base_client import ( make_request_options, ) @@ -30,12 +38,16 @@ def accounts(self) -> Accounts: def with_raw_response(self) -> ConnectionsWithRawResponse: return ConnectionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ConnectionsWithStreamingResponse: + return ConnectionsWithStreamingResponse(self) + def create( self, *, provider_id: str, - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, - employer_size: int | NotGiven = NOT_GIVEN, + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, + employee_size: int | NotGiven = NOT_GIVEN, products: List[str] | 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. @@ -48,7 +60,7 @@ def create( Create a new connection (new company/provider pair) with a new account Args: - employer_size: Optional: the size of the employer to be created with this connection. Defaults + employee_size: Optional: the size of the employer to be created with this connection. Defaults to 20 extra_headers: Send extra headers @@ -65,7 +77,7 @@ def create( { "provider_id": provider_id, "authentication_type": authentication_type, - "employer_size": employer_size, + "employee_size": employee_size, "products": products, }, connection_create_params.ConnectionCreateParams, @@ -86,12 +98,16 @@ def accounts(self) -> AsyncAccounts: def with_raw_response(self) -> AsyncConnectionsWithRawResponse: return AsyncConnectionsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncConnectionsWithStreamingResponse: + return AsyncConnectionsWithStreamingResponse(self) + async def create( self, *, provider_id: str, - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, - employer_size: int | NotGiven = NOT_GIVEN, + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] | NotGiven = NOT_GIVEN, + employee_size: int | NotGiven = NOT_GIVEN, products: List[str] | 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. @@ -104,7 +120,7 @@ async def create( Create a new connection (new company/provider pair) with a new account Args: - employer_size: Optional: the size of the employer to be created with this connection. Defaults + employee_size: Optional: the size of the employer to be created with this connection. Defaults to 20 extra_headers: Send extra headers @@ -121,7 +137,7 @@ async def create( { "provider_id": provider_id, "authentication_type": authentication_type, - "employer_size": employer_size, + "employee_size": employee_size, "products": products, }, connection_create_params.ConnectionCreateParams, @@ -135,17 +151,51 @@ async def create( class ConnectionsWithRawResponse: def __init__(self, connections: Connections) -> None: - self.accounts = AccountsWithRawResponse(connections.accounts) + self._connections = connections - self.create = to_raw_response_wrapper( + self.create = _legacy_response.to_raw_response_wrapper( connections.create, ) + @cached_property + def accounts(self) -> AccountsWithRawResponse: + return AccountsWithRawResponse(self._connections.accounts) + class AsyncConnectionsWithRawResponse: def __init__(self, connections: AsyncConnections) -> None: - self.accounts = AsyncAccountsWithRawResponse(connections.accounts) + self._connections = connections - self.create = async_to_raw_response_wrapper( + self.create = _legacy_response.async_to_raw_response_wrapper( connections.create, ) + + @cached_property + def accounts(self) -> AsyncAccountsWithRawResponse: + return AsyncAccountsWithRawResponse(self._connections.accounts) + + +class ConnectionsWithStreamingResponse: + def __init__(self, connections: Connections) -> None: + self._connections = connections + + self.create = to_streamed_response_wrapper( + connections.create, + ) + + @cached_property + def accounts(self) -> AccountsWithStreamingResponse: + return AccountsWithStreamingResponse(self._connections.accounts) + + +class AsyncConnectionsWithStreamingResponse: + def __init__(self, connections: AsyncConnections) -> None: + self._connections = connections + + self.create = async_to_streamed_response_wrapper( + connections.create, + ) + + @cached_property + def accounts(self) -> AsyncAccountsWithStreamingResponse: + return AsyncAccountsWithStreamingResponse(self._connections.accounts) diff --git a/src/finch/resources/sandbox/directory.py b/src/finch/resources/sandbox/directory.py index 3249c71e..b9093be9 100644 --- a/src/finch/resources/sandbox/directory.py +++ b/src/finch/resources/sandbox/directory.py @@ -6,11 +6,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..._base_client import ( make_request_options, ) @@ -24,6 +25,10 @@ class Directory(SyncAPIResource): def with_raw_response(self) -> DirectoryWithRawResponse: return DirectoryWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> DirectoryWithStreamingResponse: + return DirectoryWithStreamingResponse(self) + def create( self, *, @@ -65,6 +70,10 @@ class AsyncDirectory(AsyncAPIResource): def with_raw_response(self) -> AsyncDirectoryWithRawResponse: return AsyncDirectoryWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncDirectoryWithStreamingResponse: + return AsyncDirectoryWithStreamingResponse(self) + async def create( self, *, @@ -103,13 +112,35 @@ async def create( class DirectoryWithRawResponse: def __init__(self, directory: Directory) -> None: - self.create = to_raw_response_wrapper( + self._directory = directory + + self.create = _legacy_response.to_raw_response_wrapper( directory.create, ) class AsyncDirectoryWithRawResponse: def __init__(self, directory: AsyncDirectory) -> None: - self.create = async_to_raw_response_wrapper( + self._directory = directory + + self.create = _legacy_response.async_to_raw_response_wrapper( + directory.create, + ) + + +class DirectoryWithStreamingResponse: + def __init__(self, directory: Directory) -> None: + self._directory = directory + + self.create = to_streamed_response_wrapper( + directory.create, + ) + + +class AsyncDirectoryWithStreamingResponse: + def __init__(self, directory: AsyncDirectory) -> None: + self._directory = directory + + self.create = async_to_streamed_response_wrapper( directory.create, ) diff --git a/src/finch/resources/sandbox/employment.py b/src/finch/resources/sandbox/employment.py index b38757b4..a26f8cf9 100644 --- a/src/finch/resources/sandbox/employment.py +++ b/src/finch/resources/sandbox/employment.py @@ -6,12 +6,13 @@ import httpx +from ... import _legacy_response from ...types import IncomeParam, LocationParam from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..._base_client import ( make_request_options, ) @@ -25,6 +26,10 @@ class Employment(SyncAPIResource): def with_raw_response(self) -> EmploymentWithRawResponse: return EmploymentWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> EmploymentWithStreamingResponse: + return EmploymentWithStreamingResponse(self) + def update( self, individual_id: str, @@ -96,6 +101,8 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not individual_id: + raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") return self._put( f"/sandbox/employment/{individual_id}", body=maybe_transform( @@ -131,6 +138,10 @@ class AsyncEmployment(AsyncAPIResource): def with_raw_response(self) -> AsyncEmploymentWithRawResponse: return AsyncEmploymentWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncEmploymentWithStreamingResponse: + return AsyncEmploymentWithStreamingResponse(self) + async def update( self, individual_id: str, @@ -202,6 +213,8 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not individual_id: + raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") return await self._put( f"/sandbox/employment/{individual_id}", body=maybe_transform( @@ -234,13 +247,35 @@ async def update( class EmploymentWithRawResponse: def __init__(self, employment: Employment) -> None: - self.update = to_raw_response_wrapper( + self._employment = employment + + self.update = _legacy_response.to_raw_response_wrapper( employment.update, ) class AsyncEmploymentWithRawResponse: def __init__(self, employment: AsyncEmployment) -> None: - self.update = async_to_raw_response_wrapper( + self._employment = employment + + self.update = _legacy_response.async_to_raw_response_wrapper( + employment.update, + ) + + +class EmploymentWithStreamingResponse: + def __init__(self, employment: Employment) -> None: + self._employment = employment + + self.update = to_streamed_response_wrapper( + employment.update, + ) + + +class AsyncEmploymentWithStreamingResponse: + def __init__(self, employment: AsyncEmployment) -> None: + self._employment = employment + + self.update = async_to_streamed_response_wrapper( employment.update, ) diff --git a/src/finch/resources/sandbox/individual.py b/src/finch/resources/sandbox/individual.py index d0da230a..5aa93cc6 100644 --- a/src/finch/resources/sandbox/individual.py +++ b/src/finch/resources/sandbox/individual.py @@ -7,12 +7,13 @@ import httpx +from ... import _legacy_response from ...types import LocationParam from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..._base_client import ( make_request_options, ) @@ -26,6 +27,10 @@ class Individual(SyncAPIResource): def with_raw_response(self) -> IndividualWithRawResponse: return IndividualWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> IndividualWithStreamingResponse: + return IndividualWithStreamingResponse(self) + def update( self, individual_id: str, @@ -95,6 +100,8 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not individual_id: + raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") return self._put( f"/sandbox/individual/{individual_id}", body=maybe_transform( @@ -126,6 +133,10 @@ class AsyncIndividual(AsyncAPIResource): def with_raw_response(self) -> AsyncIndividualWithRawResponse: return AsyncIndividualWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncIndividualWithStreamingResponse: + return AsyncIndividualWithStreamingResponse(self) + async def update( self, individual_id: str, @@ -195,6 +206,8 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ + if not individual_id: + raise ValueError(f"Expected a non-empty value for `individual_id` but received {individual_id!r}") return await self._put( f"/sandbox/individual/{individual_id}", body=maybe_transform( @@ -223,13 +236,35 @@ async def update( class IndividualWithRawResponse: def __init__(self, individual: Individual) -> None: - self.update = to_raw_response_wrapper( + self._individual = individual + + self.update = _legacy_response.to_raw_response_wrapper( individual.update, ) class AsyncIndividualWithRawResponse: def __init__(self, individual: AsyncIndividual) -> None: - self.update = async_to_raw_response_wrapper( + self._individual = individual + + self.update = _legacy_response.async_to_raw_response_wrapper( + individual.update, + ) + + +class IndividualWithStreamingResponse: + def __init__(self, individual: Individual) -> None: + self._individual = individual + + self.update = to_streamed_response_wrapper( + individual.update, + ) + + +class AsyncIndividualWithStreamingResponse: + def __init__(self, individual: AsyncIndividual) -> None: + self._individual = individual + + self.update = async_to_streamed_response_wrapper( individual.update, ) diff --git a/src/finch/resources/sandbox/jobs/__init__.py b/src/finch/resources/sandbox/jobs/__init__.py index 1a21f174..c4f1bba4 100644 --- a/src/finch/resources/sandbox/jobs/__init__.py +++ b/src/finch/resources/sandbox/jobs/__init__.py @@ -1,11 +1,20 @@ # File generated from our OpenAPI spec by Stainless. -from .jobs import Jobs, AsyncJobs, JobsWithRawResponse, AsyncJobsWithRawResponse +from .jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) from .configuration import ( Configuration, AsyncConfiguration, ConfigurationWithRawResponse, AsyncConfigurationWithRawResponse, + ConfigurationWithStreamingResponse, + AsyncConfigurationWithStreamingResponse, ) __all__ = [ @@ -13,8 +22,12 @@ "AsyncConfiguration", "ConfigurationWithRawResponse", "AsyncConfigurationWithRawResponse", + "ConfigurationWithStreamingResponse", + "AsyncConfigurationWithStreamingResponse", "Jobs", "AsyncJobs", "JobsWithRawResponse", "AsyncJobsWithRawResponse", + "JobsWithStreamingResponse", + "AsyncJobsWithStreamingResponse", ] diff --git a/src/finch/resources/sandbox/jobs/configuration.py b/src/finch/resources/sandbox/jobs/configuration.py index f3e8d6ab..de8e1c18 100644 --- a/src/finch/resources/sandbox/jobs/configuration.py +++ b/src/finch/resources/sandbox/jobs/configuration.py @@ -6,11 +6,12 @@ import httpx +from .... import _legacy_response from ...._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ...._utils import maybe_transform from ...._compat import cached_property from ...._resource import SyncAPIResource, AsyncAPIResource -from ...._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ...._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ...._base_client import ( make_request_options, ) @@ -24,6 +25,10 @@ class Configuration(SyncAPIResource): def with_raw_response(self) -> ConfigurationWithRawResponse: return ConfigurationWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> ConfigurationWithStreamingResponse: + return ConfigurationWithStreamingResponse(self) + def retrieve( self, *, @@ -88,6 +93,10 @@ class AsyncConfiguration(AsyncAPIResource): def with_raw_response(self) -> AsyncConfigurationWithRawResponse: return AsyncConfigurationWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncConfigurationWithStreamingResponse: + return AsyncConfigurationWithStreamingResponse(self) + async def retrieve( self, *, @@ -149,19 +158,47 @@ async def update( class ConfigurationWithRawResponse: def __init__(self, configuration: Configuration) -> None: - self.retrieve = to_raw_response_wrapper( + self._configuration = configuration + + self.retrieve = _legacy_response.to_raw_response_wrapper( configuration.retrieve, ) - self.update = to_raw_response_wrapper( + self.update = _legacy_response.to_raw_response_wrapper( configuration.update, ) class AsyncConfigurationWithRawResponse: def __init__(self, configuration: AsyncConfiguration) -> None: - self.retrieve = async_to_raw_response_wrapper( + self._configuration = configuration + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + configuration.retrieve, + ) + self.update = _legacy_response.async_to_raw_response_wrapper( + configuration.update, + ) + + +class ConfigurationWithStreamingResponse: + def __init__(self, configuration: Configuration) -> None: + self._configuration = configuration + + self.retrieve = to_streamed_response_wrapper( + configuration.retrieve, + ) + self.update = to_streamed_response_wrapper( + configuration.update, + ) + + +class AsyncConfigurationWithStreamingResponse: + def __init__(self, configuration: AsyncConfiguration) -> None: + self._configuration = configuration + + self.retrieve = async_to_streamed_response_wrapper( configuration.retrieve, ) - self.update = async_to_raw_response_wrapper( + self.update = async_to_streamed_response_wrapper( configuration.update, ) diff --git a/src/finch/resources/sandbox/jobs/jobs.py b/src/finch/resources/sandbox/jobs/jobs.py index 3f45e345..916e4057 100644 --- a/src/finch/resources/sandbox/jobs/jobs.py +++ b/src/finch/resources/sandbox/jobs/jobs.py @@ -9,6 +9,8 @@ AsyncConfiguration, ConfigurationWithRawResponse, AsyncConfigurationWithRawResponse, + ConfigurationWithStreamingResponse, + AsyncConfigurationWithStreamingResponse, ) __all__ = ["Jobs", "AsyncJobs"] @@ -23,6 +25,10 @@ def configuration(self) -> Configuration: def with_raw_response(self) -> JobsWithRawResponse: return JobsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> JobsWithStreamingResponse: + return JobsWithStreamingResponse(self) + class AsyncJobs(AsyncAPIResource): @cached_property @@ -33,12 +39,42 @@ def configuration(self) -> AsyncConfiguration: def with_raw_response(self) -> AsyncJobsWithRawResponse: return AsyncJobsWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncJobsWithStreamingResponse: + return AsyncJobsWithStreamingResponse(self) + class JobsWithRawResponse: def __init__(self, jobs: Jobs) -> None: - self.configuration = ConfigurationWithRawResponse(jobs.configuration) + self._jobs = jobs + + @cached_property + def configuration(self) -> ConfigurationWithRawResponse: + return ConfigurationWithRawResponse(self._jobs.configuration) class AsyncJobsWithRawResponse: def __init__(self, jobs: AsyncJobs) -> None: - self.configuration = AsyncConfigurationWithRawResponse(jobs.configuration) + self._jobs = jobs + + @cached_property + def configuration(self) -> AsyncConfigurationWithRawResponse: + return AsyncConfigurationWithRawResponse(self._jobs.configuration) + + +class JobsWithStreamingResponse: + def __init__(self, jobs: Jobs) -> None: + self._jobs = jobs + + @cached_property + def configuration(self) -> ConfigurationWithStreamingResponse: + return ConfigurationWithStreamingResponse(self._jobs.configuration) + + +class AsyncJobsWithStreamingResponse: + def __init__(self, jobs: AsyncJobs) -> None: + self._jobs = jobs + + @cached_property + def configuration(self) -> AsyncConfigurationWithStreamingResponse: + return AsyncConfigurationWithStreamingResponse(self._jobs.configuration) diff --git a/src/finch/resources/sandbox/payment.py b/src/finch/resources/sandbox/payment.py index 18753bd6..020de8b8 100644 --- a/src/finch/resources/sandbox/payment.py +++ b/src/finch/resources/sandbox/payment.py @@ -6,11 +6,12 @@ import httpx +from ... import _legacy_response from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven from ..._utils import maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import to_raw_response_wrapper, async_to_raw_response_wrapper +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper from ..._base_client import ( make_request_options, ) @@ -24,6 +25,10 @@ class Payment(SyncAPIResource): def with_raw_response(self) -> PaymentWithRawResponse: return PaymentWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> PaymentWithStreamingResponse: + return PaymentWithStreamingResponse(self) + def create( self, *, @@ -71,6 +76,10 @@ class AsyncPayment(AsyncAPIResource): def with_raw_response(self) -> AsyncPaymentWithRawResponse: return AsyncPaymentWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncPaymentWithStreamingResponse: + return AsyncPaymentWithStreamingResponse(self) + async def create( self, *, @@ -115,13 +124,35 @@ async def create( class PaymentWithRawResponse: def __init__(self, payment: Payment) -> None: - self.create = to_raw_response_wrapper( + self._payment = payment + + self.create = _legacy_response.to_raw_response_wrapper( payment.create, ) class AsyncPaymentWithRawResponse: def __init__(self, payment: AsyncPayment) -> None: - self.create = async_to_raw_response_wrapper( + self._payment = payment + + self.create = _legacy_response.async_to_raw_response_wrapper( + payment.create, + ) + + +class PaymentWithStreamingResponse: + def __init__(self, payment: Payment) -> None: + self._payment = payment + + self.create = to_streamed_response_wrapper( + payment.create, + ) + + +class AsyncPaymentWithStreamingResponse: + def __init__(self, payment: AsyncPayment) -> None: + self._payment = payment + + self.create = async_to_streamed_response_wrapper( payment.create, ) diff --git a/src/finch/resources/sandbox/sandbox.py b/src/finch/resources/sandbox/sandbox.py index 23b1994d..9daf72aa 100644 --- a/src/finch/resources/sandbox/sandbox.py +++ b/src/finch/resources/sandbox/sandbox.py @@ -2,16 +2,65 @@ from __future__ import annotations -from .jobs import Jobs, AsyncJobs, JobsWithRawResponse, AsyncJobsWithRawResponse -from .company import Company, AsyncCompany, CompanyWithRawResponse, AsyncCompanyWithRawResponse -from .payment import Payment, AsyncPayment, PaymentWithRawResponse, AsyncPaymentWithRawResponse +from .jobs import ( + Jobs, + AsyncJobs, + JobsWithRawResponse, + AsyncJobsWithRawResponse, + JobsWithStreamingResponse, + AsyncJobsWithStreamingResponse, +) +from .company import ( + Company, + AsyncCompany, + CompanyWithRawResponse, + AsyncCompanyWithRawResponse, + CompanyWithStreamingResponse, + AsyncCompanyWithStreamingResponse, +) +from .payment import ( + Payment, + AsyncPayment, + PaymentWithRawResponse, + AsyncPaymentWithRawResponse, + PaymentWithStreamingResponse, + AsyncPaymentWithStreamingResponse, +) from ..._compat import cached_property -from .directory import Directory, AsyncDirectory, DirectoryWithRawResponse, AsyncDirectoryWithRawResponse +from .directory import ( + Directory, + AsyncDirectory, + DirectoryWithRawResponse, + AsyncDirectoryWithRawResponse, + DirectoryWithStreamingResponse, + AsyncDirectoryWithStreamingResponse, +) from .jobs.jobs import Jobs, AsyncJobs -from .employment import Employment, AsyncEmployment, EmploymentWithRawResponse, AsyncEmploymentWithRawResponse -from .individual import Individual, AsyncIndividual, IndividualWithRawResponse, AsyncIndividualWithRawResponse +from .employment import ( + Employment, + AsyncEmployment, + EmploymentWithRawResponse, + AsyncEmploymentWithRawResponse, + EmploymentWithStreamingResponse, + AsyncEmploymentWithStreamingResponse, +) +from .individual import ( + Individual, + AsyncIndividual, + IndividualWithRawResponse, + AsyncIndividualWithRawResponse, + IndividualWithStreamingResponse, + AsyncIndividualWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource -from .connections import Connections, AsyncConnections, ConnectionsWithRawResponse, AsyncConnectionsWithRawResponse +from .connections import ( + Connections, + AsyncConnections, + ConnectionsWithRawResponse, + AsyncConnectionsWithRawResponse, + ConnectionsWithStreamingResponse, + AsyncConnectionsWithStreamingResponse, +) from .connections.connections import Connections, AsyncConnections __all__ = ["Sandbox", "AsyncSandbox"] @@ -50,6 +99,10 @@ def jobs(self) -> Jobs: def with_raw_response(self) -> SandboxWithRawResponse: return SandboxWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> SandboxWithStreamingResponse: + return SandboxWithStreamingResponse(self) + class AsyncSandbox(AsyncAPIResource): @cached_property @@ -84,24 +137,138 @@ def jobs(self) -> AsyncJobs: def with_raw_response(self) -> AsyncSandboxWithRawResponse: return AsyncSandboxWithRawResponse(self) + @cached_property + def with_streaming_response(self) -> AsyncSandboxWithStreamingResponse: + return AsyncSandboxWithStreamingResponse(self) + class SandboxWithRawResponse: def __init__(self, sandbox: Sandbox) -> None: - self.connections = ConnectionsWithRawResponse(sandbox.connections) - self.company = CompanyWithRawResponse(sandbox.company) - self.directory = DirectoryWithRawResponse(sandbox.directory) - self.individual = IndividualWithRawResponse(sandbox.individual) - self.employment = EmploymentWithRawResponse(sandbox.employment) - self.payment = PaymentWithRawResponse(sandbox.payment) - self.jobs = JobsWithRawResponse(sandbox.jobs) + self._sandbox = sandbox + + @cached_property + def connections(self) -> ConnectionsWithRawResponse: + return ConnectionsWithRawResponse(self._sandbox.connections) + + @cached_property + def company(self) -> CompanyWithRawResponse: + return CompanyWithRawResponse(self._sandbox.company) + + @cached_property + def directory(self) -> DirectoryWithRawResponse: + return DirectoryWithRawResponse(self._sandbox.directory) + + @cached_property + def individual(self) -> IndividualWithRawResponse: + return IndividualWithRawResponse(self._sandbox.individual) + + @cached_property + def employment(self) -> EmploymentWithRawResponse: + return EmploymentWithRawResponse(self._sandbox.employment) + + @cached_property + def payment(self) -> PaymentWithRawResponse: + return PaymentWithRawResponse(self._sandbox.payment) + + @cached_property + def jobs(self) -> JobsWithRawResponse: + return JobsWithRawResponse(self._sandbox.jobs) class AsyncSandboxWithRawResponse: def __init__(self, sandbox: AsyncSandbox) -> None: - self.connections = AsyncConnectionsWithRawResponse(sandbox.connections) - self.company = AsyncCompanyWithRawResponse(sandbox.company) - self.directory = AsyncDirectoryWithRawResponse(sandbox.directory) - self.individual = AsyncIndividualWithRawResponse(sandbox.individual) - self.employment = AsyncEmploymentWithRawResponse(sandbox.employment) - self.payment = AsyncPaymentWithRawResponse(sandbox.payment) - self.jobs = AsyncJobsWithRawResponse(sandbox.jobs) + self._sandbox = sandbox + + @cached_property + def connections(self) -> AsyncConnectionsWithRawResponse: + return AsyncConnectionsWithRawResponse(self._sandbox.connections) + + @cached_property + def company(self) -> AsyncCompanyWithRawResponse: + return AsyncCompanyWithRawResponse(self._sandbox.company) + + @cached_property + def directory(self) -> AsyncDirectoryWithRawResponse: + return AsyncDirectoryWithRawResponse(self._sandbox.directory) + + @cached_property + def individual(self) -> AsyncIndividualWithRawResponse: + return AsyncIndividualWithRawResponse(self._sandbox.individual) + + @cached_property + def employment(self) -> AsyncEmploymentWithRawResponse: + return AsyncEmploymentWithRawResponse(self._sandbox.employment) + + @cached_property + def payment(self) -> AsyncPaymentWithRawResponse: + return AsyncPaymentWithRawResponse(self._sandbox.payment) + + @cached_property + def jobs(self) -> AsyncJobsWithRawResponse: + return AsyncJobsWithRawResponse(self._sandbox.jobs) + + +class SandboxWithStreamingResponse: + def __init__(self, sandbox: Sandbox) -> None: + self._sandbox = sandbox + + @cached_property + def connections(self) -> ConnectionsWithStreamingResponse: + return ConnectionsWithStreamingResponse(self._sandbox.connections) + + @cached_property + def company(self) -> CompanyWithStreamingResponse: + return CompanyWithStreamingResponse(self._sandbox.company) + + @cached_property + def directory(self) -> DirectoryWithStreamingResponse: + return DirectoryWithStreamingResponse(self._sandbox.directory) + + @cached_property + def individual(self) -> IndividualWithStreamingResponse: + return IndividualWithStreamingResponse(self._sandbox.individual) + + @cached_property + def employment(self) -> EmploymentWithStreamingResponse: + return EmploymentWithStreamingResponse(self._sandbox.employment) + + @cached_property + def payment(self) -> PaymentWithStreamingResponse: + return PaymentWithStreamingResponse(self._sandbox.payment) + + @cached_property + def jobs(self) -> JobsWithStreamingResponse: + return JobsWithStreamingResponse(self._sandbox.jobs) + + +class AsyncSandboxWithStreamingResponse: + def __init__(self, sandbox: AsyncSandbox) -> None: + self._sandbox = sandbox + + @cached_property + def connections(self) -> AsyncConnectionsWithStreamingResponse: + return AsyncConnectionsWithStreamingResponse(self._sandbox.connections) + + @cached_property + def company(self) -> AsyncCompanyWithStreamingResponse: + return AsyncCompanyWithStreamingResponse(self._sandbox.company) + + @cached_property + def directory(self) -> AsyncDirectoryWithStreamingResponse: + return AsyncDirectoryWithStreamingResponse(self._sandbox.directory) + + @cached_property + def individual(self) -> AsyncIndividualWithStreamingResponse: + return AsyncIndividualWithStreamingResponse(self._sandbox.individual) + + @cached_property + def employment(self) -> AsyncEmploymentWithStreamingResponse: + return AsyncEmploymentWithStreamingResponse(self._sandbox.employment) + + @cached_property + def payment(self) -> AsyncPaymentWithStreamingResponse: + return AsyncPaymentWithStreamingResponse(self._sandbox.payment) + + @cached_property + def jobs(self) -> AsyncJobsWithStreamingResponse: + return AsyncJobsWithStreamingResponse(self._sandbox.jobs) diff --git a/src/finch/types/account_update_event.py b/src/finch/types/account_update_event.py index e87a6997..449da582 100644 --- a/src/finch/types/account_update_event.py +++ b/src/finch/types/account_update_event.py @@ -38,6 +38,7 @@ "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatements", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEarnings", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions", + "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayment", @@ -291,6 +292,16 @@ class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPaySt type: Optional[bool] = None +class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions( + BaseModel +): + amount: Optional[bool] = None + + currency: Optional[bool] = None + + name: Optional[bool] = None + + class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions(BaseModel): amount: Optional[bool] = None @@ -320,9 +331,14 @@ class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPaySt AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions ] = None + employer_contributions: Optional[ + AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions + ] = None + employer_deductions: Optional[ AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions ] = None + """[DEPRECATED] Use `employer_contributions` instead""" gross_pay: Optional[bool] = None diff --git a/src/finch/types/provider.py b/src/finch/types/provider.py index f41eae12..87d346ee 100644 --- a/src/finch/types/provider.py +++ b/src/finch/types/provider.py @@ -35,6 +35,7 @@ "AuthenticationMethodSupportedFieldsPayStatementPayStatements", "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEarnings", "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions", + "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions", "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions", "AuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes", "AuthenticationMethodSupportedFieldsPayment", @@ -288,6 +289,14 @@ class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeduct type: Optional[bool] = None +class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions(BaseModel): + amount: Optional[bool] = None + + currency: Optional[bool] = None + + name: Optional[bool] = None + + class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions(BaseModel): amount: Optional[bool] = None @@ -313,7 +322,12 @@ class AuthenticationMethodSupportedFieldsPayStatementPayStatements(BaseModel): employee_deductions: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions] = None + employer_contributions: Optional[ + AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions + ] = None + employer_deductions: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions] = None + """[DEPRECATED] Use `employer_contributions` instead""" gross_pay: Optional[bool] = None diff --git a/src/finch/types/sandbox/connection_create_params.py b/src/finch/types/sandbox/connection_create_params.py index bd2908e8..d6c29927 100644 --- a/src/finch/types/sandbox/connection_create_params.py +++ b/src/finch/types/sandbox/connection_create_params.py @@ -11,9 +11,9 @@ class ConnectionCreateParams(TypedDict, total=False): provider_id: Required[str] - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] - employer_size: int + employee_size: int """Optional: the size of the employer to be created with this connection. Defaults to 20 diff --git a/src/finch/types/sandbox/connection_create_response.py b/src/finch/types/sandbox/connection_create_response.py index bc31f5b5..fed64a63 100644 --- a/src/finch/types/sandbox/connection_create_response.py +++ b/src/finch/types/sandbox/connection_create_response.py @@ -13,7 +13,7 @@ class ConnectionCreateResponse(BaseModel): account_id: str - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] company_id: str diff --git a/src/finch/types/sandbox/connections/account_create_params.py b/src/finch/types/sandbox/connections/account_create_params.py index bc6e5bf4..9dff294e 100644 --- a/src/finch/types/sandbox/connections/account_create_params.py +++ b/src/finch/types/sandbox/connections/account_create_params.py @@ -13,7 +13,7 @@ class AccountCreateParams(TypedDict, total=False): provider_id: Required[str] - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] products: List[str] """ diff --git a/src/finch/types/sandbox/connections/account_create_response.py b/src/finch/types/sandbox/connections/account_create_response.py index 5bb2c84f..d1d32ad7 100644 --- a/src/finch/types/sandbox/connections/account_create_response.py +++ b/src/finch/types/sandbox/connections/account_create_response.py @@ -13,7 +13,7 @@ class AccountCreateResponse(BaseModel): account_id: str - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] company_id: str diff --git a/src/finch/types/sandbox/connections/account_update_response.py b/src/finch/types/sandbox/connections/account_update_response.py index 2e8cae7e..2fe7507d 100644 --- a/src/finch/types/sandbox/connections/account_update_response.py +++ b/src/finch/types/sandbox/connections/account_update_response.py @@ -11,7 +11,7 @@ class AccountUpdateResponse(BaseModel): account_id: str - authentication_type: Literal["credentials", "api_token", "oauth", "assisted"] + authentication_type: Literal["credential", "api_token", "oauth", "assisted"] company_id: str diff --git a/tests/api_resources/hris/benefits/test_individuals.py b/tests/api_resources/hris/benefits/test_individuals.py index 3c45aa12..00e182a5 100644 --- a/tests/api_resources/hris/benefits/test_individuals.py +++ b/tests/api_resources/hris/benefits/test_individuals.py @@ -3,12 +3,12 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncSinglePage, AsyncSinglePage from finch.types.hris.benefits import ( IndividualBenefit, @@ -18,13 +18,10 @@ ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestIndividuals: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_enroll_many(self, client: Finch) -> None: @@ -40,10 +37,34 @@ def test_raw_response_enroll_many(self, client: Finch) -> None: "string", individuals=[{}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(SyncSinglePage[EnrolledIndividual], individual, path=["response"]) + @parametrize + def test_streaming_response_enroll_many(self, client: Finch) -> None: + with client.hris.benefits.individuals.with_streaming_response.enroll_many( + "string", + individuals=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = response.parse() + assert_matches_type(SyncSinglePage[EnrolledIndividual], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_enroll_many(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + client.hris.benefits.individuals.with_raw_response.enroll_many( + "", + individuals=[{}], + ) + @parametrize def test_method_enrolled_ids(self, client: Finch) -> None: individual = client.hris.benefits.individuals.enrolled_ids( @@ -56,10 +77,32 @@ def test_raw_response_enrolled_ids(self, client: Finch) -> None: response = client.hris.benefits.individuals.with_raw_response.enrolled_ids( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(IndividualEnrolledIDsResponse, individual, path=["response"]) + @parametrize + def test_streaming_response_enrolled_ids(self, client: Finch) -> None: + with client.hris.benefits.individuals.with_streaming_response.enrolled_ids( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = response.parse() + assert_matches_type(IndividualEnrolledIDsResponse, individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_enrolled_ids(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + client.hris.benefits.individuals.with_raw_response.enrolled_ids( + "", + ) + @parametrize def test_method_retrieve_many_benefits(self, client: Finch) -> None: individual = client.hris.benefits.individuals.retrieve_many_benefits( @@ -80,10 +123,32 @@ def test_raw_response_retrieve_many_benefits(self, client: Finch) -> None: response = client.hris.benefits.individuals.with_raw_response.retrieve_many_benefits( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(SyncSinglePage[IndividualBenefit], individual, path=["response"]) + @parametrize + def test_streaming_response_retrieve_many_benefits(self, client: Finch) -> None: + with client.hris.benefits.individuals.with_streaming_response.retrieve_many_benefits( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = response.parse() + assert_matches_type(SyncSinglePage[IndividualBenefit], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve_many_benefits(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + client.hris.benefits.individuals.with_raw_response.retrieve_many_benefits( + "", + ) + @parametrize def test_method_unenroll_many(self, client: Finch) -> None: individual = client.hris.benefits.individuals.unenroll_many( @@ -104,94 +169,204 @@ def test_raw_response_unenroll_many(self, client: Finch) -> None: response = client.hris.benefits.individuals.with_raw_response.unenroll_many( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(SyncSinglePage[UnenrolledIndividual], individual, path=["response"]) + @parametrize + def test_streaming_response_unenroll_many(self, client: Finch) -> None: + with client.hris.benefits.individuals.with_streaming_response.unenroll_many( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = response.parse() + assert_matches_type(SyncSinglePage[UnenrolledIndividual], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_unenroll_many(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + client.hris.benefits.individuals.with_raw_response.unenroll_many( + "", + ) + class TestAsyncIndividuals: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_enroll_many(self, client: AsyncFinch) -> None: - individual = await client.hris.benefits.individuals.enroll_many( + async def test_method_enroll_many(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.benefits.individuals.enroll_many( "string", individuals=[{}], ) assert_matches_type(AsyncSinglePage[EnrolledIndividual], individual, path=["response"]) @parametrize - async def test_raw_response_enroll_many(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.individuals.with_raw_response.enroll_many( + async def test_raw_response_enroll_many(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.individuals.with_raw_response.enroll_many( "string", individuals=[{}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(AsyncSinglePage[EnrolledIndividual], individual, path=["response"]) @parametrize - async def test_method_enrolled_ids(self, client: AsyncFinch) -> None: - individual = await client.hris.benefits.individuals.enrolled_ids( + async def test_streaming_response_enroll_many(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.individuals.with_streaming_response.enroll_many( + "string", + individuals=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = await response.parse() + assert_matches_type(AsyncSinglePage[EnrolledIndividual], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_enroll_many(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + await async_client.hris.benefits.individuals.with_raw_response.enroll_many( + "", + individuals=[{}], + ) + + @parametrize + async def test_method_enrolled_ids(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.benefits.individuals.enrolled_ids( "string", ) assert_matches_type(IndividualEnrolledIDsResponse, individual, path=["response"]) @parametrize - async def test_raw_response_enrolled_ids(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.individuals.with_raw_response.enrolled_ids( + async def test_raw_response_enrolled_ids(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.individuals.with_raw_response.enrolled_ids( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(IndividualEnrolledIDsResponse, individual, path=["response"]) @parametrize - async def test_method_retrieve_many_benefits(self, client: AsyncFinch) -> None: - individual = await client.hris.benefits.individuals.retrieve_many_benefits( + async def test_streaming_response_enrolled_ids(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.individuals.with_streaming_response.enrolled_ids( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = await response.parse() + assert_matches_type(IndividualEnrolledIDsResponse, individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_enrolled_ids(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + await async_client.hris.benefits.individuals.with_raw_response.enrolled_ids( + "", + ) + + @parametrize + async def test_method_retrieve_many_benefits(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.benefits.individuals.retrieve_many_benefits( "string", ) assert_matches_type(AsyncSinglePage[IndividualBenefit], individual, path=["response"]) @parametrize - async def test_method_retrieve_many_benefits_with_all_params(self, client: AsyncFinch) -> None: - individual = await client.hris.benefits.individuals.retrieve_many_benefits( + async def test_method_retrieve_many_benefits_with_all_params(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.benefits.individuals.retrieve_many_benefits( "string", individual_ids="d675d2b7-6d7b-41a8-b2d3-001eb3fb88f6,d02a6346-1f08-4312-a064-49ff3cafaa7a", ) assert_matches_type(AsyncSinglePage[IndividualBenefit], individual, path=["response"]) @parametrize - async def test_raw_response_retrieve_many_benefits(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.individuals.with_raw_response.retrieve_many_benefits( + async def test_raw_response_retrieve_many_benefits(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.individuals.with_raw_response.retrieve_many_benefits( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(AsyncSinglePage[IndividualBenefit], individual, path=["response"]) @parametrize - async def test_method_unenroll_many(self, client: AsyncFinch) -> None: - individual = await client.hris.benefits.individuals.unenroll_many( + async def test_streaming_response_retrieve_many_benefits(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.individuals.with_streaming_response.retrieve_many_benefits( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = await response.parse() + assert_matches_type(AsyncSinglePage[IndividualBenefit], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve_many_benefits(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + await async_client.hris.benefits.individuals.with_raw_response.retrieve_many_benefits( + "", + ) + + @parametrize + async def test_method_unenroll_many(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.benefits.individuals.unenroll_many( "string", ) assert_matches_type(AsyncSinglePage[UnenrolledIndividual], individual, path=["response"]) @parametrize - async def test_method_unenroll_many_with_all_params(self, client: AsyncFinch) -> None: - individual = await client.hris.benefits.individuals.unenroll_many( + async def test_method_unenroll_many_with_all_params(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.benefits.individuals.unenroll_many( "string", individual_ids=["string", "string", "string"], ) assert_matches_type(AsyncSinglePage[UnenrolledIndividual], individual, path=["response"]) @parametrize - async def test_raw_response_unenroll_many(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.individuals.with_raw_response.unenroll_many( + async def test_raw_response_unenroll_many(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.individuals.with_raw_response.unenroll_many( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(AsyncSinglePage[UnenrolledIndividual], individual, path=["response"]) + + @parametrize + async def test_streaming_response_unenroll_many(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.individuals.with_streaming_response.unenroll_many( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = await response.parse() + assert_matches_type(AsyncSinglePage[UnenrolledIndividual], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_unenroll_many(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + await async_client.hris.benefits.individuals.with_raw_response.unenroll_many( + "", + ) diff --git a/tests/api_resources/hris/test_benefits.py b/tests/api_resources/hris/test_benefits.py index 77f4caff..8d7d6510 100644 --- a/tests/api_resources/hris/test_benefits.py +++ b/tests/api_resources/hris/test_benefits.py @@ -3,12 +3,12 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncSinglePage, AsyncSinglePage from finch.types.hris import ( CompanyBenefit, @@ -18,13 +18,10 @@ ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestBenefits: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_create(self, client: Finch) -> None: @@ -43,10 +40,23 @@ def test_method_create_with_all_params(self, client: Finch) -> None: @parametrize def test_raw_response_create(self, client: Finch) -> None: response = client.hris.benefits.with_raw_response.create() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: Finch) -> None: + with client.hris.benefits.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = response.parse() + assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: Finch) -> None: benefit = client.hris.benefits.retrieve( @@ -59,10 +69,32 @@ def test_raw_response_retrieve(self, client: Finch) -> None: response = client.hris.benefits.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(CompanyBenefit, benefit, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: Finch) -> None: + with client.hris.benefits.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = response.parse() + assert_matches_type(CompanyBenefit, benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + client.hris.benefits.with_raw_response.retrieve( + "", + ) + @parametrize def test_method_update(self, client: Finch) -> None: benefit = client.hris.benefits.update( @@ -83,10 +115,32 @@ def test_raw_response_update(self, client: Finch) -> None: response = client.hris.benefits.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(UpdateCompanyBenefitResponse, benefit, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: Finch) -> None: + with client.hris.benefits.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = response.parse() + assert_matches_type(UpdateCompanyBenefitResponse, benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + client.hris.benefits.with_raw_response.update( + "", + ) + @parametrize def test_method_list(self, client: Finch) -> None: benefit = client.hris.benefits.list() @@ -95,10 +149,23 @@ def test_method_list(self, client: Finch) -> None: @parametrize def test_raw_response_list(self, client: Finch) -> None: response = client.hris.benefits.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(SyncSinglePage[CompanyBenefit], benefit, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: Finch) -> None: + with client.hris.benefits.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = response.parse() + assert_matches_type(SyncSinglePage[CompanyBenefit], benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list_supported_benefits(self, client: Finch) -> None: benefit = client.hris.benefits.list_supported_benefits() @@ -107,24 +174,35 @@ def test_method_list_supported_benefits(self, client: Finch) -> None: @parametrize def test_raw_response_list_supported_benefits(self, client: Finch) -> None: response = client.hris.benefits.with_raw_response.list_supported_benefits() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(SyncSinglePage[SupportedBenefit], benefit, path=["response"]) + @parametrize + def test_streaming_response_list_supported_benefits(self, client: Finch) -> None: + with client.hris.benefits.with_streaming_response.list_supported_benefits() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = response.parse() + assert_matches_type(SyncSinglePage[SupportedBenefit], benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncBenefits: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, client: AsyncFinch) -> None: - benefit = await client.hris.benefits.create() + async def test_method_create(self, async_client: AsyncFinch) -> None: + benefit = await async_client.hris.benefits.create() assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, client: AsyncFinch) -> None: - benefit = await client.hris.benefits.create( + async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: + benefit = await async_client.hris.benefits.create( description="string", frequency="one_time", type="401k", @@ -132,72 +210,155 @@ async def test_method_create_with_all_params(self, client: AsyncFinch) -> None: assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) @parametrize - async def test_raw_response_create(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.with_raw_response.create() + async def test_raw_response_create(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.with_raw_response.create() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) @parametrize - async def test_method_retrieve(self, client: AsyncFinch) -> None: - benefit = await client.hris.benefits.retrieve( + async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = await response.parse() + assert_matches_type(CreateCompanyBenefitsResponse, benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncFinch) -> None: + benefit = await async_client.hris.benefits.retrieve( "string", ) assert_matches_type(CompanyBenefit, benefit, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.with_raw_response.retrieve( + async def test_raw_response_retrieve(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(CompanyBenefit, benefit, path=["response"]) @parametrize - async def test_method_update(self, client: AsyncFinch) -> None: - benefit = await client.hris.benefits.update( + async def test_streaming_response_retrieve(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = await response.parse() + assert_matches_type(CompanyBenefit, benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + await async_client.hris.benefits.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_update(self, async_client: AsyncFinch) -> None: + benefit = await async_client.hris.benefits.update( "string", ) assert_matches_type(UpdateCompanyBenefitResponse, benefit, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: - benefit = await client.hris.benefits.update( + async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> None: + benefit = await async_client.hris.benefits.update( "string", description="string", ) assert_matches_type(UpdateCompanyBenefitResponse, benefit, path=["response"]) @parametrize - async def test_raw_response_update(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.with_raw_response.update( + async def test_raw_response_update(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(UpdateCompanyBenefitResponse, benefit, path=["response"]) @parametrize - async def test_method_list(self, client: AsyncFinch) -> None: - benefit = await client.hris.benefits.list() + async def test_streaming_response_update(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = await response.parse() + assert_matches_type(UpdateCompanyBenefitResponse, benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `benefit_id` but received ''"): + await async_client.hris.benefits.with_raw_response.update( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncFinch) -> None: + benefit = await async_client.hris.benefits.list() assert_matches_type(AsyncSinglePage[CompanyBenefit], benefit, path=["response"]) @parametrize - async def test_raw_response_list(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.with_raw_response.list() + async def test_raw_response_list(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(AsyncSinglePage[CompanyBenefit], benefit, path=["response"]) @parametrize - async def test_method_list_supported_benefits(self, client: AsyncFinch) -> None: - benefit = await client.hris.benefits.list_supported_benefits() + async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = await response.parse() + assert_matches_type(AsyncSinglePage[CompanyBenefit], benefit, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @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[SupportedBenefit], benefit, path=["response"]) @parametrize - async def test_raw_response_list_supported_benefits(self, client: AsyncFinch) -> None: - response = await client.hris.benefits.with_raw_response.list_supported_benefits() + async def test_raw_response_list_supported_benefits(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.benefits.with_raw_response.list_supported_benefits() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" benefit = response.parse() assert_matches_type(AsyncSinglePage[SupportedBenefit], benefit, path=["response"]) + + @parametrize + async def test_streaming_response_list_supported_benefits(self, async_client: AsyncFinch) -> None: + async with async_client.hris.benefits.with_streaming_response.list_supported_benefits() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + benefit = await response.parse() + 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 2f51d84f..d9f50bb0 100644 --- a/tests/api_resources/hris/test_company.py +++ b/tests/api_resources/hris/test_company.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.hris import Company base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestCompany: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_retrieve(self, client: Finch) -> None: @@ -28,24 +25,48 @@ def test_method_retrieve(self, client: Finch) -> None: @parametrize def test_raw_response_retrieve(self, client: Finch) -> None: response = client.hris.company.with_raw_response.retrieve() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" company = response.parse() assert_matches_type(Company, company, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: Finch) -> None: + with client.hris.company.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = response.parse() + assert_matches_type(Company, company, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncCompany: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, client: AsyncFinch) -> None: - company = await client.hris.company.retrieve() + async def test_method_retrieve(self, async_client: AsyncFinch) -> None: + company = await async_client.hris.company.retrieve() assert_matches_type(Company, company, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, client: AsyncFinch) -> None: - response = await client.hris.company.with_raw_response.retrieve() + async def test_raw_response_retrieve(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.company.with_raw_response.retrieve() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" company = response.parse() assert_matches_type(Company, company, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncFinch) -> None: + async with async_client.hris.company.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = await response.parse() + assert_matches_type(Company, company, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/hris/test_directory.py b/tests/api_resources/hris/test_directory.py index 53dd541d..92308518 100644 --- a/tests/api_resources/hris/test_directory.py +++ b/tests/api_resources/hris/test_directory.py @@ -3,25 +3,22 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncIndividualsPage, AsyncIndividualsPage from finch.types.hris import IndividualInDirectory # pyright: reportDeprecated=false base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestDirectory: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_list(self, client: Finch) -> None: @@ -39,14 +36,28 @@ def test_method_list_with_all_params(self, client: Finch) -> None: @parametrize def test_raw_response_list(self, client: Finch) -> None: response = client.hris.directory.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: Finch) -> None: + with client.hris.directory.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + directory = response.parse() + assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_list_individuals(self, client: Finch) -> None: with pytest.warns(DeprecationWarning): directory = client.hris.directory.list_individuals() + assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) @parametrize @@ -56,61 +67,103 @@ def test_method_list_individuals_with_all_params(self, client: Finch) -> None: limit=0, offset=0, ) + assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) @parametrize def test_raw_response_list_individuals(self, client: Finch) -> None: with pytest.warns(DeprecationWarning): response = client.hris.directory.with_raw_response.list_individuals() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + @parametrize + def test_streaming_response_list_individuals(self, client: Finch) -> None: + with pytest.warns(DeprecationWarning): + with client.hris.directory.with_streaming_response.list_individuals() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + directory = response.parse() + assert_matches_type(SyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncDirectory: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_list(self, client: AsyncFinch) -> None: - directory = await client.hris.directory.list() + async def test_method_list(self, async_client: AsyncFinch) -> None: + directory = await async_client.hris.directory.list() assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) @parametrize - async def test_method_list_with_all_params(self, client: AsyncFinch) -> None: - directory = await client.hris.directory.list( + async def test_method_list_with_all_params(self, async_client: AsyncFinch) -> None: + directory = await async_client.hris.directory.list( limit=0, offset=0, ) assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) @parametrize - async def test_raw_response_list(self, client: AsyncFinch) -> None: - response = await client.hris.directory.with_raw_response.list() + async def test_raw_response_list(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.directory.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) @parametrize - async def test_method_list_individuals(self, client: AsyncFinch) -> None: + async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: + async with async_client.hris.directory.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + directory = await response.parse() + assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_list_individuals(self, async_client: AsyncFinch) -> None: with pytest.warns(DeprecationWarning): - directory = await client.hris.directory.list_individuals() + directory = await async_client.hris.directory.list_individuals() + assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) @parametrize - async def test_method_list_individuals_with_all_params(self, client: AsyncFinch) -> None: + async def test_method_list_individuals_with_all_params(self, async_client: AsyncFinch) -> None: with pytest.warns(DeprecationWarning): - directory = await client.hris.directory.list_individuals( + directory = await async_client.hris.directory.list_individuals( limit=0, offset=0, ) + assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) @parametrize - async def test_raw_response_list_individuals(self, client: AsyncFinch) -> None: + async def test_raw_response_list_individuals(self, async_client: AsyncFinch) -> None: with pytest.warns(DeprecationWarning): - response = await client.hris.directory.with_raw_response.list_individuals() + response = await async_client.hris.directory.with_raw_response.list_individuals() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + + @parametrize + async def test_streaming_response_list_individuals(self, async_client: AsyncFinch) -> None: + with pytest.warns(DeprecationWarning): + async with async_client.hris.directory.with_streaming_response.list_individuals() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + directory = await response.parse() + assert_matches_type(AsyncIndividualsPage[IndividualInDirectory], directory, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/hris/test_employments.py b/tests/api_resources/hris/test_employments.py index fd6d2499..d6d53313 100644 --- a/tests/api_resources/hris/test_employments.py +++ b/tests/api_resources/hris/test_employments.py @@ -3,23 +3,20 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncResponsesPage, AsyncResponsesPage from finch.types.hris import EmploymentDataResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestEmployments: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_retrieve_many(self, client: Finch) -> None: @@ -33,28 +30,56 @@ def test_raw_response_retrieve_many(self, client: Finch) -> None: response = client.hris.employments.with_raw_response.retrieve_many( requests=[{"individual_id": "string"}, {"individual_id": "string"}, {"individual_id": "string"}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" employment = response.parse() assert_matches_type(SyncResponsesPage[EmploymentDataResponse], employment, path=["response"]) + @parametrize + def test_streaming_response_retrieve_many(self, client: Finch) -> None: + with client.hris.employments.with_streaming_response.retrieve_many( + requests=[{"individual_id": "string"}, {"individual_id": "string"}, {"individual_id": "string"}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + employment = response.parse() + assert_matches_type(SyncResponsesPage[EmploymentDataResponse], employment, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncEmployments: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve_many(self, client: AsyncFinch) -> None: - employment = await client.hris.employments.retrieve_many( + async def test_method_retrieve_many(self, async_client: AsyncFinch) -> None: + employment = await async_client.hris.employments.retrieve_many( requests=[{"individual_id": "string"}, {"individual_id": "string"}, {"individual_id": "string"}], ) assert_matches_type(AsyncResponsesPage[EmploymentDataResponse], employment, path=["response"]) @parametrize - async def test_raw_response_retrieve_many(self, client: AsyncFinch) -> None: - response = await client.hris.employments.with_raw_response.retrieve_many( + async def test_raw_response_retrieve_many(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.employments.with_raw_response.retrieve_many( requests=[{"individual_id": "string"}, {"individual_id": "string"}, {"individual_id": "string"}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" employment = response.parse() assert_matches_type(AsyncResponsesPage[EmploymentDataResponse], employment, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_many(self, async_client: AsyncFinch) -> None: + async with async_client.hris.employments.with_streaming_response.retrieve_many( + requests=[{"individual_id": "string"}, {"individual_id": "string"}, {"individual_id": "string"}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + employment = await response.parse() + assert_matches_type(AsyncResponsesPage[EmploymentDataResponse], employment, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/hris/test_individuals.py b/tests/api_resources/hris/test_individuals.py index d109dbe9..12b244a0 100644 --- a/tests/api_resources/hris/test_individuals.py +++ b/tests/api_resources/hris/test_individuals.py @@ -3,23 +3,20 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncResponsesPage, AsyncResponsesPage from finch.types.hris import IndividualResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestIndividuals: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_retrieve_many(self, client: Finch) -> None: @@ -37,32 +34,56 @@ def test_method_retrieve_many_with_all_params(self, client: Finch) -> None: @parametrize def test_raw_response_retrieve_many(self, client: Finch) -> None: response = client.hris.individuals.with_raw_response.retrieve_many() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(SyncResponsesPage[IndividualResponse], individual, path=["response"]) + @parametrize + def test_streaming_response_retrieve_many(self, client: Finch) -> None: + with client.hris.individuals.with_streaming_response.retrieve_many() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = response.parse() + assert_matches_type(SyncResponsesPage[IndividualResponse], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncIndividuals: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve_many(self, client: AsyncFinch) -> None: - individual = await client.hris.individuals.retrieve_many() + async def test_method_retrieve_many(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.individuals.retrieve_many() assert_matches_type(AsyncResponsesPage[IndividualResponse], individual, path=["response"]) @parametrize - async def test_method_retrieve_many_with_all_params(self, client: AsyncFinch) -> None: - individual = await client.hris.individuals.retrieve_many( + async def test_method_retrieve_many_with_all_params(self, async_client: AsyncFinch) -> None: + individual = await async_client.hris.individuals.retrieve_many( options={"include": ["string", "string", "string"]}, requests=[{"individual_id": "string"}, {"individual_id": "string"}, {"individual_id": "string"}], ) assert_matches_type(AsyncResponsesPage[IndividualResponse], individual, path=["response"]) @parametrize - async def test_raw_response_retrieve_many(self, client: AsyncFinch) -> None: - response = await client.hris.individuals.with_raw_response.retrieve_many() + async def test_raw_response_retrieve_many(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.individuals.with_raw_response.retrieve_many() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(AsyncResponsesPage[IndividualResponse], individual, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_many(self, async_client: AsyncFinch) -> None: + async with async_client.hris.individuals.with_streaming_response.retrieve_many() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = await response.parse() + assert_matches_type(AsyncResponsesPage[IndividualResponse], individual, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/hris/test_pay_statements.py b/tests/api_resources/hris/test_pay_statements.py index ec2cac24..8b96e656 100644 --- a/tests/api_resources/hris/test_pay_statements.py +++ b/tests/api_resources/hris/test_pay_statements.py @@ -3,23 +3,20 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncResponsesPage, AsyncResponsesPage from finch.types.hris import PayStatementResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestPayStatements: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_retrieve_many(self, client: Finch) -> None: @@ -33,28 +30,56 @@ def test_raw_response_retrieve_many(self, client: Finch) -> None: response = client.hris.pay_statements.with_raw_response.retrieve_many( requests=[{"payment_id": "string"}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" pay_statement = response.parse() assert_matches_type(SyncResponsesPage[PayStatementResponse], pay_statement, path=["response"]) + @parametrize + def test_streaming_response_retrieve_many(self, client: Finch) -> None: + with client.hris.pay_statements.with_streaming_response.retrieve_many( + requests=[{"payment_id": "string"}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + pay_statement = response.parse() + assert_matches_type(SyncResponsesPage[PayStatementResponse], pay_statement, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncPayStatements: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve_many(self, client: AsyncFinch) -> None: - pay_statement = await client.hris.pay_statements.retrieve_many( + async def test_method_retrieve_many(self, async_client: AsyncFinch) -> None: + pay_statement = await async_client.hris.pay_statements.retrieve_many( requests=[{"payment_id": "string"}], ) assert_matches_type(AsyncResponsesPage[PayStatementResponse], pay_statement, path=["response"]) @parametrize - async def test_raw_response_retrieve_many(self, client: AsyncFinch) -> None: - response = await client.hris.pay_statements.with_raw_response.retrieve_many( + async def test_raw_response_retrieve_many(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.pay_statements.with_raw_response.retrieve_many( requests=[{"payment_id": "string"}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" pay_statement = response.parse() assert_matches_type(AsyncResponsesPage[PayStatementResponse], pay_statement, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve_many(self, async_client: AsyncFinch) -> None: + async with async_client.hris.pay_statements.with_streaming_response.retrieve_many( + requests=[{"payment_id": "string"}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + pay_statement = await response.parse() + assert_matches_type(AsyncResponsesPage[PayStatementResponse], pay_statement, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/hris/test_payments.py b/tests/api_resources/hris/test_payments.py index b117122b..022a0c30 100644 --- a/tests/api_resources/hris/test_payments.py +++ b/tests/api_resources/hris/test_payments.py @@ -3,24 +3,21 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type from finch._utils import parse_date -from finch._client import Finch, AsyncFinch from finch.pagination import SyncSinglePage, AsyncSinglePage from finch.types.hris import Payment base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestPayments: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_list(self, client: Finch) -> None: @@ -36,30 +33,60 @@ def test_raw_response_list(self, client: Finch) -> None: end_date=parse_date("2021-01-01"), start_date=parse_date("2021-01-01"), ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" payment = response.parse() assert_matches_type(SyncSinglePage[Payment], payment, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: Finch) -> None: + with client.hris.payments.with_streaming_response.list( + end_date=parse_date("2021-01-01"), + start_date=parse_date("2021-01-01"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(SyncSinglePage[Payment], payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncPayments: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_list(self, client: AsyncFinch) -> None: - payment = await client.hris.payments.list( + async def test_method_list(self, async_client: AsyncFinch) -> None: + payment = await async_client.hris.payments.list( end_date=parse_date("2021-01-01"), start_date=parse_date("2021-01-01"), ) assert_matches_type(AsyncSinglePage[Payment], payment, path=["response"]) @parametrize - async def test_raw_response_list(self, client: AsyncFinch) -> None: - response = await client.hris.payments.with_raw_response.list( + async def test_raw_response_list(self, async_client: AsyncFinch) -> None: + response = await async_client.hris.payments.with_raw_response.list( end_date=parse_date("2021-01-01"), start_date=parse_date("2021-01-01"), ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" payment = response.parse() assert_matches_type(AsyncSinglePage[Payment], payment, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: + async with async_client.hris.payments.with_streaming_response.list( + end_date=parse_date("2021-01-01"), + start_date=parse_date("2021-01-01"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(AsyncSinglePage[Payment], payment, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/jobs/test_automated.py b/tests/api_resources/jobs/test_automated.py index ae4ff4fa..c683780c 100644 --- a/tests/api_resources/jobs/test_automated.py +++ b/tests/api_resources/jobs/test_automated.py @@ -3,23 +3,20 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncPage, AsyncPage from finch.types.jobs import AutomatedAsyncJob, AutomatedCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestAutomated: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_create(self, client: Finch) -> None: @@ -33,10 +30,25 @@ def test_raw_response_create(self, client: Finch) -> None: response = client.jobs.automated.with_raw_response.create( type="data_sync_all", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() assert_matches_type(AutomatedCreateResponse, automated, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: Finch) -> None: + with client.jobs.automated.with_streaming_response.create( + type="data_sync_all", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + automated = response.parse() + assert_matches_type(AutomatedCreateResponse, automated, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_retrieve(self, client: Finch) -> None: automated = client.jobs.automated.retrieve( @@ -49,10 +61,32 @@ def test_raw_response_retrieve(self, client: Finch) -> None: response = client.jobs.automated.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() assert_matches_type(AutomatedAsyncJob, automated, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: Finch) -> None: + with client.jobs.automated.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + automated = response.parse() + assert_matches_type(AutomatedAsyncJob, automated, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + client.jobs.automated.with_raw_response.retrieve( + "", + ) + @parametrize def test_method_list(self, client: Finch) -> None: automated = client.jobs.automated.list() @@ -69,64 +103,125 @@ def test_method_list_with_all_params(self, client: Finch) -> None: @parametrize def test_raw_response_list(self, client: Finch) -> None: response = client.jobs.automated.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() assert_matches_type(SyncPage[AutomatedAsyncJob], automated, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: Finch) -> None: + with client.jobs.automated.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + automated = response.parse() + assert_matches_type(SyncPage[AutomatedAsyncJob], automated, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncAutomated: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, client: AsyncFinch) -> None: - automated = await client.jobs.automated.create( + async def test_method_create(self, async_client: AsyncFinch) -> None: + automated = await async_client.jobs.automated.create( type="data_sync_all", ) assert_matches_type(AutomatedCreateResponse, automated, path=["response"]) @parametrize - async def test_raw_response_create(self, client: AsyncFinch) -> None: - response = await client.jobs.automated.with_raw_response.create( + async def test_raw_response_create(self, async_client: AsyncFinch) -> None: + response = await async_client.jobs.automated.with_raw_response.create( type="data_sync_all", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() assert_matches_type(AutomatedCreateResponse, automated, path=["response"]) @parametrize - async def test_method_retrieve(self, client: AsyncFinch) -> None: - automated = await client.jobs.automated.retrieve( + async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: + async with async_client.jobs.automated.with_streaming_response.create( + type="data_sync_all", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + automated = await response.parse() + assert_matches_type(AutomatedCreateResponse, automated, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncFinch) -> None: + automated = await async_client.jobs.automated.retrieve( "string", ) assert_matches_type(AutomatedAsyncJob, automated, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, client: AsyncFinch) -> None: - response = await client.jobs.automated.with_raw_response.retrieve( + async def test_raw_response_retrieve(self, async_client: AsyncFinch) -> None: + response = await async_client.jobs.automated.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() assert_matches_type(AutomatedAsyncJob, automated, path=["response"]) @parametrize - async def test_method_list(self, client: AsyncFinch) -> None: - automated = await client.jobs.automated.list() + async def test_streaming_response_retrieve(self, async_client: AsyncFinch) -> None: + async with async_client.jobs.automated.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + automated = await response.parse() + assert_matches_type(AutomatedAsyncJob, automated, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + await async_client.jobs.automated.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncFinch) -> None: + automated = await async_client.jobs.automated.list() assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) @parametrize - async def test_method_list_with_all_params(self, client: AsyncFinch) -> None: - automated = await client.jobs.automated.list( + async def test_method_list_with_all_params(self, async_client: AsyncFinch) -> None: + automated = await async_client.jobs.automated.list( limit=0, offset=0, ) assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) @parametrize - async def test_raw_response_list(self, client: AsyncFinch) -> None: - response = await client.jobs.automated.with_raw_response.list() + async def test_raw_response_list(self, async_client: AsyncFinch) -> None: + response = await async_client.jobs.automated.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" automated = response.parse() assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: + async with async_client.jobs.automated.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + automated = await response.parse() + assert_matches_type(AsyncPage[AutomatedAsyncJob], automated, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/jobs/test_manual.py b/tests/api_resources/jobs/test_manual.py index 9df08a02..f8ab6edf 100644 --- a/tests/api_resources/jobs/test_manual.py +++ b/tests/api_resources/jobs/test_manual.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.jobs import ManualAsyncJob base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestManual: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_retrieve(self, client: Finch) -> None: @@ -32,28 +29,70 @@ def test_raw_response_retrieve(self, client: Finch) -> None: response = client.jobs.manual.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" manual = response.parse() assert_matches_type(ManualAsyncJob, manual, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: Finch) -> None: + with client.jobs.manual.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + manual = response.parse() + assert_matches_type(ManualAsyncJob, manual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + client.jobs.manual.with_raw_response.retrieve( + "", + ) + class TestAsyncManual: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, client: AsyncFinch) -> None: - manual = await client.jobs.manual.retrieve( + async def test_method_retrieve(self, async_client: AsyncFinch) -> None: + manual = await async_client.jobs.manual.retrieve( "string", ) assert_matches_type(ManualAsyncJob, manual, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, client: AsyncFinch) -> None: - response = await client.jobs.manual.with_raw_response.retrieve( + async def test_raw_response_retrieve(self, async_client: AsyncFinch) -> None: + response = await async_client.jobs.manual.with_raw_response.retrieve( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" manual = response.parse() assert_matches_type(ManualAsyncJob, manual, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncFinch) -> None: + async with async_client.jobs.manual.with_streaming_response.retrieve( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + manual = await response.parse() + assert_matches_type(ManualAsyncJob, manual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `job_id` but received ''"): + await async_client.jobs.manual.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/sandbox/connections/test_accounts.py b/tests/api_resources/sandbox/connections/test_accounts.py index ef1b3f3c..5dedf19d 100644 --- a/tests/api_resources/sandbox/connections/test_accounts.py +++ b/tests/api_resources/sandbox/connections/test_accounts.py @@ -3,25 +3,22 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox.connections import ( AccountCreateResponse, AccountUpdateResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestAccounts: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize @@ -38,7 +35,7 @@ def test_method_create_with_all_params(self, client: Finch) -> None: account = client.sandbox.connections.accounts.create( company_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", provider_id="string", - authentication_type="credentials", + authentication_type="credential", products=["string", "string", "string"], ) assert_matches_type(AccountCreateResponse, account, path=["response"]) @@ -50,10 +47,27 @@ def test_raw_response_create(self, client: Finch) -> None: company_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", provider_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="Auth isn't setup correctly in this test") + @parametrize + def test_streaming_response_create(self, client: Finch) -> None: + with client.sandbox.connections.accounts.with_streaming_response.create( + company_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + provider_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = response.parse() + assert_matches_type(AccountCreateResponse, account, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_update(self, client: Finch) -> None: account = client.sandbox.connections.accounts.update() @@ -69,20 +83,31 @@ def test_method_update_with_all_params(self, client: Finch) -> None: @parametrize def test_raw_response_update(self, client: Finch) -> None: response = client.sandbox.connections.accounts.with_raw_response.update() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(AccountUpdateResponse, account, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: Finch) -> None: + with client.sandbox.connections.accounts.with_streaming_response.update() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = response.parse() + assert_matches_type(AccountUpdateResponse, account, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncAccounts: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize - async def test_method_create(self, client: AsyncFinch) -> None: - account = await client.sandbox.connections.accounts.create( + async def test_method_create(self, async_client: AsyncFinch) -> None: + account = await async_client.sandbox.connections.accounts.create( company_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", provider_id="string", ) @@ -90,41 +115,71 @@ async def test_method_create(self, client: AsyncFinch) -> None: @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize - async def test_method_create_with_all_params(self, client: AsyncFinch) -> None: - account = await client.sandbox.connections.accounts.create( + async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: + account = await async_client.sandbox.connections.accounts.create( company_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", provider_id="string", - authentication_type="credentials", + authentication_type="credential", products=["string", "string", "string"], ) assert_matches_type(AccountCreateResponse, account, path=["response"]) @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize - async def test_raw_response_create(self, client: AsyncFinch) -> None: - response = await client.sandbox.connections.accounts.with_raw_response.create( + async def test_raw_response_create(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.connections.accounts.with_raw_response.create( company_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", provider_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(AccountCreateResponse, account, path=["response"]) + @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize - async def test_method_update(self, client: AsyncFinch) -> None: - account = await client.sandbox.connections.accounts.update() + async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.connections.accounts.with_streaming_response.create( + company_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + provider_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = await response.parse() + assert_matches_type(AccountCreateResponse, account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_update(self, async_client: AsyncFinch) -> None: + account = await async_client.sandbox.connections.accounts.update() assert_matches_type(AccountUpdateResponse, account, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: - account = await client.sandbox.connections.accounts.update( + async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> None: + account = await async_client.sandbox.connections.accounts.update( connection_status="reauth", ) assert_matches_type(AccountUpdateResponse, account, path=["response"]) @parametrize - async def test_raw_response_update(self, client: AsyncFinch) -> None: - response = await client.sandbox.connections.accounts.with_raw_response.update() + async def test_raw_response_update(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.connections.accounts.with_raw_response.update() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(AccountUpdateResponse, account, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.connections.accounts.with_streaming_response.update() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = await response.parse() + assert_matches_type(AccountUpdateResponse, account, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/sandbox/jobs/test_configuration.py b/tests/api_resources/sandbox/jobs/test_configuration.py index c9a6f5e2..17d4f21b 100644 --- a/tests/api_resources/sandbox/jobs/test_configuration.py +++ b/tests/api_resources/sandbox/jobs/test_configuration.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox.jobs import SandboxJobConfiguration, ConfigurationRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestConfiguration: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_retrieve(self, client: Finch) -> None: @@ -28,10 +25,23 @@ def test_method_retrieve(self, client: Finch) -> None: @parametrize def test_raw_response_retrieve(self, client: Finch) -> None: response = client.sandbox.jobs.configuration.with_raw_response.retrieve() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" configuration = response.parse() assert_matches_type(ConfigurationRetrieveResponse, configuration, path=["response"]) + @parametrize + def test_streaming_response_retrieve(self, client: Finch) -> None: + with client.sandbox.jobs.configuration.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + configuration = response.parse() + assert_matches_type(ConfigurationRetrieveResponse, configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_update(self, client: Finch) -> None: configuration = client.sandbox.jobs.configuration.update( @@ -46,42 +56,85 @@ def test_raw_response_update(self, client: Finch) -> None: completion_status="complete", type="data_sync_all", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" configuration = response.parse() assert_matches_type(SandboxJobConfiguration, configuration, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: Finch) -> None: + with client.sandbox.jobs.configuration.with_streaming_response.update( + completion_status="complete", + type="data_sync_all", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + configuration = response.parse() + assert_matches_type(SandboxJobConfiguration, configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncConfiguration: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_retrieve(self, client: AsyncFinch) -> None: - configuration = await client.sandbox.jobs.configuration.retrieve() + async def test_method_retrieve(self, async_client: AsyncFinch) -> None: + configuration = await async_client.sandbox.jobs.configuration.retrieve() assert_matches_type(ConfigurationRetrieveResponse, configuration, path=["response"]) @parametrize - async def test_raw_response_retrieve(self, client: AsyncFinch) -> None: - response = await client.sandbox.jobs.configuration.with_raw_response.retrieve() + async def test_raw_response_retrieve(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.jobs.configuration.with_raw_response.retrieve() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" configuration = response.parse() assert_matches_type(ConfigurationRetrieveResponse, configuration, path=["response"]) @parametrize - async def test_method_update(self, client: AsyncFinch) -> None: - configuration = await client.sandbox.jobs.configuration.update( + async def test_streaming_response_retrieve(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.jobs.configuration.with_streaming_response.retrieve() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + configuration = await response.parse() + assert_matches_type(ConfigurationRetrieveResponse, configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_update(self, async_client: AsyncFinch) -> None: + configuration = await async_client.sandbox.jobs.configuration.update( completion_status="complete", type="data_sync_all", ) assert_matches_type(SandboxJobConfiguration, configuration, path=["response"]) @parametrize - async def test_raw_response_update(self, client: AsyncFinch) -> None: - response = await client.sandbox.jobs.configuration.with_raw_response.update( + async def test_raw_response_update(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.jobs.configuration.with_raw_response.update( completion_status="complete", type="data_sync_all", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" configuration = response.parse() assert_matches_type(SandboxJobConfiguration, configuration, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.jobs.configuration.with_streaming_response.update( + completion_status="complete", + type="data_sync_all", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + configuration = await response.parse() + assert_matches_type(SandboxJobConfiguration, configuration, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/sandbox/test_company.py b/tests/api_resources/sandbox/test_company.py index 23a76f9c..c542d166 100644 --- a/tests/api_resources/sandbox/test_company.py +++ b/tests/api_resources/sandbox/test_company.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox import CompanyUpdateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestCompany: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_update(self, client: Finch) -> None: @@ -129,19 +126,39 @@ def test_raw_response_update(self, client: Finch) -> None: primary_email="string", primary_phone_number="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" company = response.parse() assert_matches_type(CompanyUpdateResponse, company, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: Finch) -> None: + with client.sandbox.company.with_streaming_response.update( + accounts=[{}, {}, {}], + departments=[{}, {}, {}], + ein="string", + entity={}, + legal_name="string", + locations=[{}, {}, {}], + primary_email="string", + primary_phone_number="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = response.parse() + assert_matches_type(CompanyUpdateResponse, company, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncCompany: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_update(self, client: AsyncFinch) -> None: - company = await client.sandbox.company.update( + async def test_method_update(self, async_client: AsyncFinch) -> None: + company = await async_client.sandbox.company.update( accounts=[{}, {}, {}], departments=[{}, {}, {}], ein="string", @@ -154,8 +171,8 @@ async def test_method_update(self, client: AsyncFinch) -> None: assert_matches_type(CompanyUpdateResponse, company, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: - company = await client.sandbox.company.update( + async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> None: + company = await async_client.sandbox.company.update( accounts=[ { "routing_number": "string", @@ -237,8 +254,8 @@ async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: assert_matches_type(CompanyUpdateResponse, company, path=["response"]) @parametrize - async def test_raw_response_update(self, client: AsyncFinch) -> None: - response = await client.sandbox.company.with_raw_response.update( + async def test_raw_response_update(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.company.with_raw_response.update( accounts=[{}, {}, {}], departments=[{}, {}, {}], ein="string", @@ -248,6 +265,28 @@ async def test_raw_response_update(self, client: AsyncFinch) -> None: primary_email="string", primary_phone_number="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" company = response.parse() assert_matches_type(CompanyUpdateResponse, company, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.company.with_streaming_response.update( + accounts=[{}, {}, {}], + departments=[{}, {}, {}], + ein="string", + entity={}, + legal_name="string", + locations=[{}, {}, {}], + primary_email="string", + primary_phone_number="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = await response.parse() + assert_matches_type(CompanyUpdateResponse, company, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/sandbox/test_connections.py b/tests/api_resources/sandbox/test_connections.py index f505c56b..c3d19e06 100644 --- a/tests/api_resources/sandbox/test_connections.py +++ b/tests/api_resources/sandbox/test_connections.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox import ConnectionCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestConnections: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize @@ -33,8 +30,8 @@ def test_method_create(self, client: Finch) -> None: def test_method_create_with_all_params(self, client: Finch) -> None: connection = client.sandbox.connections.create( provider_id="string", - authentication_type="credentials", - employer_size=0, + authentication_type="credential", + employee_size=0, products=["string", "string", "string"], ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) @@ -45,41 +42,71 @@ def test_raw_response_create(self, client: Finch) -> None: response = client.sandbox.connections.with_raw_response.create( provider_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" connection = response.parse() assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + @pytest.mark.skip(reason="Auth isn't setup correctly in this test") + @parametrize + def test_streaming_response_create(self, client: Finch) -> None: + with client.sandbox.connections.with_streaming_response.create( + provider_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncConnections: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize - async def test_method_create(self, client: AsyncFinch) -> None: - connection = await client.sandbox.connections.create( + async def test_method_create(self, async_client: AsyncFinch) -> None: + connection = await async_client.sandbox.connections.create( provider_id="string", ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize - async def test_method_create_with_all_params(self, client: AsyncFinch) -> None: - connection = await client.sandbox.connections.create( + async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: + connection = await async_client.sandbox.connections.create( provider_id="string", - authentication_type="credentials", - employer_size=0, + authentication_type="credential", + employee_size=0, products=["string", "string", "string"], ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) @pytest.mark.skip(reason="Auth isn't setup correctly in this test") @parametrize - async def test_raw_response_create(self, client: AsyncFinch) -> None: - response = await client.sandbox.connections.with_raw_response.create( + async def test_raw_response_create(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.connections.with_raw_response.create( provider_id="string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" connection = response.parse() assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Auth isn't setup correctly in this test") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.connections.with_streaming_response.create( + provider_id="string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/sandbox/test_directory.py b/tests/api_resources/sandbox/test_directory.py index ef7776db..c73edba0 100644 --- a/tests/api_resources/sandbox/test_directory.py +++ b/tests/api_resources/sandbox/test_directory.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox import DirectoryCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestDirectory: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_create(self, client: Finch) -> None: @@ -32,28 +29,56 @@ def test_raw_response_create(self, client: Finch) -> None: response = client.sandbox.directory.with_raw_response.create( body=[{}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() assert_matches_type(DirectoryCreateResponse, directory, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: Finch) -> None: + with client.sandbox.directory.with_streaming_response.create( + body=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + directory = response.parse() + assert_matches_type(DirectoryCreateResponse, directory, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncDirectory: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, client: AsyncFinch) -> None: - directory = await client.sandbox.directory.create( + async def test_method_create(self, async_client: AsyncFinch) -> None: + directory = await async_client.sandbox.directory.create( body=[{}], ) assert_matches_type(DirectoryCreateResponse, directory, path=["response"]) @parametrize - async def test_raw_response_create(self, client: AsyncFinch) -> None: - response = await client.sandbox.directory.with_raw_response.create( + async def test_raw_response_create(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.directory.with_raw_response.create( body=[{}], ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" directory = response.parse() assert_matches_type(DirectoryCreateResponse, directory, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.directory.with_streaming_response.create( + body=[{}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + directory = await response.parse() + assert_matches_type(DirectoryCreateResponse, directory, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/sandbox/test_employment.py b/tests/api_resources/sandbox/test_employment.py index 9a1c9bf2..f055e7ad 100644 --- a/tests/api_resources/sandbox/test_employment.py +++ b/tests/api_resources/sandbox/test_employment.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox import EmploymentUpdateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestEmployment: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_update(self, client: Finch) -> None: @@ -104,26 +101,46 @@ def test_raw_response_update(self, client: Finch) -> None: response = client.sandbox.employment.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" employment = response.parse() assert_matches_type(EmploymentUpdateResponse, employment, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: Finch) -> None: + with client.sandbox.employment.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + employment = response.parse() + assert_matches_type(EmploymentUpdateResponse, employment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `individual_id` but received ''"): + client.sandbox.employment.with_raw_response.update( + "", + ) + class TestAsyncEmployment: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_update(self, client: AsyncFinch) -> None: - employment = await client.sandbox.employment.update( + async def test_method_update(self, async_client: AsyncFinch) -> None: + employment = await async_client.sandbox.employment.update( "string", ) assert_matches_type(EmploymentUpdateResponse, employment, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: - employment = await client.sandbox.employment.update( + async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> None: + employment = await async_client.sandbox.employment.update( "string", class_code="string", custom_fields=[ @@ -194,10 +211,32 @@ async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: assert_matches_type(EmploymentUpdateResponse, employment, path=["response"]) @parametrize - async def test_raw_response_update(self, client: AsyncFinch) -> None: - response = await client.sandbox.employment.with_raw_response.update( + async def test_raw_response_update(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.employment.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" employment = response.parse() assert_matches_type(EmploymentUpdateResponse, employment, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.employment.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + employment = await response.parse() + assert_matches_type(EmploymentUpdateResponse, employment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `individual_id` but received ''"): + await async_client.sandbox.employment.with_raw_response.update( + "", + ) diff --git a/tests/api_resources/sandbox/test_individual.py b/tests/api_resources/sandbox/test_individual.py index 8a32a64c..7e40c602 100644 --- a/tests/api_resources/sandbox/test_individual.py +++ b/tests/api_resources/sandbox/test_individual.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox import IndividualUpdateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestIndividual: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_update(self, client: Finch) -> None: @@ -86,26 +83,46 @@ def test_raw_response_update(self, client: Finch) -> None: response = client.sandbox.individual.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(IndividualUpdateResponse, individual, path=["response"]) + @parametrize + def test_streaming_response_update(self, client: Finch) -> None: + with client.sandbox.individual.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = response.parse() + assert_matches_type(IndividualUpdateResponse, individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_update(self, client: Finch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `individual_id` but received ''"): + client.sandbox.individual.with_raw_response.update( + "", + ) + class TestAsyncIndividual: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_update(self, client: AsyncFinch) -> None: - individual = await client.sandbox.individual.update( + async def test_method_update(self, async_client: AsyncFinch) -> None: + individual = await async_client.sandbox.individual.update( "string", ) assert_matches_type(IndividualUpdateResponse, individual, path=["response"]) @parametrize - async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: - individual = await client.sandbox.individual.update( + async def test_method_update_with_all_params(self, async_client: AsyncFinch) -> None: + individual = await async_client.sandbox.individual.update( "string", dob="12/20/1989", emails=[ @@ -158,10 +175,32 @@ async def test_method_update_with_all_params(self, client: AsyncFinch) -> None: assert_matches_type(IndividualUpdateResponse, individual, path=["response"]) @parametrize - async def test_raw_response_update(self, client: AsyncFinch) -> None: - response = await client.sandbox.individual.with_raw_response.update( + async def test_raw_response_update(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.individual.with_raw_response.update( "string", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" individual = response.parse() assert_matches_type(IndividualUpdateResponse, individual, path=["response"]) + + @parametrize + async def test_streaming_response_update(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.individual.with_streaming_response.update( + "string", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + individual = await response.parse() + assert_matches_type(IndividualUpdateResponse, individual, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_update(self, async_client: AsyncFinch) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `individual_id` but received ''"): + await async_client.sandbox.individual.with_raw_response.update( + "", + ) diff --git a/tests/api_resources/sandbox/test_payment.py b/tests/api_resources/sandbox/test_payment.py index ee4a2a1d..86be4bbb 100644 --- a/tests/api_resources/sandbox/test_payment.py +++ b/tests/api_resources/sandbox/test_payment.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.types.sandbox import PaymentCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestPayment: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_create(self, client: Finch) -> None: @@ -127,24 +124,35 @@ def test_method_create_with_all_params(self, client: Finch) -> None: @parametrize def test_raw_response_create(self, client: Finch) -> None: response = client.sandbox.payment.with_raw_response.create() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" payment = response.parse() assert_matches_type(PaymentCreateResponse, payment, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: Finch) -> None: + with client.sandbox.payment.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(PaymentCreateResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncPayment: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, client: AsyncFinch) -> None: - payment = await client.sandbox.payment.create() + async def test_method_create(self, async_client: AsyncFinch) -> None: + payment = await async_client.sandbox.payment.create() assert_matches_type(PaymentCreateResponse, payment, path=["response"]) @parametrize - async def test_method_create_with_all_params(self, client: AsyncFinch) -> None: - payment = await client.sandbox.payment.create( + async def test_method_create_with_all_params(self, async_client: AsyncFinch) -> None: + payment = await async_client.sandbox.payment.create( end_date="string", pay_statements=[ { @@ -242,8 +250,21 @@ async def test_method_create_with_all_params(self, client: AsyncFinch) -> None: assert_matches_type(PaymentCreateResponse, payment, path=["response"]) @parametrize - async def test_raw_response_create(self, client: AsyncFinch) -> None: - response = await client.sandbox.payment.with_raw_response.create() + async def test_raw_response_create(self, async_client: AsyncFinch) -> None: + response = await async_client.sandbox.payment.with_raw_response.create() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" payment = response.parse() assert_matches_type(PaymentCreateResponse, payment, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: + async with async_client.sandbox.payment.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(PaymentCreateResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_access_tokens.py b/tests/api_resources/test_access_tokens.py index 4a89d515..b03994d3 100644 --- a/tests/api_resources/test_access_tokens.py +++ b/tests/api_resources/test_access_tokens.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from finch.types import CreateAccessTokenResponse from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestAccessTokens: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_create(self, client: Finch) -> None: @@ -38,19 +35,35 @@ def test_raw_response_create(self, client: Finch) -> None: code="", redirect_uri="https://example.com", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_token = response.parse() assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + @parametrize + def test_streaming_response_create(self, client: Finch) -> None: + with client.access_tokens.with_streaming_response.create( + client_id="", + client_secret="", + code="", + redirect_uri="https://example.com", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_token = response.parse() + assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncAccessTokens: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_create(self, client: AsyncFinch) -> None: - access_token = await client.access_tokens.create( + async def test_method_create(self, async_client: AsyncFinch) -> None: + access_token = await async_client.access_tokens.create( client_id="", client_secret="", code="", @@ -59,13 +72,31 @@ async def test_method_create(self, client: AsyncFinch) -> None: assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) @parametrize - async def test_raw_response_create(self, client: AsyncFinch) -> None: - response = await client.access_tokens.with_raw_response.create( + async def test_raw_response_create(self, async_client: AsyncFinch) -> None: + response = await async_client.access_tokens.with_raw_response.create( client_id="", client_secret="", code="", redirect_uri="https://example.com", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" access_token = response.parse() assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncFinch) -> None: + async with async_client.access_tokens.with_streaming_response.create( + client_id="", + client_secret="", + code="", + redirect_uri="https://example.com", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + access_token = await response.parse() + assert_matches_type(CreateAccessTokenResponse, access_token, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_account.py b/tests/api_resources/test_account.py index 33933b69..7dedf857 100644 --- a/tests/api_resources/test_account.py +++ b/tests/api_resources/test_account.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from finch.types import Introspection, DisconnectResponse from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestAccount: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_disconnect(self, client: Finch) -> None: @@ -28,10 +25,23 @@ def test_method_disconnect(self, client: Finch) -> None: @parametrize def test_raw_response_disconnect(self, client: Finch) -> None: response = client.account.with_raw_response.disconnect() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(DisconnectResponse, account, path=["response"]) + @parametrize + def test_streaming_response_disconnect(self, client: Finch) -> None: + with client.account.with_streaming_response.disconnect() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = response.parse() + assert_matches_type(DisconnectResponse, account, path=["response"]) + + assert cast(Any, response.is_closed) is True + @parametrize def test_method_introspect(self, client: Finch) -> None: account = client.account.introspect() @@ -40,36 +50,73 @@ def test_method_introspect(self, client: Finch) -> None: @parametrize def test_raw_response_introspect(self, client: Finch) -> None: response = client.account.with_raw_response.introspect() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(Introspection, account, path=["response"]) + @parametrize + def test_streaming_response_introspect(self, client: Finch) -> None: + with client.account.with_streaming_response.introspect() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = response.parse() + assert_matches_type(Introspection, account, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncAccount: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_disconnect(self, client: AsyncFinch) -> None: - account = await client.account.disconnect() + async def test_method_disconnect(self, async_client: AsyncFinch) -> None: + account = await async_client.account.disconnect() assert_matches_type(DisconnectResponse, account, path=["response"]) @parametrize - async def test_raw_response_disconnect(self, client: AsyncFinch) -> None: - response = await client.account.with_raw_response.disconnect() + async def test_raw_response_disconnect(self, async_client: AsyncFinch) -> None: + response = await async_client.account.with_raw_response.disconnect() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(DisconnectResponse, account, path=["response"]) @parametrize - async def test_method_introspect(self, client: AsyncFinch) -> None: - account = await client.account.introspect() + async def test_streaming_response_disconnect(self, async_client: AsyncFinch) -> None: + async with async_client.account.with_streaming_response.disconnect() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = await response.parse() + assert_matches_type(DisconnectResponse, account, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_introspect(self, async_client: AsyncFinch) -> None: + account = await async_client.account.introspect() assert_matches_type(Introspection, account, path=["response"]) @parametrize - async def test_raw_response_introspect(self, client: AsyncFinch) -> None: - response = await client.account.with_raw_response.introspect() + async def test_raw_response_introspect(self, async_client: AsyncFinch) -> None: + response = await async_client.account.with_raw_response.introspect() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" account = response.parse() assert_matches_type(Introspection, account, path=["response"]) + + @parametrize + async def test_streaming_response_introspect(self, async_client: AsyncFinch) -> None: + async with async_client.account.with_streaming_response.introspect() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + account = await response.parse() + assert_matches_type(Introspection, account, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_providers.py b/tests/api_resources/test_providers.py index bb0803d5..a9ef8c2b 100644 --- a/tests/api_resources/test_providers.py +++ b/tests/api_resources/test_providers.py @@ -3,23 +3,20 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from finch.types import Provider from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch from finch.pagination import SyncSinglePage, AsyncSinglePage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestProviders: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_list(self, client: Finch) -> None: @@ -29,24 +26,48 @@ def test_method_list(self, client: Finch) -> None: @parametrize def test_raw_response_list(self, client: Finch) -> None: response = client.providers.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" provider = response.parse() assert_matches_type(SyncSinglePage[Provider], provider, path=["response"]) + @parametrize + def test_streaming_response_list(self, client: Finch) -> None: + with client.providers.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + provider = response.parse() + assert_matches_type(SyncSinglePage[Provider], provider, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncProviders: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_list(self, client: AsyncFinch) -> None: - provider = await client.providers.list() + async def test_method_list(self, async_client: AsyncFinch) -> None: + provider = await async_client.providers.list() assert_matches_type(AsyncSinglePage[Provider], provider, path=["response"]) @parametrize - async def test_raw_response_list(self, client: AsyncFinch) -> None: - response = await client.providers.with_raw_response.list() + async def test_raw_response_list(self, async_client: AsyncFinch) -> None: + response = await async_client.providers.with_raw_response.list() + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" provider = response.parse() assert_matches_type(AsyncSinglePage[Provider], provider, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncFinch) -> None: + async with async_client.providers.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + provider = await response.parse() + assert_matches_type(AsyncSinglePage[Provider], provider, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_request_forwarding.py b/tests/api_resources/test_request_forwarding.py index 66d2dbdf..de68152d 100644 --- a/tests/api_resources/test_request_forwarding.py +++ b/tests/api_resources/test_request_forwarding.py @@ -3,22 +3,19 @@ from __future__ import annotations import os +from typing import Any, cast import pytest from finch import Finch, AsyncFinch from finch.types import RequestForwardingForwardResponse from tests.utils import assert_matches_type -from finch._client import Finch, AsyncFinch base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestRequestForwarding: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize def test_method_forward(self, client: Finch) -> None: @@ -48,27 +45,41 @@ def test_raw_response_forward(self, client: Finch) -> None: method="POST", route="/people/search", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" request_forwarding = response.parse() assert_matches_type(RequestForwardingForwardResponse, request_forwarding, path=["response"]) + @parametrize + def test_streaming_response_forward(self, client: Finch) -> None: + with client.request_forwarding.with_streaming_response.forward( + method="POST", + route="/people/search", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + request_forwarding = response.parse() + assert_matches_type(RequestForwardingForwardResponse, request_forwarding, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncRequestForwarding: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @parametrize - async def test_method_forward(self, client: AsyncFinch) -> None: - request_forwarding = await client.request_forwarding.forward( + async def test_method_forward(self, async_client: AsyncFinch) -> None: + request_forwarding = await async_client.request_forwarding.forward( method="POST", route="/people/search", ) assert_matches_type(RequestForwardingForwardResponse, request_forwarding, path=["response"]) @parametrize - async def test_method_forward_with_all_params(self, client: AsyncFinch) -> None: - request_forwarding = await client.request_forwarding.forward( + async def test_method_forward_with_all_params(self, async_client: AsyncFinch) -> None: + request_forwarding = await async_client.request_forwarding.forward( method="POST", route="/people/search", data=None, @@ -81,11 +92,27 @@ async def test_method_forward_with_all_params(self, client: AsyncFinch) -> None: assert_matches_type(RequestForwardingForwardResponse, request_forwarding, path=["response"]) @parametrize - async def test_raw_response_forward(self, client: AsyncFinch) -> None: - response = await client.request_forwarding.with_raw_response.forward( + async def test_raw_response_forward(self, async_client: AsyncFinch) -> None: + response = await async_client.request_forwarding.with_raw_response.forward( method="POST", route="/people/search", ) + + assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" request_forwarding = response.parse() assert_matches_type(RequestForwardingForwardResponse, request_forwarding, path=["response"]) + + @parametrize + async def test_streaming_response_forward(self, async_client: AsyncFinch) -> None: + async with async_client.request_forwarding.with_streaming_response.forward( + method="POST", + route="/people/search", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + request_forwarding = await response.parse() + assert_matches_type(RequestForwardingForwardResponse, request_forwarding, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_top_level.py b/tests/api_resources/test_top_level.py index a36d997f..c0752165 100644 --- a/tests/api_resources/test_top_level.py +++ b/tests/api_resources/test_top_level.py @@ -6,20 +6,12 @@ import pytest -from finch import Finch, AsyncFinch -from finch._client import Finch, AsyncFinch - base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestTopLevel: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) class TestAsyncTopLevel: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py index 203deb9c..edcd45f8 100644 --- a/tests/api_resources/test_webhooks.py +++ b/tests/api_resources/test_webhooks.py @@ -12,16 +12,12 @@ from pydantic import BaseModel from finch import Finch, AsyncFinch -from finch._client import Finch, AsyncFinch base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -access_token = "My Access Token" class TestWebhooks: - strict_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) timestamp = "1676312382" fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) @@ -36,21 +32,21 @@ class TestWebhooks: secret = "5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH" @time_machine.travel(fake_now) - def test_unwrap(self) -> None: + def test_unwrap(self, client: Finch) -> None: payload = self.payload headers = self.headers secret = self.secret - obj = self.strict_client.webhooks.unwrap(payload, headers, secret=secret) + obj = client.webhooks.unwrap(payload, headers, secret=secret) assert isinstance(obj, BaseModel) @time_machine.travel(fake_now) - def test_verify_signature(self) -> None: + def test_verify_signature(self, client: Finch) -> None: payload = self.payload headers = self.headers secret = self.secret signature = self.signature - verify = self.strict_client.webhooks.verify_signature + verify = client.webhooks.verify_signature assert verify(payload=payload, headers=headers, secret=secret) is None @@ -120,9 +116,7 @@ def test_verify_signature(self) -> None: class TestAsyncWebhooks: - strict_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) - loose_client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=False) - parametrize = pytest.mark.parametrize("client", [strict_client, loose_client], ids=["strict", "loose"]) + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) timestamp = "1676312382" fake_now = datetime.fromtimestamp(float(timestamp), tz=timezone.utc) @@ -137,21 +131,21 @@ class TestAsyncWebhooks: secret = "5WbX5kEWLlfzsGNjH64I8lOOqUB6e8FH" @time_machine.travel(fake_now) - def test_unwrap(self) -> None: + def test_unwrap(self, async_client: AsyncFinch) -> None: payload = self.payload headers = self.headers secret = self.secret - obj = self.strict_client.webhooks.unwrap(payload, headers, secret=secret) + obj = async_client.webhooks.unwrap(payload, headers, secret=secret) assert isinstance(obj, BaseModel) @time_machine.travel(fake_now) - def test_verify_signature(self) -> None: + def test_verify_signature(self, async_client: AsyncFinch) -> None: payload = self.payload headers = self.headers secret = self.secret signature = self.signature - verify = self.strict_client.webhooks.verify_signature + verify = async_client.webhooks.verify_signature assert verify(payload=payload, headers=headers, secret=secret) is None diff --git a/tests/conftest.py b/tests/conftest.py index f981b45d..2c791d06 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,17 @@ +from __future__ import annotations + +import os import asyncio import logging -from typing import Iterator +from typing import TYPE_CHECKING, Iterator, AsyncIterator import pytest +from finch import Finch, AsyncFinch + +if TYPE_CHECKING: + from _pytest.fixtures import FixtureRequest + pytest.register_assert_rewrite("tests.utils") logging.getLogger("finch").setLevel(logging.DEBUG) @@ -14,3 +22,28 @@ def event_loop() -> Iterator[asyncio.AbstractEventLoop]: loop = asyncio.new_event_loop() yield loop loop.close() + + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + +access_token = "My Access Token" + + +@pytest.fixture(scope="session") +def client(request: FixtureRequest) -> Iterator[Finch]: + strict = getattr(request, "param", True) + if not isinstance(strict, bool): + raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") + + with Finch(base_url=base_url, access_token=access_token, _strict_response_validation=strict) as client: + yield client + + +@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: + yield client diff --git a/tests/test_client.py b/tests/test_client.py index e7480def..d3c64e1b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -211,6 +211,7 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic # to_raw_response_wrapper leaks through the @functools.wraps() decorator. # # removing the decorator fixes the leak for reasons we don't understand. + "finch/_legacy_response.py", "finch/_response.py", # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. "finch/_compat.py", @@ -442,6 +443,35 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} + def test_multipart_repeating_array(self, client: Finch) -> None: + request = client._build_request( + FinalRequestOptions.construct( + method="get", + url="/foo", + headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, + json_data={"array": ["foo", "bar"]}, + files=[("foo.txt", b"hello world")], + ) + ) + + assert request.read().split(b"\r\n") == [ + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"foo", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"bar", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="foo.txt"; filename="upload"', + b"Content-Type: application/octet-stream", + b"", + b"hello world", + b"--6b7ba517decee4a450543ea6ae821c82--", + b"", + ] + @pytest.mark.respx(base_url=base_url) def test_basic_union_response(self, respx_mock: MockRouter) -> None: class Model1(BaseModel): @@ -946,6 +976,7 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic # to_raw_response_wrapper leaks through the @functools.wraps() decorator. # # removing the decorator fixes the leak for reasons we don't understand. + "finch/_legacy_response.py", "finch/_response.py", # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. "finch/_compat.py", @@ -1177,6 +1208,35 @@ def test_request_extra_query(self) -> None: params = dict(request.url.params) assert params == {"foo": "2"} + def test_multipart_repeating_array(self, async_client: AsyncFinch) -> None: + request = async_client._build_request( + FinalRequestOptions.construct( + method="get", + url="/foo", + headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, + json_data={"array": ["foo", "bar"]}, + files=[("foo.txt", b"hello world")], + ) + ) + + assert request.read().split(b"\r\n") == [ + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"foo", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="array[]"', + b"", + b"bar", + b"--6b7ba517decee4a450543ea6ae821c82", + b'Content-Disposition: form-data; name="foo.txt"; filename="upload"', + b"Content-Type: application/octet-stream", + b"", + b"hello world", + b"--6b7ba517decee4a450543ea6ae821c82--", + b"", + ] + @pytest.mark.respx(base_url=base_url) async def test_basic_union_response(self, respx_mock: MockRouter) -> None: class Model1(BaseModel): diff --git a/tests/test_legacy_response.py b/tests/test_legacy_response.py new file mode 100644 index 00000000..d86dc9ed --- /dev/null +++ b/tests/test_legacy_response.py @@ -0,0 +1,65 @@ +import json + +import httpx +import pytest +import pydantic + +from finch import Finch, BaseModel +from finch._streaming import Stream +from finch._base_client import FinalRequestOptions +from finch._legacy_response import LegacyAPIResponse + + +class PydanticModel(pydantic.BaseModel): + ... + + +def test_response_parse_mismatched_basemodel(client: Finch) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=b"foo"), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + with pytest.raises( + TypeError, + match="Pydantic models must subclass our base model type, e.g. `from finch import BaseModel`", + ): + response.parse(to=PydanticModel) + + +def test_response_parse_custom_stream(client: Finch) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=b"foo"), + client=client, + stream=True, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + stream = response.parse(to=Stream[int]) + assert stream._cast_to == int + + +class CustomModel(BaseModel): + foo: str + bar: int + + +def test_response_parse_custom_model(client: Finch) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=CustomModel) + assert obj.foo == "hello!" + assert obj.bar == 2 diff --git a/tests/test_response.py b/tests/test_response.py new file mode 100644 index 00000000..ba4ccb28 --- /dev/null +++ b/tests/test_response.py @@ -0,0 +1,159 @@ +import json +from typing import List + +import httpx +import pytest +import pydantic + +from finch import Finch, BaseModel, AsyncFinch +from finch._response import ( + APIResponse, + BaseAPIResponse, + AsyncAPIResponse, + BinaryAPIResponse, + AsyncBinaryAPIResponse, + extract_response_type, +) +from finch._streaming import Stream +from finch._base_client import FinalRequestOptions + + +class ConcreteBaseAPIResponse(APIResponse[bytes]): + ... + + +class ConcreteAPIResponse(APIResponse[List[str]]): + ... + + +class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): + ... + + +def test_extract_response_type_direct_classes() -> None: + assert extract_response_type(BaseAPIResponse[str]) == str + assert extract_response_type(APIResponse[str]) == str + assert extract_response_type(AsyncAPIResponse[str]) == str + + +def test_extract_response_type_direct_class_missing_type_arg() -> None: + with pytest.raises( + RuntimeError, + match="Expected type to have a type argument at index 0 but it did not", + ): + extract_response_type(AsyncAPIResponse) + + +def test_extract_response_type_concrete_subclasses() -> None: + assert extract_response_type(ConcreteBaseAPIResponse) == bytes + assert extract_response_type(ConcreteAPIResponse) == List[str] + assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response + + +def test_extract_response_type_binary_response() -> None: + assert extract_response_type(BinaryAPIResponse) == bytes + assert extract_response_type(AsyncBinaryAPIResponse) == bytes + + +class PydanticModel(pydantic.BaseModel): + ... + + +def test_response_parse_mismatched_basemodel(client: Finch) -> None: + response = APIResponse( + raw=httpx.Response(200, content=b"foo"), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + with pytest.raises( + TypeError, + match="Pydantic models must subclass our base model type, e.g. `from finch import BaseModel`", + ): + response.parse(to=PydanticModel) + + +@pytest.mark.asyncio +async def test_async_response_parse_mismatched_basemodel(async_client: AsyncFinch) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=b"foo"), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + with pytest.raises( + TypeError, + match="Pydantic models must subclass our base model type, e.g. `from finch import BaseModel`", + ): + await response.parse(to=PydanticModel) + + +def test_response_parse_custom_stream(client: Finch) -> None: + response = APIResponse( + raw=httpx.Response(200, content=b"foo"), + client=client, + stream=True, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + stream = response.parse(to=Stream[int]) + assert stream._cast_to == int + + +@pytest.mark.asyncio +async def test_async_response_parse_custom_stream(async_client: AsyncFinch) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=b"foo"), + client=async_client, + stream=True, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + stream = await response.parse(to=Stream[int]) + assert stream._cast_to == int + + +class CustomModel(BaseModel): + foo: str + bar: int + + +def test_response_parse_custom_model(client: Finch) -> None: + response = APIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = response.parse(to=CustomModel) + assert obj.foo == "hello!" + assert obj.bar == 2 + + +@pytest.mark.asyncio +async def test_async_response_parse_custom_model(async_client: AsyncFinch) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), + client=async_client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + obj = await response.parse(to=CustomModel) + assert obj.foo == "hello!" + assert obj.bar == 2 diff --git a/tests/test_utils/test_typing.py b/tests/test_utils/test_typing.py new file mode 100644 index 00000000..55d73a39 --- /dev/null +++ b/tests/test_utils/test_typing.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from typing import Generic, TypeVar, cast + +from finch._utils import extract_type_var_from_base + +_T = TypeVar("_T") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") + + +class BaseGeneric(Generic[_T]): + ... + + +class SubclassGeneric(BaseGeneric[_T]): + ... + + +class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): + ... + + +class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): + ... + + +class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): + ... + + +def test_extract_type_var() -> None: + assert ( + extract_type_var_from_base( + BaseGeneric[int], + index=0, + generic_bases=cast("tuple[type, ...]", (BaseGeneric,)), + ) + == int + ) + + +def test_extract_type_var_generic_subclass() -> None: + assert ( + extract_type_var_from_base( + SubclassGeneric[int], + index=0, + generic_bases=cast("tuple[type, ...]", (BaseGeneric,)), + ) + == int + ) + + +def test_extract_type_var_multiple() -> None: + typ = BaseGenericMultipleTypeArgs[int, str, None] + + generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) + assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int + assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str + assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) + + +def test_extract_type_var_generic_subclass_multiple() -> None: + typ = SubclassGenericMultipleTypeArgs[int, str, None] + + generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) + assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int + assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str + assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) + + +def test_extract_type_var_generic_subclass_different_ordering_multiple() -> None: + typ = SubclassDifferentOrderGenericMultipleTypeArgs[int, str, None] + + generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,)) + assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int + assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str + assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None) diff --git a/tests/utils.py b/tests/utils.py index da96975e..3217f048 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import inspect import traceback import contextlib from typing import Any, TypeVar, Iterator, cast @@ -68,6 +69,8 @@ def assert_matches_type( assert isinstance(value, bool) elif origin == float: assert isinstance(value, float) + elif origin == bytes: + assert isinstance(value, bytes) elif origin == datetime: assert isinstance(value, datetime) elif origin == date: @@ -100,6 +103,8 @@ def assert_matches_type( elif issubclass(origin, BaseModel): assert isinstance(value, type_) assert assert_matches_model(type_, cast(Any, value), path=path) + elif inspect.isclass(origin) and origin.__name__ == "HttpxBinaryResponseContent": + assert value.__class__.__name__ == "HttpxBinaryResponseContent" else: assert None, f"Unhandled field type: {type_}"