Gunicorn Autoreload on Source Change

gunicorn autoreload on source change

While this is old question you need to know that ever since version 19.0 gunicorn has had the --reload option.
So now no third party tools are needed.

Gunicorn reflect changed code dynamically

I was able to solve this by changing the extension of the python scripts to anything but .py

Then I loaded these files using the following function:

from importlib import util
from immportlib.machinary import SourceFileLoader

def load_module(module_name, modele_path):
module_path = path.join(path.dirname(__file__), "/path/to/your/files{}.anyextension".format(module_name))
spec = util.spec_from_loader(module_name,
SourceFileLoader(module_name, module_path))
module = util.module_from_spec(spec)
spec.loader.exec_module(module)
return module

In this case, they are not loaded by Gunicorn in RAM and I was able to apply the changes on fly without the need to apply eval or exec functiong.

Auto reloading Flask app when source code changes

The issue here, as stated in other answers, is that it looks like you moved from python run.py to foreman start, or you changed your Procfile from

# Procfile
web: python run.py

to

# Procfile
web: gunicorn --log-level=DEBUG run:app

When you run foreman start, it simply runs the commands that you've specified in the Procfile. (I'm going to guess you're working with Heroku, but even if not, this is nice because it will mimic what's going to run on your server/Heroku dyno/whatever.)

So now, when you run gunicorn --log-level=DEBUG run:app (via foreman start) you are now running your application with gunicorn rather than the built in webserver that comes with Flask. The run:app argument tells gunicorn to look in run.py for a Flask instance named app, import it, and run it. This is where it get's fun: since the run.py is being imported, __name__ == '__main__' is False (see more on that here), and so app.run(debug = True, port = 5000) is never called.

This is what you want (at least in a setting that's available publicly) because the webserver that's built into Flask that's used when app.run() is called has some pretty serious security vulnerabilities. The --log-level=DEBUG may also be a bit deceiving since it uses the word "DEBUG" but it's only telling gunicorn which logging statements to print and which to ignore (check out the Python docs on logging.)

The solution is to run python run.py when running the app locally and working/debugging on it, and only run foreman start when you want to mimic a production environment. Also, since gunicorn only needs to import the app object, you could remove some ambiguity and change your Procfile to

# Procfile
web: gunicorn --log-level=DEBUG app:app

You could also look into Flask Script which has a built in command python manage.py runserver that runs the built in Flask webserver in debug mode.

Gunicorn :: reload from the application itself

Guess off the top of my head is that the restart is not working because the process calling the reload is being killed. Maybe try to daemonize a subprocess that exits after calling the reload? Take a look at this post:

spawning process from python

Why server is not detecting changes source code in my python project?

It is hard to what the reason is, as there are many "moving" parts (the IDE, Gunicorn, etc); but I cannot reproduce your issue.

Maybe the server was not reloaded or restarted, against your expectation, and you are simply still dealing with Gunicorn running old code?
If you are sure you have restarted your debug server, then it should not matter; otherwise make sure to pass the --reload option, see also: gunicorn autoreload on source change.

As to the application itself, the following MRE works for me:

import falcon

class Contest:
def on_get(self, req, resp, contest_id):
resp.media = {
'contest_id': contest_id,
'uri_template': req.uri_template,
}

def on_get_ping(self, req, resp):
resp.content_type = falcon.MEDIA_TEXT
resp.text = 'PONG\n'

application = falcon.App()

contest = Contest()
application.add_route('/api/v1/ping', contest, suffix='ping')
application.add_route('/api/v1/member/contest/{contest_id:int}', contest)
application.add_route('/api/v1/member/contest/new/{contest_id:int}', contest)

When running with gunicorn --reload --access-logfile - test:application, I can even comment out routes or bring them back, save, and the changes are reflected in the application's behaviour.

Checking the end points in question:

$ curl http://localhost:8000/api/v1/ping
PONG
$ curl http://localhost:8000/api/v1/member/contest/1
{"contest_id": 1, "uri_template": "/api/v1/member/contest/{contest_id:int}"}
$ curl http://localhost:8000/api/v1/member/contest/new/2
{"contest_id": 2, "uri_template": "/api/v1/member/contest/new/{contest_id:int}"}


Related Topics



Leave a reply



Submit