Search code examples
pythonflaskgunicornuwsgi

404 for all routes with Flask on uWSGI/Gunicorn


I found and forked the following Flask/SQLAlchemy/Marshmallow example project:

https://github.com/summersab/RestaurantAPI

It works like a charm, and I used it to build a custom API. It runs flawlessly with:

python run.py

Then, I tried to run it on a proper webserver:

uwsgi --http localhost:5000 --wsgi-file run.py --callable app

However, I get 404 for all routes except /, /api, and /api/v1.0. I tried it on Gunicorn with the same results leading me to believe that the problem is with the code, not the webserver config.

All of the following posts have the same problem, but from what I could tell, none of them had solutions that worked for me (I may have missed something):

Could someone look at the code in my repo and help me figure out what's wrong?

EDIT:

Per the response from @v25, I changed my run.py to the following:

from flask import Flask, redirect, render_template
from app import api_bp
from model import db, redis_cache
from config import DevelopmentConfig, TestingConfig, BaseConfig, PresentConfig

app = Flask(__name__)

t = 0
def create_app(config_filename):
    app.config.from_object(config_filename)
    global t
    if t == 0:
        app.register_blueprint(api_bp, url_prefix='/api/v1.0')
        t = 1
    if config_filename != TestingConfig:
        db.init_app(app)
        redis_cache.init_app(app)
    return app

@app.route('/')
@app.route('/api/')
@app.route('/api/v1.0/')
def availableApps():

    return render_template('availableApp.html')

PresentConfig = BaseConfig
app = create_app(PresentConfig)

if __name__ == "__main__":
    app.run(use_debugger=False, use_reloader=False, passthrough_errors=True)

I then ran this with uwsgi, and it works as expected:

uwsgi --http localhost:5000 --wsgi-file run.py --callable app

Thanks for your help!


Solution

  • This could quickly be solved by creating a new file wsgi.py, with the following contents:

    import run
    PresentConfig = run.BaseConfig
    app = run.create_app(PresentConfig)
    

    Then execute with:

    uwsgi --http localhost:5000 --wsgi-file wsgi.py --callable app
    

    or...

    gunicorn --bind 'localhost:5000' wsgi:app
    

    Why does this work...

    If you have a look inside the run.py file, and note what's happening when you launch that directly with python:

    if __name__ == "__main__":
        PresentConfig = BaseConfig
        app = create_app(PresentConfig)
        app.run(use_debugger=False, use_reloader=False, passthrough_errors=True)
    

    you can see that app is being created, based on the return value of the create_app function which is passed a config. Note also that the create_app function registers the "other URLs" as part of the api_bp blueprint.

    However the code inside this if clause is never executed when the app is executed with uwsgi/gunicorn; instead the app object which is imported is one without the other URLs registered.

    By creating the wsgi.py file above, you're doing all of this in a manner which can then be improted by the wsgi/gunicorn executable.


    With that in mind, another way to fix this would be to change the last four lines of run.py to look more like:

    PresentConfig = BaseConfig
    app = create_app(PresentConfig)
    
    if __name__ == "__main__":    
        app.run(use_debugger=False, use_reloader=False, passthrough_errors=True)
    

    You could then execute this with your original wsgi command.

    It's worth noting this may break other code which does from run import app and expects app not to be the return value of create_app (unlikely in this case).

    Let me know if you need clarification on anything.