OpenAPITools / OpenAPITools/openapi-generator

[REQ] [python-fastapi] Better support for spec-first development

Open
#13,863 4 comments 6 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Enhancement: Feature Server: Python
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Is your feature request related to a problem? Please describe.

The current python-fastapi generator seems to be geared towards a one-time generation of the python server rather than a continuous spec-first approach. For example:

Let's say I have the following OpenAPI spec

openapi: 3.0.0
info:
  description: |
    My API

  version: "1.0.0"
  title: my API
  contact:
    email: myemail@gmail.com
  license:
    name: MIT
    url: "https://opensource.org/licenses/MIT"

paths:
  /hello_world:
    get:
      description: "Sample endpoint"
      responses:
        "200":
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  hello:
                    type: string

When I run openapi-generator -i <path_to_spec> -g python-fastapi, a file src/openapi/apis/default_api.py (among other things) is autogenerated containing the following code:

router = APIRouter()

@router.get(
    "/hello_world",
    responses={
        200: {"model": InlineResponse200, "description": "Successful operation"},
    },
    tags=["default"],
)
async def hello_world_get(
) -> InlineResponse200:
    """Sample endpoint"""
    ...

The problem with this code gen pattern is that it doesn't enable a spec-first workflow because it doesn't separate API contract declaration from its implementation. That is, if I implement the generated hello_world_get method, then regenerate the file from the OpenAPI spec, my implementation gets overwritten.

If this was say, Java, the endpoints would be encapsulated under an interface like:

// DefaultAPI.java
// This interface is autogenerated 
interface DefaultAPI() {
  InlineResponse200 helloWorld();
}

Then in a separate file I'd implement the API

// DefaultApiImplementation.java
class DefaultApiImplementation implements DefaultAPI {
  InlineResponse200 helloWorld() {
    // ... 
  }
}

If a new method is added to the spec or an existing method is updated, I would run the generator and it would update DefaultAPI.java with any new parameters, return types, API endpoints, etc... And because the implementation (which I create by hand) implements that interface, the build will fail if one of the signatures doesn't correctly match or if an endpoint is not implemented.

Describe the solution you'd like

I'd love to start a conversation about whether the generator intends to evolve in this direction or if I'm just misunderstanding its usage pattern. Ideally, the code generated above enables two development experience invariants:

  1. allow separating the implementation of api endpoints from their declaration
  2. detect if an API method in the spec was not implemented by the developer or implemented with incorrect signatures and raise an exception during CI or tests

I have some outlines of a solution and would love some feedback. Basically, we'd change the structure of the generator to have the following output components:

  1. for each API, there is an ABC declaring the methods a developer needs to implement. Each such method corresponds to one API endpoint.
  2. For each API, the user implements a class which extends the ABC from step 1 in a separate file
  3. The user manually has to write the main.py file which initializes the FastAPI instance which actually launches the server, using the implementation they created from step 2 to initialize any routers.

I've included some pseudocode below that demoes what this would look like on the OpenAPI spec above:

default_api.py (this file is autogenerated):

class APIInterface(ABC): 
  # autogenned, contains pythonic signature of api
  @abstractmethod
  async def hello_world():
     """ hello world """


def initialize_router(api: APIInterface) -> APIRouter:
    router = APIRouter()
    # Directly add the method as a route implementation 
    # instead of adding via decoration (which is the current approach)
    router.get(
        "/hello_world",
        responses={
            200: {"model": InlineResponse200, "description": "Successful operation"},
        },
        tags=["default"],
    )
    
    # for each endpoint, do the same thing...
    # ...
    
    # at the end just return the router
    return router

api_implementation.py (this file is written and maintained by the user):

from default_api import APIInterface

# handwritten implementation of the API methods
class APIImplementation(APIInterface):
  async def hello_world():
    # code code code 

main.py (this file is written and maintained by the user, although we can probably automate some parts of it. Not essential that we do that for the sake of this discussion):

# The user must manually write this class
from default_api import initialize_router
from api_implementation import APIImplementation

app = FastAPI(
    title="my API",
    description=" API ",
    version="1.0.0",
)

app.include_router(initialize_router(APIImplementation()))

Would love some feedback on this approach and whether there is any appetite for adopting it.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the python-fastapi generator output described in default_api.py and compare it with the proposed api_implementation.py and main.py separation. The issue does not identify generator templates, entry points, or tests, so first establish the intended design and affected files. Done would require an agreed approach that preserves handwritten implementations while detecting missing or mismatched endpoint implementations.

Written by the indexing model from the issue text.

Assessment

Tech stack
fastapi, openapi, python
Domain
api, backend, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.