Fail to start Flask + Connexion + Swagger

524 Views Asked by At

Problem

I initiated a Flask app (+ Connexion and Swagger UI) and tried to open http://127.0.0.1:5000/api/ui. The browser showed starlette.exceptions.HTTPException: 404: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

Setup

% pip install "connexion[flask, swagger-ui]"
% export FLASK_APP="app"
    (Prepare files)
% flask run --debug
    (Access http://127.0.0.1:5000/api/ui)

Result

  • Python 3.12.0
  • connexion 3.0.2
  • Flask 3.0.0
  • swagger_ui_bundle 1.1.0
  • Werkzeug 3.0.1

Files

Directory structure

app/
    __init__.py
    openapi.yaml
    hello.py

__init__.py

from connexion import FlaskApp
from flask.app import Flask
from pathlib import Path


BASE_DIR = Path(__file__).parent.resolve()


def create_app() -> Flask:
    flask_app: FlaskApp = FlaskApp(__name__)
    app: Flask = flask_app.app

    flask_app.add_api("openapi.yaml")
    return app

openapi.yaml

openapi: 3.0.3
info:
  title: "test"
  description: "test"
  version: "1.0.0"

servers:
  - url: "/api"

paths:
  /hello:
    get:
      summary: "hello"
      description: "hello"
      operationId: "hello.say_hello"
      responses:
        200:
          description: "OK"
          content:
            text/plain:
              schema:
                type: string
                example: "hello"

hello.py

def say_hello() -> str:
    return 'Hello, world!'

Error message

Based on these settings, I believe I can see Swagger UI at http://127.0.0.1:5000/api/ui. However, I faced the error message below.

Traceback (most recent call last):
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 867, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 841, in dispatch_request
    self.raise_routing_exception(req)
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 450, in raise_routing_exception
    raise request.routing_exception  # type: ignore
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/ctx.py", line 353, in match_request
    result = self.url_adapter.match(return_rule=True)  # type: ignore
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/werkzeug/routing/map.py", line 624, in match
    raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 1478, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 1458, in wsgi_app
    response = self.handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 1455, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 869, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/flask/app.py", line 759, in handle_user_exception
    return self.ensure_sync(handler)(e)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/myusername/tmp/.venv/lib/python3.12/site-packages/connexion/apps/flask.py", line 245, in _http_exception
    raise starlette.exceptions.HTTPException(exc.code, detail=exc.description)
starlette.exceptions.HTTPException: 404: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
1

There are 1 best solutions below

2
On BEST ANSWER

TL:DR: You need an ASGI server to run your application.

A similar problem on the connexion github issue tracker.

This will only lead you to the documentation on running your application which can be found here.

Bearing the above in mind, I managed to create my own solution:

__init__.py:

from connexion import FlaskApp

def create_app():
    app = FlaskApp(__name__)
    app.add_api("openapi.yaml", validate_responses=True)
    return app

app = create_app()

openapi.yaml:

openapi: 3.0.3
info:
  title: test
  description: test
  version: 1.0.0

paths:
  /hello:
    get:
      summary: hello
      description: hello
      operationId: app.hello.say_hello
      responses:
        200:
          description: OK
          content:
            text/plain:
              schema:
                type: string
                example: hello

For additional assistance, please see below the docker setup I used:

docker-compose.yaml:

version: '3.8'

services:
  test-api:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - '8000:8000'
    volumes:
      - ./app:/usr/src/app/app

Dockerfile:

FROM public.ecr.aws/lambda/python:3.10

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY requirements.txt /usr/src/app/

RUN pip3 install --no-cache-dir -r requirements.txt

ENV FLASK_RUN_HOST=0.0.0.0
ENV FLASK_RUN_PORT=8000

EXPOSE 8000

COPY entrypoint.sh /usr/src/app/entrypoint.sh
RUN chmod +x /usr/src/app/entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]

entrypoint.sh:

#!/bin/sh

gunicorn -w 1 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app:app --reload

# Keep the script running to keep the container alive
tail -f /dev/null

requirements.txt:

connexion[flask, swagger-ui, uvicorn]==3.0.2
gunicorn==21.2.0
Werkzeug==3.0.1
Flask==3.0.0
setuptools >= 21.0.0
swagger-ui-bundle==1.1.0

project structure:

project-root/
|-- app/
|   |-- __init__.py
|   |-- hello.py
|   |-- openapi.yaml
|-- Dockerfile
|-- docker-compose.yml
|-- entrypoint.sh
|-- requirements.txt
|-- .env

Hope this helps! I'd recommend playing around from this working point with specific settings such as CORS, db setup, migrations, alterations to the docker setup etc.