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
This commit is contained in:
Randall Leeds 2017-03-12 17:14:36 -07:00
parent 7af6f651c0
commit 61e136b922
3 changed files with 67 additions and 204 deletions

View File

@ -3,206 +3,73 @@
# This file is part of gunicorn released under the MIT license. # This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information. # See the NOTICE for more information.
# pylint: skip-file import configparser
import os import os
import pkg_resources
import sys
try: from paste.deploy import loadapp
import configparser as ConfigParser
except ImportError:
import ConfigParser
from paste.deploy import loadapp, loadwsgi from gunicorn.app.wsgiapp import WSGIApplication
SERVER = loadwsgi.SERVER from gunicorn.config import get_default_config_file
from gunicorn.app.base import Application
from gunicorn.config import Config, get_default_config_file
from gunicorn import util
def _has_logging_config(paste_file): def get_wsgi_app(config_uri, name=None, defaults=None):
cfg_parser = ConfigParser.ConfigParser() if ':' not in config_uri:
cfg_parser.read([paste_file]) config_uri = "config:%s" % config_uri
return cfg_parser.has_section('loggers')
return loadapp(
config_uri,
name=name,
relative_to=os.getcwd(),
global_conf=defaults,
)
def paste_config(gconfig, config_url, relative_to, global_conf=None): def has_logging_config(config_file):
# add entry to pkg_resources parser = configparser.ConfigParser()
sys.path.insert(0, relative_to) parser.read([config_file])
pkg_resources.working_set.add_entry(relative_to) 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: if host and port:
cfg['bind'] = '%s:%s' % (host, port) local_conf['bind'] = '%s:%s' % (host, port)
elif host: 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 if has_logging_config(config_file):
config_file = config_url.split(':')[1] self.cfg.set("logconfig", config_file)
if _has_logging_config(config_file):
cfg.setdefault('logconfig', config_file)
for k, v in gc.items(): if gunicorn_config_file:
if k not in gconfig.settings: self.load_config_from_file(gunicorn_config_file)
continue else:
cfg[k] = v 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(): for k, v in local_conf.items():
if k not in gconfig.settings: if v is not None:
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:
self.cfg.set(k.lower(), v) 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"): def load(self):
self.load_config_from_file(cfg["config"]) return app
else:
default_config = get_default_config_file()
if default_config is not None:
self.load_config_from_file(default_config)
def load(self): PasterServerApplication().run()
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()

View File

@ -13,22 +13,21 @@ from gunicorn import util
class WSGIApplication(Application): class WSGIApplication(Application):
def init(self, parser, opts, args): def init(self, parser, opts, args):
if opts.paste: if opts.paste:
app_name = 'main' from .pasterapp import has_logging_config
path = opts.paste
if '#' in path:
path, app_name = path.split('#')
path = os.path.abspath(os.path.normpath(
os.path.join(util.getcwd(), path)))
if not os.path.exists(path): config_uri = os.path.abspath(opts.paste)
raise ConfigError("%r not found" % path) config_file = config_uri.split('#')[0]
# paste application, load the config if not os.path.exists(config_file):
self.cfgurl = 'config:%s#%s' % (path, app_name) raise ConfigError("%r not found" % config_file)
self.relpath = os.path.dirname(path)
from .pasterapp import paste_config self.cfg.set("default_proc_name", config_file)
return paste_config(self.cfg, self.cfgurl, self.relpath) self.app_uri = config_uri
if has_logging_config(config_file):
self.cfg.set("logconfig", config_file)
return
if not args: if not args:
parser.error("No application module specified.") parser.error("No application module specified.")
@ -37,13 +36,11 @@ class WSGIApplication(Application):
self.app_uri = args[0] self.app_uri = args[0]
def load_wsgiapp(self): def load_wsgiapp(self):
# load the app
return util.import_app(self.app_uri) return util.import_app(self.app_uri)
def load_pasteapp(self): def load_pasteapp(self):
# load the paste app from .pasterapp import get_wsgi_app
from .pasterapp import load_pasteapp return get_wsgi_app(self.app_uri, defaults=self.cfg.paste_global_conf)
return load_pasteapp(self.cfgurl, self.relpath, global_conf=self.cfg.paste_global_conf)
def load(self): def load(self):
if self.cfg.paste is not None: if self.cfg.paste is not None:

View File

@ -104,10 +104,9 @@ setup(
entry_points=""" entry_points="""
[console_scripts] [console_scripts]
gunicorn=gunicorn.app.wsgiapp:run gunicorn=gunicorn.app.wsgiapp:run
gunicorn_paster=gunicorn.app.pasterapp:run
[paste.server_runner] [paste.server_runner]
main=gunicorn.app.pasterapp:paste_server main=gunicorn.app.pasterapp:serve
""", """,
extras_require=extra_require, extras_require=extra_require,
) )