0

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!

1 Answers1

2

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.

v25
  • 7,096
  • 2
  • 20
  • 36
  • So . . . it's still not working. I tried both methods, and both of them behave the same way - the root routes work, but any of the routes in the blueprint fail. Also, I'm a Python n00b, so yes, I'm still a bit confused by the `if __name__ == "__main__": ` block. My background is PHP (and C++ in another life). Why does the `if` statement not get executed by uwsgi? The syntax is rather C++ like, so I figured it would run. – Arthur Sommers Mar 13 '20 at 01:59
  • @ArthurSommers When the python interpreter reads a source file directly it sets `__name__` to `"__main__"`. That doesn't happen when another python module imports something from `run.py`. Therefor anything in that `if` clause only gets executed when you're doing `python run.py` (uwsgi/gunicorn are *importing* from `run.py`) - [this answer](https://stackoverflow.com/a/419185/2052575) has more info regarding this. – v25 Mar 13 '20 at 02:15
  • Added it above. Thanks for your help so far, @v25 ! – Arthur Sommers Mar 13 '20 at 05:38
  • Well, aren't I just stupid - I was trying it on a route that I had removed in my production system. Jeez . . . your solution worked perfectly. Updating my question and marking this as the solution. Thanks a bunch! – Arthur Sommers Mar 13 '20 at 17:46
  • Yay! Figured it might be something like this. Glad you got it working :) – v25 Mar 13 '20 at 17:59