-
-
Notifications
You must be signed in to change notification settings - Fork 257
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
httpxcan 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.getetc, and explicitly whathttpxrecommends 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
httpxitself. AuthenticatedClientandClientcan just each just become anhttpx.Clientconfigured 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.Clientcould be used directly (i.e. replaceclient.pyentirely). 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
Clientcould wrap anhttpx.Clientwhich allows you to add convenience methods as needed, and stay in control of theClientobject 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
Clientand proxy calls (with__getattr__) to an inner client, or typecheckclienton each API operation to see if you've got aClientorhttpx.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!