From 61e136b92250ead629ff0439be7447301fcc0440 Mon Sep 17 00:00:00 2001 From: Randall Leeds Date: Sun, 12 Mar 2017 17:14:36 -0700 Subject: [PATCH 1/2] Simplify Paste Deployment integration Remove the `gunicorn_paster` command. With the `--paste` option to the `gunicorn` command, Gunicorn will no longer read the server section of the configuration. Instead, server configuration must be done with Gunicorn configuration files, command line switches, and environment variables. The use of Gunicorn as a Paste Deployment server factory is no longer deprecated. It allows specifying `host` and `port`, as well as `bind`, but is otherwise more strict with options than in the past. Rather than ignoring unknown options it will raise an error. Close #1189 --- gunicorn/app/pasterapp.py | 237 +++++++++----------------------------- gunicorn/app/wsgiapp.py | 31 +++-- setup.py | 3 +- 3 files changed, 67 insertions(+), 204 deletions(-) diff --git a/gunicorn/app/pasterapp.py b/gunicorn/app/pasterapp.py index 0f9de435..4c9fc7de 100644 --- a/gunicorn/app/pasterapp.py +++ b/gunicorn/app/pasterapp.py @@ -3,206 +3,73 @@ # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. -# pylint: skip-file - +import configparser import os -import pkg_resources -import sys -try: - import configparser as ConfigParser -except ImportError: - import ConfigParser +from paste.deploy import loadapp -from paste.deploy import loadapp, loadwsgi -SERVER = loadwsgi.SERVER - -from gunicorn.app.base import Application -from gunicorn.config import Config, get_default_config_file -from gunicorn import util +from gunicorn.app.wsgiapp import WSGIApplication +from gunicorn.config import get_default_config_file -def _has_logging_config(paste_file): - cfg_parser = ConfigParser.ConfigParser() - cfg_parser.read([paste_file]) - return cfg_parser.has_section('loggers') +def get_wsgi_app(config_uri, name=None, defaults=None): + if ':' not in config_uri: + config_uri = "config:%s" % config_uri + + return loadapp( + config_uri, + name=name, + relative_to=os.getcwd(), + global_conf=defaults, + ) -def paste_config(gconfig, config_url, relative_to, global_conf=None): - # add entry to pkg_resources - sys.path.insert(0, relative_to) - pkg_resources.working_set.add_entry(relative_to) +def has_logging_config(config_file): + parser = configparser.ConfigParser() + parser.read([config_file]) + return parser.has_section('loggers') - config_url = config_url.split('#')[0] - cx = loadwsgi.loadcontext(SERVER, config_url, relative_to=relative_to, - global_conf=global_conf) - gc, lc = cx.global_conf.copy(), cx.local_conf.copy() - cfg = {} - host, port = lc.pop('host', ''), lc.pop('port', '') +def serve(app, global_conf, **local_conf): + """\ + A Paste Deployment server runner. + + Example configuration: + + [server:main] + use = egg:gunicorn#main + host = 127.0.0.1 + port = 5000 + """ + config_file = global_conf['__file__'] + gunicorn_config_file = local_conf.pop('config', None) + + host = local_conf.pop('host', '') + port = local_conf.pop('port', '') if host and port: - cfg['bind'] = '%s:%s' % (host, port) + local_conf['bind'] = '%s:%s' % (host, port) elif host: - cfg['bind'] = host.split(',') + local_conf['bind'] = host.split(',') - cfg['default_proc_name'] = gc.get('__file__') + class PasterServerApplication(WSGIApplication): + def load_config(self): + self.cfg.set("default_proc_name", config_file) - # init logging configuration - config_file = config_url.split(':')[1] - if _has_logging_config(config_file): - cfg.setdefault('logconfig', config_file) + if has_logging_config(config_file): + self.cfg.set("logconfig", config_file) - for k, v in gc.items(): - if k not in gconfig.settings: - continue - cfg[k] = v + if gunicorn_config_file: + self.load_config_from_file(gunicorn_config_file) + else: + default_gunicorn_config_file = get_default_config_file() + if default_gunicorn_config_file is not None: + self.load_config_from_file(default_gunicorn_config_file) - for k, v in lc.items(): - if k not in gconfig.settings: - continue - cfg[k] = v - - return cfg - - -def load_pasteapp(config_url, relative_to, global_conf=None): - return loadapp(config_url, relative_to=relative_to, - global_conf=global_conf) - -class PasterBaseApplication(Application): - gcfg = None - - def app_config(self): - return paste_config(self.cfg, self.cfgurl, self.relpath, - global_conf=self.gcfg) - - def load_config(self): - super(PasterBaseApplication, self).load_config() - - # reload logging conf - if hasattr(self, "cfgfname"): - parser = ConfigParser.ConfigParser() - parser.read([self.cfgfname]) - if parser.has_section('loggers'): - from logging.config import fileConfig - config_file = os.path.abspath(self.cfgfname) - fileConfig(config_file, dict(__file__=config_file, - here=os.path.dirname(config_file))) - - -class PasterApplication(PasterBaseApplication): - - def init(self, parser, opts, args): - if len(args) != 1: - parser.error("No application name specified.") - - cwd = util.getcwd() - cfgfname = os.path.normpath(os.path.join(cwd, args[0])) - cfgfname = os.path.abspath(cfgfname) - if not os.path.exists(cfgfname): - parser.error("Config file not found: %s" % cfgfname) - - self.cfgurl = 'config:%s' % cfgfname - self.relpath = os.path.dirname(cfgfname) - self.cfgfname = cfgfname - - sys.path.insert(0, self.relpath) - pkg_resources.working_set.add_entry(self.relpath) - - return self.app_config() - - def load(self): - # chdir to the configured path before loading, - # default is the current dir - os.chdir(self.cfg.chdir) - - return load_pasteapp(self.cfgurl, self.relpath, global_conf=self.gcfg) - - -class PasterServerApplication(PasterBaseApplication): - - def __init__(self, app, gcfg=None, host="127.0.0.1", port=None, **kwargs): - # pylint: disable=super-init-not-called - self.cfg = Config() - self.gcfg = gcfg # need to hold this for app_config - self.app = app - self.callable = None - - gcfg = gcfg or {} - cfgfname = gcfg.get("__file__") - if cfgfname is not None: - self.cfgurl = 'config:%s' % cfgfname - self.relpath = os.path.dirname(cfgfname) - self.cfgfname = cfgfname - - cfg = kwargs.copy() - - if port and not host.startswith("unix:"): - bind = "%s:%s" % (host, port) - else: - bind = host - cfg["bind"] = bind.split(',') - - if gcfg: - for k, v in gcfg.items(): - cfg[k] = v - cfg["default_proc_name"] = cfg['__file__'] - - try: - for k, v in cfg.items(): - if k.lower() in self.cfg.settings and v is not None: + for k, v in local_conf.items(): + if v is not None: self.cfg.set(k.lower(), v) - except Exception as e: - print("\nConfig error: %s" % str(e), file=sys.stderr) - sys.stderr.flush() - sys.exit(1) - if cfg.get("config"): - self.load_config_from_file(cfg["config"]) - else: - default_config = get_default_config_file() - if default_config is not None: - self.load_config_from_file(default_config) + def load(self): + return app - def load(self): - return self.app - - -def run(): - """\ - The ``gunicorn_paster`` command for launching Paster compatible - applications like Pylons or Turbogears2 - """ - util.warn("""This command is deprecated. - - You should now use the `--paste` option. Ex.: - - gunicorn --paste development.ini - """) - - from gunicorn.app.pasterapp import PasterApplication - PasterApplication("%(prog)s [OPTIONS] pasteconfig.ini").run() - - -def paste_server(app, gcfg=None, host="127.0.0.1", port=None, **kwargs): - """\ - A paster server. - - Then entry point in your paster ini file should looks like this: - - [server:main] - use = egg:gunicorn#main - host = 127.0.0.1 - port = 5000 - - """ - - util.warn("""This command is deprecated. - - You should now use the `--paste` option. Ex.: - - gunicorn --paste development.ini - """) - - from gunicorn.app.pasterapp import PasterServerApplication - PasterServerApplication(app, gcfg=gcfg, host=host, port=port, **kwargs).run() + PasterServerApplication().run() diff --git a/gunicorn/app/wsgiapp.py b/gunicorn/app/wsgiapp.py index 916a2b1d..c8501e5f 100644 --- a/gunicorn/app/wsgiapp.py +++ b/gunicorn/app/wsgiapp.py @@ -13,22 +13,21 @@ from gunicorn import util class WSGIApplication(Application): def init(self, parser, opts, args): if opts.paste: - app_name = 'main' - path = opts.paste - if '#' in path: - path, app_name = path.split('#') - path = os.path.abspath(os.path.normpath( - os.path.join(util.getcwd(), path))) + from .pasterapp import has_logging_config - if not os.path.exists(path): - raise ConfigError("%r not found" % path) + config_uri = os.path.abspath(opts.paste) + config_file = config_uri.split('#')[0] - # paste application, load the config - self.cfgurl = 'config:%s#%s' % (path, app_name) - self.relpath = os.path.dirname(path) + if not os.path.exists(config_file): + raise ConfigError("%r not found" % config_file) - from .pasterapp import paste_config - return paste_config(self.cfg, self.cfgurl, self.relpath) + self.cfg.set("default_proc_name", config_file) + self.app_uri = config_uri + + if has_logging_config(config_file): + self.cfg.set("logconfig", config_file) + + return if not args: parser.error("No application module specified.") @@ -37,13 +36,11 @@ class WSGIApplication(Application): self.app_uri = args[0] def load_wsgiapp(self): - # load the app return util.import_app(self.app_uri) def load_pasteapp(self): - # load the paste app - from .pasterapp import load_pasteapp - return load_pasteapp(self.cfgurl, self.relpath, global_conf=self.cfg.paste_global_conf) + from .pasterapp import get_wsgi_app + return get_wsgi_app(self.app_uri, defaults=self.cfg.paste_global_conf) def load(self): if self.cfg.paste is not None: diff --git a/setup.py b/setup.py index 19e85c94..fd24c0df 100644 --- a/setup.py +++ b/setup.py @@ -104,10 +104,9 @@ setup( entry_points=""" [console_scripts] gunicorn=gunicorn.app.wsgiapp:run - gunicorn_paster=gunicorn.app.pasterapp:run [paste.server_runner] - main=gunicorn.app.pasterapp:paste_server + main=gunicorn.app.pasterapp:serve """, extras_require=extra_require, ) From 47e208717b85a8feaccd160eefc4575a5551a55e Mon Sep 17 00:00:00 2001 From: Randall Leeds Date: Tue, 22 Jan 2019 03:21:52 -0800 Subject: [PATCH 2/2] Update integration docs for Paste Deployment --- docs/source/run.rst | 57 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/docs/source/run.rst b/docs/source/run.rst index d0799fa0..1ee68ee9 100644 --- a/docs/source/run.rst +++ b/docs/source/run.rst @@ -4,8 +4,9 @@ Running Gunicorn .. highlight:: bash -You can run Gunicorn by using commands or integrate with Django or Paster. For -deploying Gunicorn in production see :doc:`deploy`. +You can run Gunicorn by using commands or integrate with popular frameworks +like Django, Pyramid, or TurboGears. For deploying Gunicorn in production see +:doc:`deploy`. Commands ======== @@ -78,7 +79,7 @@ See :ref:`configuration` and :ref:`settings` for detailed usage. Integration =========== -We also provide integration for both Django and Paster applications. +Gunicorn also provides integration for Django and Paste Deploy applications. Django ------ @@ -104,13 +105,40 @@ option:: $ gunicorn --env DJANGO_SETTINGS_MODULE=myproject.settings myproject.wsgi -Paste ------ +Paste Deployment +---------------- -If you are a user/developer of a paste-compatible framework/app (as -Pyramid, Pylons and Turbogears) you can use the -`--paste `_ option -to run your application. +Frameworks such as Pyramid and Turbogears are typically configured using Paste +Deployment configuration files. If you would like to use these files with +Gunicorn, there are two approaches. + +As a server runner, Gunicorn can serve your application using the commands from +your framework, such as ``pserve`` or ``gearbox``. To use Gunicorn with these +commands, specify it as a server in your configuration file: + +.. code-block:: ini + + [server:main] + use = egg:gunicorn#main + host = 127.0.0.1 + port = 8080 + workers = 3 + +This approach is the quickest way to get started with Gunicorn, but there are +some limitations. Gunicorn will have no control over how the application is +loaded, so settings such as reload_ will have no effect and Gunicorn will be +unable to hot upgrade a running application. Using the daemon_ option may +confuse your command line tool. Instead, use the built-in support for these +features provided by that tool. For example, run ``pserve --reload`` instead of +specifying ``reload = True`` in the server configuration block. For advanced +configuration of Gunicorn, such as `Server Hooks`_ specifying a Gunicorn +configuration file using the ``config`` key is supported. + +To use the full power of Gunicorn's reloading and hot code upgrades, use the +`paste option`_ to run your application instead. When used this way, Gunicorn +will use the application defined by the PasteDeploy configuration file, but +Gunicorn will not use any server configuration defined in the file. Instead, +`configure gunicorn`_. For example:: @@ -120,4 +148,13 @@ Or use a different application:: $ gunicorn --paste development.ini#admin -b :8080 --chdir /path/to/project -It is all here. No configuration files nor additional Python modules to write! +With both approaches, Gunicorn will use any loggers section found in Paste +Deployment configuration file, unless instructed otherwise by specifying +additional `logging settings`_. + +.. _reload: http://docs.gunicorn.org/en/latest/settings.html#reload +.. _daemon: http://docs.gunicorn.org/en/latest/settings.html#daemon +.. _Server Hooks: http://docs.gunicorn.org/en/latest/settings.html#server-hooks +.. _paste option: http://docs.gunicorn.org/en/latest/settings.html#paste +.. _configure gunicorn: http://docs.gunicorn.org/en/latest/configure.html +.. _logging settings: http://docs.gunicorn.org/en/latest/settings.html#logging