Skip to content

0.6.0 API #167

@dbanty

Description

@dbanty

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🆘 help wantedExtra attention is needed🥚breakingThis change breaks compatibility

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions