diff --git a/.gitignore b/.gitignore index 3e9b0e57..de32a7e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__ dist .env +codegen.log diff --git a/README.md b/README.md index 61503b82..fc06cf6f 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ pip install finch-api ```python from finch import Finch -finch = Finch( +client = Finch( access_token="my access token", ) -candidate = finch.ats.candidates.retrieve( +candidate = client.ats.candidates.retrieve( "", ) print(candidate.first_name) @@ -38,13 +38,13 @@ Simply import `AsyncFinch` instead of `Finch` and use `await` with each API call ```python from finch import AsyncFinch -finch = AsyncFinch( +client = AsyncFinch( access_token="my access token", ) async def main(): - candidate = await finch.ats.candidates.retrieve( + candidate = await client.ats.candidates.retrieve( "", ) print(candidate.first_name) @@ -70,11 +70,11 @@ This library provides auto-paginating iterators with each list response, so you ```python import finch -finch = Finch() +client = Finch() all_jobs = [] # Automatically fetches more pages as needed. -for job in finch.ats.jobs.list(): +for job in client.ats.jobs.list(): # Do something with job here all_jobs.append(job) print(all_jobs) @@ -86,13 +86,13 @@ Or, asynchronously: import asyncio import finch -finch = AsyncFinch() +client = AsyncFinch() async def main() -> None: all_jobs = [] # Iterate through items across all pages, issuing requests as needed. - async for job in finch.ats.jobs.list(): + async for job in client.ats.jobs.list(): all_jobs.append(job) print(all_jobs) @@ -103,7 +103,7 @@ asyncio.run(main()) Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: ```python -first_page = await finch.ats.jobs.list() +first_page = await client.ats.jobs.list() if first_page.has_next_page(): print(f"will fetch next page using these details: {first_page.next_page_info()}") next_page = await first_page.get_next_page() @@ -115,7 +115,7 @@ if first_page.has_next_page(): Or just work directly with the returned data: ```python -first_page = await finch.ats.jobs.list() +first_page = await client.ats.jobs.list() print( f"the current start offset for this page: {first_page.paging.offset}" @@ -133,9 +133,9 @@ Nested parameters are dictionaries, typed using `TypedDict`, for example: ```python from finch import Finch -finch = Finch() +client = Finch() -finch.hris.directory.list_individuals( +client.hris.directory.list_individuals( path_params=[], params={}, ) @@ -151,12 +151,13 @@ response), a subclass of `finch.APIStatusError` will be raised, containing `stat All errors inherit from `finch.APIError`. ```python +import finch from finch import Finch -finch = Finch() +client = Finch() try: - finch.hris.directory.list_individuals() + client.hris.directory.list_individuals() except finch.APIConnectionError as e: print("The server could not be reached") print(e.__cause__) # an underlying Exception, likely raised within httpx. @@ -193,13 +194,13 @@ You can use the `max_retries` option to configure or disable this: from finch import Finch # Configure the default for all requests: -finch = Finch( +client = Finch( # default is 2 max_retries=0, ) # Or, configure per-request: -finch.with_options(max_retries=5).hris.directory.list_individuals() +client.with_options(max_retries=5).hris.directory.list_individuals() ``` ### Timeouts @@ -211,18 +212,18 @@ which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advan from finch import Finch # Configure the default for all requests: -finch = Finch( +client = Finch( # default is 60s timeout=20.0, ) # More granular control: -finch = Finch( +client = Finch( timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) # Override per-request: -finch.with_options(timeout=5 * 1000).hris.directory.list_individuals() +client.with_options(timeout=5 * 1000).hris.directory.list_individuals() ``` On timeout, an `APITimeoutError` is thrown. @@ -240,7 +241,7 @@ Be aware that doing so may result in incorrect types and other unexpected or und ```python from finch import Finch -finch = Finch( +client = Finch( default_headers={"Finch-API-Version": "My-Custom-Value"}, ) ``` @@ -253,7 +254,7 @@ You can configure the following keyword arguments when instantiating the client: import httpx from finch import Finch -finch = Finch( +client = Finch( # Use a custom base URL base_url="http://my.test.server.example.com:8083", proxies="http://my.test.proxy.example.com", @@ -263,6 +264,10 @@ finch = Finch( See the httpx documentation for information about the [`proxies`](https://www.python-httpx.org/advanced/#http-proxying) and [`transport`](https://www.python-httpx.org/advanced/#custom-transports) keyword arguments. +## Advanced: Managing HTTP resources + +By default we will close the underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__) is called but you can also manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. + ## Status This package is in beta. Its internals and interfaces are not stable and subject to change without a major semver bump; diff --git a/poetry.lock b/poetry.lock index 798b127c..c0ceb122 100644 --- a/poetry.lock +++ b/poetry.lock @@ -95,13 +95,13 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2023.5.7" +version = "2022.9.24" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, - {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] [[package]] @@ -398,51 +398,51 @@ files = [ [[package]] name = "pydantic" -version = "1.10.2" +version = "1.10.12" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, - {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, - {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, - {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, - {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, - {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, - {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, - {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, - {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, - {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, ] [package.dependencies] -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.2.0" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] @@ -464,13 +464,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyright" -version = "1.1.297" +version = "1.1.318" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.297-py3-none-any.whl", hash = "sha256:3fd6528280eb649f8b64b7ece55299f01e340d29f4cf257da876957e3ee24062"}, - {file = "pyright-1.1.297.tar.gz", hash = "sha256:89082de2fbd240fa75767b57824f4d8516f2fb9005047265a67b895547c6272f"}, + {file = "pyright-1.1.318-py3-none-any.whl", hash = "sha256:056c1b2e711c3526e32919de1684ae599d34b7ec27e94398858a43f56ac9ba9b"}, + {file = "pyright-1.1.318.tar.gz", hash = "sha256:69dcf9c32d5be27d531750de627e76a7cadc741d333b547c09044278b508db7b"}, ] [package.dependencies] @@ -744,13 +744,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] @@ -771,4 +771,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "cab46dc3be2a1bc422f10fb0046e47c31006fbcf9a0830168b236c5ab92a3c0d" +content-hash = "0f3e6676697cde9cd0547e264e0fb37288edc2ba78e2f117ea266196218b701d" diff --git a/pyproject.toml b/pyproject.toml index 44c3c1fd..61bd7895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ distro = ">= 1.7.0, < 2" [tool.poetry.group.dev.dependencies] -pyright = "1.1.297" +pyright = "1.1.318" mypy = "1.1.1" black = "22.10.0" respx = "0.19.2" diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py index 857dcadd..d646f53a 100644 --- a/src/finch/_base_client.py +++ b/src/finch/_base_client.py @@ -5,6 +5,7 @@ import uuid import inspect import platform +from types import TracebackType from random import random from typing import ( Any, @@ -677,6 +678,27 @@ def __init__( headers={"Accept": "application/json"}, ) + def is_closed(self) -> bool: + return self._client.is_closed + + def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + self._client.close() + + def __enter__(self: _T) -> _T: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + @overload def request( self, @@ -812,7 +834,7 @@ def _request_api_list( page: Type[SyncPageT], options: FinalRequestOptions, ) -> SyncPageT: - resp = cast(SyncPageT, self.request(page, options, stream=False)) + resp = self.request(page, options, stream=False) resp._set_private_attributes( # pyright: ignore[reportPrivateUsage] client=self, model=model, @@ -933,7 +955,7 @@ def patch( options: RequestOptions = {}, ) -> ResponseT: opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) - return cast(ResponseT, self.request(cast_to, opts)) + return self.request(cast_to, opts) def put( self, @@ -945,7 +967,7 @@ def put( options: RequestOptions = {}, ) -> ResponseT: opts = FinalRequestOptions.construct(method="put", url=path, json_data=body, files=files, **options) - return cast(ResponseT, self.request(cast_to, opts)) + return self.request(cast_to, opts) def delete( self, @@ -956,7 +978,7 @@ def delete( options: RequestOptions = {}, ) -> ResponseT: opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) - return cast(ResponseT, self.request(cast_to, opts)) + return self.request(cast_to, opts) def get_api_list( self, @@ -1009,6 +1031,27 @@ def __init__( headers={"Accept": "application/json"}, ) + def is_closed(self) -> bool: + return self._client.is_closed + + async def close(self) -> None: + """Close the underlying HTTPX client. + + The client will *not* be usable after this. + """ + await self._client.aclose() + + async def __aenter__(self: _T) -> _T: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.close() + @overload async def request( self, diff --git a/src/finch/_client.py b/src/finch/_client.py index b1f1cbf5..49e49696 100644 --- a/src/finch/_client.py +++ b/src/finch/_client.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import asyncio from typing import Union, Mapping, Optional import httpx @@ -87,9 +88,16 @@ def __init__( - `client_id` from `FINCH_CLIENT_ID` - `client_secret` from `FINCH_CLIENT_SECRET` """ + self.access_token = access_token + + client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None) + self.client_id = client_id or client_id_envvar or None + + client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None) + self.client_secret = client_secret or client_secret_envvar or None if base_url is None: - base_url = "https://api.tryfinch.com" + base_url = f"https://api.tryfinch.com" super().__init__( version=__version__, @@ -104,14 +112,6 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.access_token = access_token - - client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None) - self.client_id = client_id or client_id_envvar or None - - client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None) - self.client_secret = client_secret or client_secret_envvar or None - self.ats = resources.ATS(self) self.hris = resources.HRIS(self) self.providers = resources.Providers(self) @@ -204,6 +204,9 @@ def copy( # client.with_options(timeout=10).foo.create(...) with_options = copy + def __del__(self) -> None: + self.close() + def get_access_token( self, code: str, @@ -308,9 +311,16 @@ def __init__( - `client_id` from `FINCH_CLIENT_ID` - `client_secret` from `FINCH_CLIENT_SECRET` """ + self.access_token = access_token + + client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None) + self.client_id = client_id or client_id_envvar or None + + client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None) + self.client_secret = client_secret or client_secret_envvar or None if base_url is None: - base_url = "https://api.tryfinch.com" + base_url = f"https://api.tryfinch.com" super().__init__( version=__version__, @@ -325,14 +335,6 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.access_token = access_token - - client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None) - self.client_id = client_id or client_id_envvar or None - - client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None) - self.client_secret = client_secret or client_secret_envvar or None - self.ats = resources.AsyncATS(self) self.hris = resources.AsyncHRIS(self) self.providers = resources.AsyncProviders(self) @@ -425,6 +427,12 @@ def copy( # client.with_options(timeout=10).foo.create(...) with_options = copy + def __del__(self) -> None: + try: + asyncio.get_running_loop().create_task(self.close()) + except Exception: + pass + async def get_access_token( self, code: str, diff --git a/src/finch/pagination.py b/src/finch/pagination.py index d0b046ea..9f15e5c2 100644 --- a/src/finch/pagination.py +++ b/src/finch/pagination.py @@ -1,10 +1,11 @@ # File generated from our OpenAPI spec by Stainless. -from typing import List, Type, Generic, Mapping, TypeVar, Optional +from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast from httpx import Response from ._types import ModelT +from ._utils import is_mapping from ._models import BaseModel from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage @@ -45,7 +46,7 @@ def next_page_info(self) -> None: def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: return cls.construct( **{ - **(data if isinstance(data, Mapping) else {"items": data}), + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), } ) @@ -67,7 +68,7 @@ def next_page_info(self) -> None: def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: return cls.construct( **{ - **(data if isinstance(data, Mapping) else {"items": data}), + **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}), } ) diff --git a/tests/api_resources/test_ats.py b/tests/api_resources/test_ats.py deleted file mode 100644 index 8c8a9926..00000000 --- a/tests/api_resources/test_ats.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os - -import pytest - -from finch import Finch, AsyncFinch - -base_url = os.environ.get("API_BASE_URL", "http://127.0.0.1:4010") -access_token = os.environ.get("API_KEY", "something1234") - - -class TestATS: - 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"]) - - -class TestAsyncATS: - 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"]) diff --git a/tests/api_resources/test_hris.py b/tests/api_resources/test_hris.py deleted file mode 100644 index 9e954a66..00000000 --- a/tests/api_resources/test_hris.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. - -from __future__ import annotations - -import os - -import pytest - -from finch import Finch, AsyncFinch - -base_url = os.environ.get("API_BASE_URL", "http://127.0.0.1:4010") -access_token = os.environ.get("API_KEY", "something1234") - - -class TestHRIS: - 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"]) - - -class TestAsyncHRIS: - 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"]) diff --git a/tests/test_client.py b/tests/test_client.py index 1bc0a33e..1da67a2e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -4,6 +4,7 @@ import os import json +import asyncio import inspect from typing import Any, Dict, Union, cast @@ -368,6 +369,22 @@ def test_base_url_no_trailing_slash(self) -> None: ) assert request.url == "http://localhost:5000/custom/path/foo" + def test_client_del(self) -> None: + client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + assert not client.is_closed() + + client.__del__() + + assert client.is_closed() + + def test_client_context_manager(self) -> None: + client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + with client as c2: + assert c2 is client + assert not c2.is_closed() + assert not client.is_closed() + assert client.is_closed() + class TestAsyncFinch: client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) @@ -710,3 +727,20 @@ def test_base_url_no_trailing_slash(self) -> None: ), ) assert request.url == "http://localhost:5000/custom/path/foo" + + async def test_client_del(self) -> None: + client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + assert not client.is_closed() + + client.__del__() + + await asyncio.sleep(0.2) + assert client.is_closed() + + async def test_client_context_manager(self) -> None: + client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) + async with client as c2: + assert c2 is client + assert not c2.is_closed() + assert not client.is_closed() + assert client.is_closed()