├── .gitignore ├── LICENSE ├── README.md ├── flask_gunicorn.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2017 by Anthony Plunkett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-Gunicorn 2 | 3 | Flask-Gunicorn lets you simply run your Flask Application using the 4 | gunicorn application server easily from the command line. 5 | 6 | Unfortunately Gunicorn doesn't work on Windows machines, so you may 7 | want to look into alternative solutions for serving your application. 8 | 9 | ## Installation 10 | 11 | Install the extension with pip: 12 | 13 | ```sh 14 | $ pip install flask-gunicorn 15 | ``` 16 | 17 | ## Usage 18 | 19 | Once installed, Flask-Gunicorn automatically overides the `run` 20 | command in the `flask` command line tool. 21 | 22 | To use, simply let `flask` know where your application is by setting 23 | an environment variable: 24 | 25 | ```sh 26 | export FLASK_APP=myapp.py 27 | ``` 28 | 29 | Then run the server: 30 | 31 | ```sh 32 | flask run 33 | ``` 34 | 35 | By default Flask-Gunicorn will make a sensible guess at how many 36 | workers to allocate to the application server based the number of 37 | CPU cores on your machine, but this can be specified using the 38 | `--workers x` argument or `WORKERS` environment variable. 39 | 40 | 41 | the `flask run` command also takes serveral optional arguments to 42 | help you customize your gunicorn server. 43 | 44 | | Argument | Description| 45 | | ------------- | ------------- | 46 | | `--workers` | How many workers should Gunicorn spawn | 47 | | `--worker_class` | Should Gunicorn use a specific class of worker? E.g. gevent | 48 | | `--debugger` | Run the server with the interactive debugger | 49 | | `--no-debugger` | Turn off the debugger mode | 50 | | `--host` | What address should the server bind to (e.g. `127.0.0.1`) 51 | | `--port` | What port should be used (e.g. `5000`) | 52 | | `--reload` | Turn on the reloader (gunicorn will notice changes to code and restart if noticed) This is on by default in debug mode.| 53 | | `--noreload` | Turn off the reloader | 54 | 55 | 56 | ## Contributing 57 | 58 | Contributions will be gleefully received! Check out the current issues, or feel 59 | free to crate new issues for any problems you've encountered and we'll push this 60 | little project forwards. 61 | 62 | Thanks to [Flask-Common](https://github.com/kennethreitz/flask-common) and the 63 | [Gunicorn Docs on Custom Applications](http://docs.gunicorn.org/en/stable/custom.html) 64 | for hints on getting this all working. 65 | 66 | 67 | -------------------------------------------------------------------------------- /flask_gunicorn.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import gunicorn.app.base 3 | import flask.cli 4 | import click 5 | import os 6 | from gunicorn.six import iteritems 7 | from werkzeug.debug import DebuggedApplication 8 | 9 | 10 | def server_port(): 11 | if 'PORT' not in os.environ: 12 | return '5000' 13 | else: 14 | return os.environ['PORT'] 15 | 16 | 17 | def server_bind_address(): 18 | if 'HTTP_HOST' not in os.environ: 19 | return '127.0.0.1' 20 | else: 21 | return os.environ['HTTP_HOST'] 22 | 23 | 24 | def number_of_workers(): 25 | if not 'WEB_CONCURRENCY' in os.environ: 26 | return (multiprocessing.cpu_count() * 2) + 1 27 | else: 28 | return os.environ['WEB_CONCURRENCY'] 29 | 30 | 31 | class GunicornStandalone(gunicorn.app.base.BaseApplication): 32 | def __init__(self, application, options=None): 33 | """ Construct the Application. Default gUnicorn configuration is loaded """ 34 | 35 | self.application = application 36 | self.options = options or {} 37 | print(self.options) 38 | 39 | # if port, or host isn't set-- run from os.environments 40 | # 41 | super(GunicornStandalone, self).__init__() 42 | 43 | def init(self, parser, opts, args): 44 | """ Apply our custom settings """ 45 | 46 | cfg = {} 47 | for k, v in self.options.items(): 48 | if k.lower() in self.cfg.settings and v is not None: 49 | cfg[k.lower()] = v 50 | return cfg 51 | 52 | def load_config(self): 53 | config = dict([(key, value) for key, value in iteritems(self.options) 54 | if key in self.cfg.settings and value is not None]) 55 | for key, value in iteritems(config): 56 | self.cfg.set(key.lower(), value) 57 | 58 | def load(self): 59 | return self.application 60 | 61 | 62 | @click.command('run', 63 | short_help='Start serving application from Gunicorn.', 64 | context_settings={ 65 | 'allow_extra_args': True, 66 | 'ignore_unknown_options': True, 67 | 'allow_interspersed_args': False, 68 | }) 69 | @click.option('--host', '-h', default=None, help='The interface to bind to.') 70 | @click.option('--port', '-p', default=None, help='The port to bind to.') 71 | @click.option('--reload/--no-reload', default=None, 72 | help='Enable or disable the reloader. By default the reloader ' 73 | 'is active if debug is enabled.') 74 | @click.option('--debugger/--no-debugger', default=None, 75 | help='Enable or disable the debugger. By default the debugger ' 76 | 'is active if debug is enabled.') 77 | @click.option('--workers', '-w', default=number_of_workers(), help='Number of Gunicorn workers') 78 | @click.option('--worker_class', '-wc', default=None, help="Specify a custom class of worker to use") 79 | @flask.cli.pass_script_info 80 | def cli(info, host, port, reload, debugger, workers, worker_class): 81 | 82 | os.environ['FLASK_RUN_FROM_CLI_SERVER'] = '1' 83 | debug = flask.cli.get_debug_flag() 84 | 85 | port = port or server_port() 86 | host = host or server_bind_address() 87 | 88 | options = { 89 | 'workers': workers or number_of_workers(), 90 | 'bind': '{}:{}'.format(host, port) 91 | } 92 | 93 | if worker_class is not None: 94 | options["worker_class"] = worker_class 95 | 96 | app = info.load_app() 97 | 98 | if debug or debugger: 99 | options["workers"] = 1 100 | app.wsgi_app = DebuggedApplication(app.wsgi_app, True) 101 | 102 | print(' * Launching in DEBUG mode') 103 | print(' * Serving Flask using a single worker "{}"'.format(info.app_import_path)) 104 | 105 | if reload is None: 106 | options["reload"] = bool(debug) 107 | if debugger is None: 108 | options["debug"] = bool(debug) or debugger 109 | 110 | else: 111 | print(' * Launching in Production Mode') 112 | print(' * Serving Flask with {} worker(s) "{}"'.format( 113 | options["workers"], info.app_import_path 114 | )) 115 | 116 | server = GunicornStandalone(app, options=options) 117 | server.run() 118 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='Flask-Gunicorn', 5 | version='0.1.1', 6 | license='MIT', 7 | url='https://github.com/doobeh/flask-gunicorn', 8 | entry_points=''' 9 | [flask.commands] 10 | run=flask_gunicorn:cli 11 | ''', 12 | author='Anthony Plunkett', 13 | py_modules=['flask_gunicorn'], 14 | install_requires=[ 15 | 'Flask', 16 | 'gunicorn', 17 | ] 18 | ) 19 | 20 | --------------------------------------------------------------------------------