Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Flask-Script and Gunicorn

I'm working on on a Flask app using Flask's built in dev server. I start it using Flask-Script. I want to switch to using Gunicorn as the web server. To do so, do I need to write some sort of integration code between Flask-Script and Gunicorn? Or is Flask-Script irrelevant to running the app using Gunicorn?

Thanks in advance!

Props to @sean-lynch. The following is working, tested code based on his answer. The changes I made were:

  • Options that aren't recognized by Gunicorn are removed from sys.argv in remove_non_gunicorn_command_line_args() before trying to start the server. Otherwise Gunicorn throws an error with a message like this: error: unrecognized arguments: --port 5010. I remove -p because, even though it doesn't cause the error, that's only because Gunicorn thinks its the short form of its pidfile option, which is obviously not what's intended.

  • GunicornServer.handle() signature modified to match the method it overrides i.e. Command.handle()

-

from flask_script import Command
from gunicorn.app.base import Application

class GunicornServer(Command):

    description = 'Run the app within Gunicorn'

    def __init__(self, host='127.0.0.1', port=8000, workers=6):

        self.port = port
        self.host = host
        self.workers = workers

    def get_options(self):
        return (
            Option('-t', '--host',
                   dest='host',
                   default=self.host),

            Option('-p', '--port',
                   dest='port',
                   type=int,
                   default=self.port),

            Option('-w', '--workers',
                   dest='workers',
                   type=int,
                   default=self.workers),
        )

    def handle(self, app, *args, **kwargs):

        host = kwargs['host']
        port = kwargs['port']
        workers = kwargs['workers']

        def remove_non_gunicorn_command_line_args():
            import sys
            args_to_remove = ['--port','-p']
            def args_filter(name_or_value):
                keep = not args_to_remove.count(name_or_value)
                if keep:
                    previous = sys.argv[sys.argv.index(name_or_value) - 1]
                    keep = not args_to_remove.count(previous)
                return keep
            sys.argv = filter(args_filter, sys.argv)

        remove_non_gunicorn_command_line_args()

        from gunicorn import version_info
        if version_info < (0, 9, 0):
            from gunicorn.arbiter import Arbiter
            from gunicorn.config import Config
            arbiter = Arbiter(Config({'bind': "%s:%d" % (host, int(port)),'workers': workers}), app)
            arbiter.run()
        else:
            class FlaskApplication(Application):
                def init(self, parser, opts, args):
                    return {
                        'bind': '{0}:{1}'.format(host, port),
                        'workers': workers
                    }

                def load(self):
                    return app

            FlaskApplication().run()

manager.add_command('gunicorn', GunicornServer())
like image 288
Julian A. Avatar asked Jan 28 '13 16:01

Julian A.


People also ask

Which is better Gunicorn or uWSGI?

Both can reach very impressive levels of performance, though some have mentioned that Gunicorn works better under high load. Drawbacks to Gunicorn are much the same as uWSGI, though I personally have found Gunicorn to be more easily configurable than uWSGI.

Can I use Gunicorn in production?

Running a local server of Django is not a recommended way in production because it's just a test server not a production ready server. So to run Django in production is to run with Gunicorn and use Nginx as a reverse proxy so it gives more security to our application.


2 Answers

As Dhaivat said, you can just use your Flask app directly with Gunicorn.

If you still want to use Flask-Script, you will need to create a custom Command. I don't have any experience with Gunicorn, but I found a similar solution for Flask-Actions and ported it to Flask-Script, although be warned, it's untested.

from flask_script import Command, Option

class GunicornServer(Command):

    description = 'Run the app within Gunicorn'

    def __init__(self, host='127.0.0.1', port=8000, workers=4):
        self.port = port
        self.host = host
        self.workers = workers

    def get_options(self):
        return (
            Option('-H', '--host',
                   dest='host',
                   default=self.host),

            Option('-p', '--port',
                   dest='port',
                   type=int,
                   default=self.port),

            Option('-w', '--workers',
                   dest='workers',
                   type=int,
                   default=self.workers),
        )

    def handle(self, app, host, port, workers):

        from gunicorn import version_info

        if version_info < (0, 9, 0):
            from gunicorn.arbiter import Arbiter
            from gunicorn.config import Config
            arbiter = Arbiter(Config({'bind': "%s:%d" % (host, int(port)),'workers': workers}), app)
            arbiter.run()
        else:
            from gunicorn.app.base import Application

            class FlaskApplication(Application):
                def init(self, parser, opts, args):
                    return {
                        'bind': '{0}:{1}'.format(host, port),
                        'workers': workers 
                    }

                def load(self):
                    return app

            FlaskApplication().run()

You can then either register it to replace Flask's local development server at python manage.py runserver

manager.add_command("runserver", GunicornServer())

or register as a new command such as python manage.py gunicorn

manager.add_command("gunicorn", GunicornServer())

Edit June 2016: With the latest version of Flask-Script, change the method handle with __call__. old flask-script vs new flask-script

like image 194
Sean Lynch Avatar answered Sep 30 '22 23:09

Sean Lynch


I wrote a better version of GunicornServer based on Sean Lynch's, the command now accept all gunicorn's arguments

from yourapp import app
from flask.ext.script import Manager, Command, Option

class GunicornServer(Command):
    """Run the app within Gunicorn"""

    def get_options(self):
        from gunicorn.config import make_settings

        settings = make_settings()
        options = (
            Option(*klass.cli, action=klass.action)
            for setting, klass in settings.iteritems() if klass.cli
        )
        return options

    def run(self, *args, **kwargs):
        from gunicorn.app.wsgiapp import WSGIApplication

        app = WSGIApplication()
        app.app_uri = 'manage:app'
        return app.run()

manager = Manager(app)
manager.add_command("gunicorn", GunicornServer())
like image 21
menghan Avatar answered Sep 30 '22 23:09

menghan