From 61e136b92250ead629ff0439be7447301fcc0440 Mon Sep 17 00:00:00 2001 From: Randall Leeds Date: Sun, 12 Mar 2017 17:14:36 -0700 Subject: [PATCH] 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, )