Skip to content

Use httpx.Client Directly #202

Closed
Closed
@kalzoo

Description

@kalzoo

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 in httpx.
  • It's more efficient than direct calls to httpx.get etc, and explicitly what httpx 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 and Client can just each just become an httpx.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.

  1. The httpx.Client could be used directly (i.e. replace client.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)
  1. The Client could wrap an httpx.Client which allows you to add convenience methods as needed, and stay in control of the Client 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 an httpx.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)
  1. Keep the Client and proxy calls (with __getattr__) to an inner client, or typecheck client on each API operation to see if you've got a Client or httpx.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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions