Skip to content

Commit 8ed806e

Browse files
stainless-botRobertCraigie
authored andcommitted
chore(internal): minor reformatting of code (#46)
1 parent 1eff35f commit 8ed806e

File tree

4 files changed

+105
-16
lines changed

4 files changed

+105
-16
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ client = Finch(
264264

265265
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.
266266

267+
## Advanced: Managing HTTP resources
268+
269+
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.
270+
267271
## Status
268272

269273
This package is in beta. Its internals and interfaces are not stable and subject to change without a major semver bump;

src/finch/_base_client.py

+43
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import uuid
66
import inspect
77
import platform
8+
from types import TracebackType
89
from random import random
910
from typing import (
1011
Any,
@@ -677,6 +678,27 @@ def __init__(
677678
headers={"Accept": "application/json"},
678679
)
679680

681+
def is_closed(self) -> bool:
682+
return self._client.is_closed
683+
684+
def close(self) -> None:
685+
"""Close the underlying HTTPX client.
686+
687+
The client will *not* be usable after this.
688+
"""
689+
self._client.close()
690+
691+
def __enter__(self: _T) -> _T:
692+
return self
693+
694+
def __exit__(
695+
self,
696+
exc_type: type[BaseException] | None,
697+
exc: BaseException | None,
698+
exc_tb: TracebackType | None,
699+
) -> None:
700+
self.close()
701+
680702
@overload
681703
def request(
682704
self,
@@ -1009,6 +1031,27 @@ def __init__(
10091031
headers={"Accept": "application/json"},
10101032
)
10111033

1034+
def is_closed(self) -> bool:
1035+
return self._client.is_closed
1036+
1037+
async def close(self) -> None:
1038+
"""Close the underlying HTTPX client.
1039+
1040+
The client will *not* be usable after this.
1041+
"""
1042+
await self._client.aclose()
1043+
1044+
async def __aenter__(self: _T) -> _T:
1045+
return self
1046+
1047+
async def __aexit__(
1048+
self,
1049+
exc_type: type[BaseException] | None,
1050+
exc: BaseException | None,
1051+
exc_tb: TracebackType | None,
1052+
) -> None:
1053+
await self.close()
1054+
10121055
@overload
10131056
async def request(
10141057
self,

src/finch/_client.py

+24-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import os
6+
import asyncio
67
from typing import Union, Mapping, Optional
78

89
import httpx
@@ -87,6 +88,13 @@ def __init__(
8788
- `client_id` from `FINCH_CLIENT_ID`
8889
- `client_secret` from `FINCH_CLIENT_SECRET`
8990
"""
91+
self.access_token = access_token
92+
93+
client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None)
94+
self.client_id = client_id or client_id_envvar or None
95+
96+
client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None)
97+
self.client_secret = client_secret or client_secret_envvar or None
9098

9199
if base_url is None:
92100
base_url = f"https://api.tryfinch.com"
@@ -104,14 +112,6 @@ def __init__(
104112
_strict_response_validation=_strict_response_validation,
105113
)
106114

107-
self.access_token = access_token
108-
109-
client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None)
110-
self.client_id = client_id or client_id_envvar or None
111-
112-
client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None)
113-
self.client_secret = client_secret or client_secret_envvar or None
114-
115115
self.ats = resources.ATS(self)
116116
self.hris = resources.HRIS(self)
117117
self.providers = resources.Providers(self)
@@ -204,6 +204,9 @@ def copy(
204204
# client.with_options(timeout=10).foo.create(...)
205205
with_options = copy
206206

207+
def __del__(self) -> None:
208+
self.close()
209+
207210
def get_access_token(
208211
self,
209212
code: str,
@@ -308,6 +311,13 @@ def __init__(
308311
- `client_id` from `FINCH_CLIENT_ID`
309312
- `client_secret` from `FINCH_CLIENT_SECRET`
310313
"""
314+
self.access_token = access_token
315+
316+
client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None)
317+
self.client_id = client_id or client_id_envvar or None
318+
319+
client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None)
320+
self.client_secret = client_secret or client_secret_envvar or None
311321

312322
if base_url is None:
313323
base_url = f"https://api.tryfinch.com"
@@ -325,14 +335,6 @@ def __init__(
325335
_strict_response_validation=_strict_response_validation,
326336
)
327337

328-
self.access_token = access_token
329-
330-
client_id_envvar = os.environ.get("FINCH_CLIENT_ID", None)
331-
self.client_id = client_id or client_id_envvar or None
332-
333-
client_secret_envvar = os.environ.get("FINCH_CLIENT_SECRET", None)
334-
self.client_secret = client_secret or client_secret_envvar or None
335-
336338
self.ats = resources.AsyncATS(self)
337339
self.hris = resources.AsyncHRIS(self)
338340
self.providers = resources.AsyncProviders(self)
@@ -425,6 +427,12 @@ def copy(
425427
# client.with_options(timeout=10).foo.create(...)
426428
with_options = copy
427429

430+
def __del__(self) -> None:
431+
try:
432+
asyncio.get_running_loop().create_task(self.close())
433+
except Exception:
434+
pass
435+
428436
async def get_access_token(
429437
self,
430438
code: str,

tests/test_client.py

+34
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import json
7+
import asyncio
78
import inspect
89
from typing import Any, Dict, Union, cast
910

@@ -368,6 +369,22 @@ def test_base_url_no_trailing_slash(self) -> None:
368369
)
369370
assert request.url == "http://localhost:5000/custom/path/foo"
370371

372+
def test_client_del(self) -> None:
373+
client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True)
374+
assert not client.is_closed()
375+
376+
client.__del__()
377+
378+
assert client.is_closed()
379+
380+
def test_client_context_manager(self) -> None:
381+
client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True)
382+
with client as c2:
383+
assert c2 is client
384+
assert not c2.is_closed()
385+
assert not client.is_closed()
386+
assert client.is_closed()
387+
371388

372389
class TestAsyncFinch:
373390
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:
710727
),
711728
)
712729
assert request.url == "http://localhost:5000/custom/path/foo"
730+
731+
async def test_client_del(self) -> None:
732+
client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True)
733+
assert not client.is_closed()
734+
735+
client.__del__()
736+
737+
await asyncio.sleep(0.2)
738+
assert client.is_closed()
739+
740+
async def test_client_context_manager(self) -> None:
741+
client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True)
742+
async with client as c2:
743+
assert c2 is client
744+
assert not c2.is_closed()
745+
assert not client.is_closed()
746+
assert client.is_closed()

0 commit comments

Comments
 (0)