Merge pull request #1957 from benoitc/simplify-paste-integrations

Simplify Paste Deployment integration
This commit is contained in:
Benoit Chesneau 2019-02-06 09:17:28 +01:00 committed by GitHub
commit 194f47f92b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 114 additions and 214 deletions

View File

@ -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 <http://docs.gunicorn.org/en/latest/settings.html#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

View File

@ -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()

View File

@ -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:

View File

@ -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,
)