Description
As of version 0.6.1, the generated Client
is somewhat configurable - headers, cookies, and timeout. However, these are all abstractions which have to then be handled explicitly within each generated API method.
Would it be simpler to just make calls using an httpx.Client
or httpx.AsyncClient
instance, and allow consumers to configure that directly? Advantages:
- Multiple versions of
httpx
can be supported, and there's less likelihood that you'll have to change your package due to changes or new features inhttpx
. - It's more efficient than direct calls to
httpx.get
etc, and explicitly whathttpx
recommends in its documentation:
If you do anything more than experimentation, one-off scripts, or prototypes, then you should use a Client instance.
of course, this package does use the context manager within API operations, but that doesn't allow multiple calls to share the same client and thus connection.
- Everything else good in that documentation, like the ability to use the generated client package as a WSGI test client
- Event hooks will allow consumers to implement our own global retry logic (like refreshing authentication tokens) prior to official retry support from
httpx
itself. AuthenticatedClient
andClient
can just each just become anhttpx.Client
configured with different headers.
tl;dr: it decreases coupling between the two packages and lets you worry less about the client configuration and how to abstract it. More httpx
functionality will be directly available to consumers, so you'll get fewer (actionable) feature requests. Future breaking changes here will be less likely. Seems like this alone would allow closing a couple currently pending issues (retries, different auth methods, response mimetypes), by putting them entirely in the hands of the consumer.
Describe the solution you'd like
There are a few options.
- The
httpx.Client
could be used directly (i.e. replaceclient.py
entirely). API methods would just accept the client and use it directly, and it would be up to the caller to configure and manage it. This is the simplest for sure, and meets the current use case. This is what I'd recommend.
def sync_detailed(
*,
client: httpx.Client,
json_body: CreateUserRequest,
) -> Response[Union[User, Error]]:
kwargs = _get_kwargs(
client=client,
json_body=json_body,
)
response = client.post(
**kwargs,
)
return _build_response(response=response)
- The
Client
could wrap anhttpx.Client
which allows you to add convenience methods as needed, and stay in control of theClient
object itself. This abstraction layer offers protected variation, but wouldn't be used for anything right now - headers, timeouts, and cookies can all be configured directly on anhttpx.Client
. However this need could also be met with configuration values passed directly to each API operation.
def sync_detailed(
*,
client: Client,
json_body: CreateUserRequest,
) -> Response[Union[User, Error]]:
kwargs = _get_kwargs(
client=client.httpx_client,
json_body=json_body,
)
response = client.httpx_client.post(
**kwargs,
)
return _build_response(response=response)
- Keep the
Client
and proxy calls (with__getattr__
) to an inner client, or typecheckclient
on each API operation to see if you've got aClient
orhttpx.Client
. This allows them to be used interchangeably in API operations. This one's the most fragile and doesn't offer any advantages at the moment.
Of course, this would all apply to AsyncClient
for the asyncio
calls.
Additional context
Happy to send a PR, can do it pretty quickly. Am looking to use this in production, and would love to stay on (and contribute to) mainline rather than a fork!