diff --git a/.stats.yml b/.stats.yml index 1024445..63c6ed1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 39 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-811587ac4ba3634e1b0dd781c54c7a0cb302d612f719036f9c9683166c7197cd.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-612b573cabcee39c562dc91f5b185e2a0bfdce3a262618f0098602af2199af67.yml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91fc889..b9879f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,13 @@ ### With Rye -We use [Rye](https://rye.astral.sh/) to manage dependencies so we highly recommend [installing it](https://rye.astral.sh/guide/installation/) as it will automatically provision a Python environment with the expected Python version. +We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run: -After installing Rye, you'll just have to run this command: +```sh +$ ./scripts/bootstrap +``` + +Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run: ```sh $ rye sync --all-features @@ -39,17 +43,17 @@ modify the contents of the `src/finch/lib/` and `examples/` directories. All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. -```bash +```py # add an example to examples/.py #!/usr/bin/env -S rye run python … ``` -``` -chmod +x examples/.py +```sh +$ chmod +x examples/.py # run the example against your api -./examples/.py +$ ./examples/.py ``` ## Using the repository from source @@ -58,8 +62,8 @@ If you’d like to use the repository from source, you can either install from g To install via git: -```bash -pip install git+ssh://git@github.com/Finch-API/finch-api-python.git +```sh +$ pip install git+ssh://git@github.com/Finch-API/finch-api-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -68,29 +72,29 @@ Building this package will create two files in the `dist/` directory, a `.tar.gz To create a distributable version of the library, all you have to do is run this command: -```bash -rye build +```sh +$ rye build # or -python -m build +$ python -m build ``` Then to install: ```sh -pip install ./path-to-wheel-file.whl +$ pip install ./path-to-wheel-file.whl ``` ## Running tests Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. -```bash +```sh # you will need npm installed -npx prism mock path/to/your/openapi.yml +$ npx prism mock path/to/your/openapi.yml ``` -```bash -rye run pytest +```sh +$ ./scripts/test ``` ## Linting and formatting @@ -100,14 +104,14 @@ This repository uses [ruff](https://github.com/astral-sh/ruff) and To lint: -```bash -rye run lint +```sh +$ ./scripts/lint ``` To format and fix all ruff issues automatically: -```bash -rye run format +```sh +$ ./scripts/format ``` ## Publishing and releases diff --git a/README.md b/README.md index 0f6ca09..83f338a 100644 --- a/README.md +++ b/README.md @@ -430,3 +430,7 @@ print(finch.__version__) ## Requirements Python 3.7 or higher. + +## Contributing + +See [the contributing documentation](./CONTRIBUTING.md). diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py index 5df3d61..739b07c 100644 --- a/src/finch/_base_client.py +++ b/src/finch/_base_client.py @@ -144,6 +144,12 @@ def __init__( self.url = url self.params = params + @override + def __repr__(self) -> str: + if self.url: + return f"{self.__class__.__name__}(url={self.url})" + return f"{self.__class__.__name__}(params={self.params})" + class BasePage(GenericModel, Generic[_T]): """ @@ -690,7 +696,8 @@ def _calculate_retry_timeout( if retry_after is not None and 0 < retry_after <= 60: return retry_after - nb_retries = max_retries - remaining_retries + # Also cap retry count to 1000 to avoid any potential overflows with `pow` + nb_retries = min(max_retries - remaining_retries, 1000) # Apply exponential backoff, but not more than the max. sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY) diff --git a/src/finch/_legacy_response.py b/src/finch/_legacy_response.py index d422f53..739b2f9 100644 --- a/src/finch/_legacy_response.py +++ b/src/finch/_legacy_response.py @@ -251,6 +251,9 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == float: return cast(R, float(response.text)) + if cast_to == bool: + return cast(R, response.text.lower() == "true") + origin = get_origin(cast_to) or cast_to if inspect.isclass(origin) and issubclass(origin, HttpxBinaryResponseContent): diff --git a/src/finch/_response.py b/src/finch/_response.py index a458c65..d22f84d 100644 --- a/src/finch/_response.py +++ b/src/finch/_response.py @@ -192,6 +192,9 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if cast_to == float: return cast(R, float(response.text)) + if cast_to == bool: + return cast(R, response.text.lower() == "true") + origin = get_origin(cast_to) or cast_to # handle the legacy binary response case diff --git a/src/finch/types/account_update_event.py b/src/finch/types/account_update_event.py index 84e914d..38dd077 100644 --- a/src/finch/types/account_update_event.py +++ b/src/finch/types/account_update_event.py @@ -39,7 +39,6 @@ "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEarnings", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions", - "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayment", "AccountUpdateEventDataAuthenticationMethodSupportedFieldsPaymentPayPeriod", @@ -302,14 +301,6 @@ class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPaySt name: Optional[bool] = None -class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions(BaseModel): - amount: Optional[bool] = None - - currency: Optional[bool] = None - - name: Optional[bool] = None - - class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes(BaseModel): amount: Optional[bool] = None @@ -335,11 +326,6 @@ class AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPaySt AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions ] = None - employer_deductions: Optional[ - AccountUpdateEventDataAuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions - ] = None - """[DEPRECATED] Use `employer_contributions` instead""" - gross_pay: Optional[bool] = None individual_id: Optional[bool] = None diff --git a/src/finch/types/provider.py b/src/finch/types/provider.py index 5cbb2c1..d34b3ac 100644 --- a/src/finch/types/provider.py +++ b/src/finch/types/provider.py @@ -36,7 +36,6 @@ "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEarnings", "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployeeDeductions", "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions", - "AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions", "AuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes", "AuthenticationMethodSupportedFieldsPayment", "AuthenticationMethodSupportedFieldsPaymentPayPeriod", @@ -297,14 +296,6 @@ class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContri name: Optional[bool] = None -class AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions(BaseModel): - amount: Optional[bool] = None - - currency: Optional[bool] = None - - name: Optional[bool] = None - - class AuthenticationMethodSupportedFieldsPayStatementPayStatementsTaxes(BaseModel): amount: Optional[bool] = None @@ -326,9 +317,6 @@ class AuthenticationMethodSupportedFieldsPayStatementPayStatements(BaseModel): AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerContributions ] = None - employer_deductions: Optional[AuthenticationMethodSupportedFieldsPayStatementPayStatementsEmployerDeductions] = None - """[DEPRECATED] Use `employer_contributions` instead""" - gross_pay: Optional[bool] = None individual_id: Optional[bool] = None diff --git a/tests/test_client.py b/tests/test_client.py index 7c5deac..4641bf7 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -809,6 +809,7 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 7.8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -1686,6 +1687,7 @@ class Model(BaseModel): [3, "", 0.5], [2, "", 0.5 * 2.0], [1, "", 0.5 * 4.0], + [-1100, "", 7.8], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) diff --git a/tests/test_legacy_response.py b/tests/test_legacy_response.py index 7712696..c6ec76a 100644 --- a/tests/test_legacy_response.py +++ b/tests/test_legacy_response.py @@ -32,6 +32,31 @@ def test_response_parse_mismatched_basemodel(client: Finch) -> None: response.parse(to=PydanticModel) +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +def test_response_parse_bool(client: Finch, content: str, expected: bool) -> None: + response = LegacyAPIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = response.parse(to=bool) + assert result is expected + + def test_response_parse_custom_stream(client: Finch) -> None: response = LegacyAPIResponse( raw=httpx.Response(200, content=b"foo"), diff --git a/tests/test_response.py b/tests/test_response.py index 4953879..8241ad9 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -190,6 +190,56 @@ async def test_async_response_parse_annotated_type(async_client: AsyncFinch) -> assert obj.bar == 2 +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +def test_response_parse_bool(client: Finch, content: str, expected: bool) -> None: + response = APIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = response.parse(to=bool) + assert result is expected + + +@pytest.mark.parametrize( + "content, expected", + [ + ("false", False), + ("true", True), + ("False", False), + ("True", True), + ("TrUe", True), + ("FalSe", False), + ], +) +async def test_async_response_parse_bool(client: AsyncFinch, content: str, expected: bool) -> None: + response = AsyncAPIResponse( + raw=httpx.Response(200, content=content), + client=client, + stream=False, + stream_cls=None, + cast_to=str, + options=FinalRequestOptions.construct(method="get", url="/foo"), + ) + + result = await response.parse(to=bool) + assert result is expected + + class OtherModel(BaseModel): a: str