For an API client I am working on, I was wondering, whether the main class should be reusable. This question basically boils down to: should the HTTP client be instantiated in __init__ or __enter__/_request.
Usage at the moment:
client = ApiClient()
with client:
client.get_user()
# Additional requests to use connection pooling.
# Can reopen the connection, as the HTTP client is instantiated in __enter__
with client:
client.get_user()
But maybe, the client instance shouldn't be reusable, as it doesn't provide any advantage other than storing the ApiKey, and the non-usable type is the expected behavior from an HTTP client:
# Main usage then, reopening wouldn't work.
with ApiClient() as client:
client.get_user()
Below you'll find the full code for the reusable variant for reference (with most of the endpoints missing obviously):
class ApiClient:
def __init__(self, api_key: str = os.environ["API_KEY"]) -> None:
"""Instantiate a new API client."""
self.api_key = api_key
def __enter__(self):
self.connect()
return self
def __exit__(self, exception_type, exception_value, exception_traceback):
self.close()
del self.client
def _request(self, method: str, endpoint: str, data: dict | None = None):
if not hasattr(self, "client"):
self.connect()
request = self.client.build_request(method=method, url=endpoint, data=data)
response = self.client.send(request)
response.raise_for_status()
return response.json()
def get_user(self):
return self._request("GET", "/api/me")
def connect(self):
headers = {"Api-Key": self.api_key}
base_url = "https://login.smoobu.com"
self.client = httpx.Client(base_url=base_url, headers=headers)
def close(self):
self.client.close()
client = ApiClient()and then two threads dowith client: .... In particular when__exit__is called twice on the sameApiClient.httpx.Clientis a perfect example since it cannot be re-opened. Personally, I think your client should ask for a httpx.Client as a dependency, not own it. That way, the caller can control the client's lifecycle.sqlite3andpsycopg2, for example, let you create a single connection object that, when used as a context manager, starts and commits a new transaction, rather than auto-closing a connection.