Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document that ConnexionMiddleware might require to specifiy routes explicitly #1886

Open
abstractbyte opened this issue Feb 17, 2024 · 2 comments
Labels
documentation enhancement PR welcome We would welcome and review a PR addressing this issue

Comments

@abstractbyte
Copy link

Description

From reading the documentation, I was under the assumption that API paths defined in the OpenAPI specification don't need to be registered explicitly.

Using Starlette with the ConnexionMiddleware however, I seem to have to explicitly add Routes to each API endpoint, otherwise, the server returns 404. This is not the case for the Swagger UI, which is served correctly without being registered explicitly.

It does not seem to be caused by Connexion not finding / resolving the python functions correctly: If I don't provide a correct operationId or configure a Resolver, Connexion would warn Failed to add operation for GET /api/v1/example and raise a ResolverError when trying to access the API.

Expected behaviour

From the documentation and the fact that the Swagger UI is served automatically, I would expect the following code to return "Hello World!" when sending a GET request to /api/v1/example (OpenAPI spec can be found under "Steps to reproduce"):

from connexion import ConnexionMiddleware
from connexion.decorators import StarletteDecorator
from starlette.applications import Starlette


@StarletteDecorator()
def get_example():
    return "Hello world!", 200


app = Starlette(debug=True)

app = ConnexionMiddleware(app)
app.add_api("api/openapi.yaml", base_path="/api/v1")

Actual behaviour

I need to explicitly add the route of the endpoint:

from starlette.routing import Route

...

app = Starlette(
    debug=True,
    routes=[
        Route("/api/v1/example", get_example),
    ],
)

...

If this is expected behavior, maybe that necessity could be mentioned in the documentation.

Steps to reproduce

Install connexion[swagger-ui], uvicorn and starlette and run uvicorn app:app with the following files:

api/openapi.yaml
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Example API
paths:
  /example:
    get:
      description: An example endpoint.
      operationId: app.get_example
      responses:
        "200":
          description: Successful.
Full app.py
from connexion import ConnexionMiddleware
from connexion.decorators import StarletteDecorator
from starlette.applications import Starlette
from starlette.routing import Route


@StarletteDecorator()
def get_example():
    return "Hello world!", 200


app = Starlette(
    debug=True,
    routes=[
        Route("/api/v1/example", get_example),
    ],
)

app = ConnexionMiddleware(app)
app.add_api("api/openapi.yaml", base_path="/api/v1")

Remove the routes=[...] argument and try to access the example endpoint.

Additional info:

I'm new to ASGI, Starlette and Connexion, please excuse any obvious things I missed.
I am only using Starlette in the first place to host a static HTML file and some resources next to the API, because I couldn't get that working with the AsyncApp. If this is possible, feel free to point me in the right direction.

My current setup would look something like this:

app = Starlette(debug=True, routes=[
    Route("/api/v1/example", get_example),
    Mount("/", app=StaticFiles(directory="static", html=True), name="static"),
])

Output of the commands:

  • python --version
    • Python 3.11.6
  • pip show connexion | grep "^Version\:"
    • Version: 3.0.6
@RobbeSneyders
Copy link
Member

Thanks for the report @abstractbyte.

Agree that we can add a note on this in the documentation.

This way of using Connexion is mostly meant if you already have a Starlette application and want to start leveraging Connexion though. If you're starting from scratch, the AsyncApp is the recommended way to go.

That being said, it's indeed not straightforward to host static files currently. For now, you should be able to do it like this:

app = AsyncApp(...)
app._middleware_app.router.mount("/", app=StaticFiles(directory="static", html=True), name="static")

We should provide a .mount() method on the AsyncApp directly to make this easier though.

@RobbeSneyders RobbeSneyders added enhancement documentation PR welcome We would welcome and review a PR addressing this issue labels Feb 17, 2024
@abstractbyte
Copy link
Author

Thanks for the fast reply, @RobbeSneyders! The proposed workaround via _middleware_app works well.
When I'm done with my project I'll try to look more into Connexion and maybe I'll be able to open a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation enhancement PR welcome We would welcome and review a PR addressing this issue
Projects
None yet
Development

No branches or pull requests

2 participants