-
-
Notifications
You must be signed in to change notification settings - Fork 251
Description
There are a few goals for an upcoming 0.6.0 version that will involve modifying the endpoint API (i.e. everything in the api
and async_api
modules in the generated clients).
The primary issue I'm trying to solve is the ability to return additional data as needed from endpoint calls (e.g. status code, headers, raw data). This opens up a number of possibilities around more graceful degradation of generated clients (like in #141) but also provides an escape hatch when there are things that OpenAPI can't represent correctly (or this generator gets wrong) like in #115 .
Currently I have narrowed down my ideas about how to do this to two options, though I'm open to other ideas if someone has a better one.
Option 1: One Module per Endpoint
The general idea is that if you previously had an endpoint function in my_client.api.default.my_endpoint
it would become my_client.api.default.my_endpoint.blocking
(or sync?). The async_api
version would be something like my_client.api.default.my_endpoint.async_()
(I hate the trailing underscore so I would love a different name if this is the solution). We then add in a wrapped
and async_wrapped
(or full
or detailed
) for the new heavier response.
This is my favorite option right now because the code (while not elegant) is straight forward. This means it should be the most accessible to people reading their generated clients (which they absolutely should) and modifying them. Each of those functions would share most of their code with the others, only differences being which httpx function is called and what is returned.
The biggest downside is that this 100% breaks compatibility for new clients. This isn't the end of the world (we're still 0.x for a reason), but is certain to be annoying.
Option 2: Endpoints as Classes
This code will be harder to read / maintain but can preserve backwards compatibility. So we keep the separate sync and async modules (though I'll be combining some of their code anyway), but instead of my_endpoint
being a function, it's a class instance with a __call__
method which does what the function does today. Then we can add a .wrapped()
method to the class. So the generated code ends up looking something like
class _MyEndpoint:
def __call__(self, *, client: Client, ...) -> SomeType:
return self.wrapped(client=client, ...).body
def wrapped(self, *, client: CLient, ...) -> Response[SomeType]:
# Do normal stuff here
my_endpoint = _MyEndpoint()
There is also a sort of moderate option where we do Option 2 but mark __call__
as deprecated (emit a warning) and provide whatever method names we intend to have as functions in Option 1. Then go to Option 1 in 0.7.0 so people have a gradual migration path. I'm not sure if enough people have generated clients today to be worth that effort though 😅.
Mentioning @emannguitar, @pawamoy, and @bowenwr since I believe they have all discussed pieces of this in other issues. Of course this is open to discussion from anyone though.