pythonflaskswaggerwerkzeugconnexion

Fail to start Flask + Connexion + Swagger


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

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.

Solution

  • 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.