Add support for a `gunicorn_pecan` console script.
This commit is contained in:
@@ -184,13 +184,4 @@ Pecan's default project::
|
|||||||
$ pip install gunicorn
|
$ pip install gunicorn
|
||||||
$ pecan create simpleapp && cd simpleapp
|
$ pecan create simpleapp && cd simpleapp
|
||||||
$ python setup.py develop
|
$ python setup.py develop
|
||||||
|
$ gunicorn_pecan config.py
|
||||||
Next, let's create a new file in the project root::
|
|
||||||
|
|
||||||
# wsgi.py
|
|
||||||
from pecan.deploy import deploy
|
|
||||||
application = deploy('config.py')
|
|
||||||
|
|
||||||
...and then run it with::
|
|
||||||
|
|
||||||
$ gunicorn wsgi
|
|
||||||
|
|||||||
@@ -132,3 +132,49 @@ class ServeCommand(BaseCommand):
|
|||||||
print(' $ pip install watchdog')
|
print(' $ pip install watchdog')
|
||||||
else:
|
else:
|
||||||
self._serve(app, conf)
|
self._serve(app, conf)
|
||||||
|
|
||||||
|
|
||||||
|
def gunicorn_run():
|
||||||
|
"""
|
||||||
|
The ``gunicorn_pecan`` command for launching ``pecan`` applications
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from gunicorn.app.wsgiapp import WSGIApplication
|
||||||
|
except ImportError as exc:
|
||||||
|
args = exc.args
|
||||||
|
arg0 = args[0] if args else ''
|
||||||
|
arg0 += ' (are you sure `gunicorn` is installed?)'
|
||||||
|
exc.args = (arg0,) + args[1:]
|
||||||
|
raise
|
||||||
|
|
||||||
|
class PecanApplication(WSGIApplication):
|
||||||
|
|
||||||
|
def init(self, parser, opts, args):
|
||||||
|
if len(args) != 1:
|
||||||
|
parser.error("No configuration file was specified.")
|
||||||
|
|
||||||
|
self.cfgfname = os.path.normpath(
|
||||||
|
os.path.join(os.getcwd(), args[0])
|
||||||
|
)
|
||||||
|
self.cfgfname = os.path.abspath(self.cfgfname)
|
||||||
|
if not os.path.exists(self.cfgfname):
|
||||||
|
parser.error("Config file not found: %s" % self.cfgfname)
|
||||||
|
|
||||||
|
from pecan.configuration import _runtime_conf, set_config
|
||||||
|
set_config(self.cfgfname, overwrite=True)
|
||||||
|
|
||||||
|
# If available, use the host and port from the pecan config file
|
||||||
|
cfg = {}
|
||||||
|
if _runtime_conf.get('server'):
|
||||||
|
server = _runtime_conf['server']
|
||||||
|
if hasattr(server, 'host') and hasattr(server, 'port'):
|
||||||
|
cfg['bind'] = '%s:%s' % (
|
||||||
|
server.host, server.port
|
||||||
|
)
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
from pecan.deploy import deploy
|
||||||
|
return deploy(self.cfgfname)
|
||||||
|
|
||||||
|
PecanApplication("%(prog)s [OPTIONS] config.py").run()
|
||||||
|
|||||||
@@ -182,13 +182,13 @@ class TestTemplateBuilds(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Make a temp install location and record the cwd
|
# Make a temp install location and record the cwd
|
||||||
self.install()
|
self.install_scaffolded_package()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(self.install_dir)
|
shutil.rmtree(self.install_dir)
|
||||||
os.chdir(self.cwd)
|
os.chdir(self.cwd)
|
||||||
|
|
||||||
def install(self):
|
def create_virtualenv(self):
|
||||||
# Create a new virtualenv in the temp install location
|
# Create a new virtualenv in the temp install location
|
||||||
import virtualenv
|
import virtualenv
|
||||||
virtualenv.create_environment(
|
virtualenv.create_environment(
|
||||||
@@ -198,6 +198,8 @@ class TestTemplateBuilds(unittest.TestCase):
|
|||||||
# chdir into the pecan source
|
# chdir into the pecan source
|
||||||
os.chdir(pkg_resources.get_distribution('pecan').location)
|
os.chdir(pkg_resources.get_distribution('pecan').location)
|
||||||
|
|
||||||
|
def install_scaffolded_package(self):
|
||||||
|
self.create_virtualenv()
|
||||||
py_exe = os.path.join(self.install_dir, 'bin', 'python')
|
py_exe = os.path.join(self.install_dir, 'bin', 'python')
|
||||||
pecan_exe = os.path.join(self.install_dir, 'bin', 'pecan')
|
pecan_exe = os.path.join(self.install_dir, 'bin', 'pecan')
|
||||||
|
|
||||||
@@ -219,6 +221,21 @@ class TestTemplateBuilds(unittest.TestCase):
|
|||||||
'develop'
|
'develop'
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def install_dependency(self, name):
|
||||||
|
pip_exe = os.path.join(self.install_dir, 'bin', 'pip')
|
||||||
|
proc = subprocess.Popen([
|
||||||
|
pip_exe,
|
||||||
|
'install',
|
||||||
|
name
|
||||||
|
])
|
||||||
|
proc.wait()
|
||||||
|
|
||||||
|
return os.path.join(
|
||||||
|
self.install_dir,
|
||||||
|
'bin',
|
||||||
|
name
|
||||||
|
)
|
||||||
|
|
||||||
def poll(self, proc):
|
def poll(self, proc):
|
||||||
limit = 30
|
limit = 30
|
||||||
for i in range(limit):
|
for i in range(limit):
|
||||||
@@ -228,7 +245,7 @@ class TestTemplateBuilds(unittest.TestCase):
|
|||||||
if proc.returncode is None:
|
if proc.returncode is None:
|
||||||
break
|
break
|
||||||
elif i == limit: # pragma: no cover
|
elif i == limit: # pragma: no cover
|
||||||
raise RuntimeError("pecan serve config.py didn't start.")
|
raise RuntimeError("Server process didn't start.")
|
||||||
time.sleep(.1)
|
time.sleep(.1)
|
||||||
|
|
||||||
@unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.')
|
@unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.')
|
||||||
@@ -330,16 +347,9 @@ class TestTemplateBuilds(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
def test_project_passes_pep8(self):
|
def test_project_passes_pep8(self):
|
||||||
# Install pep8
|
# Install pep8
|
||||||
pip_exe = os.path.join(self.install_dir, 'bin', 'pip')
|
pep8_exe = self.install_dependency('pep8')
|
||||||
proc = subprocess.Popen([
|
|
||||||
pip_exe,
|
|
||||||
'install',
|
|
||||||
'pep8'
|
|
||||||
])
|
|
||||||
proc.wait()
|
|
||||||
|
|
||||||
# Run pep8 on setup.py and the project
|
# Run pep8 on setup.py and the project
|
||||||
pep8_exe = os.path.join(self.install_dir, 'bin', 'pep8')
|
|
||||||
proc = subprocess.Popen([
|
proc = subprocess.Popen([
|
||||||
pep8_exe,
|
pep8_exe,
|
||||||
'setup.py',
|
'setup.py',
|
||||||
@@ -353,3 +363,72 @@ class TestTemplateBuilds(unittest.TestCase):
|
|||||||
# No output == good
|
# No output == good
|
||||||
output = proc.stdout.read()
|
output = proc.stdout.read()
|
||||||
assert output == ''
|
assert output == ''
|
||||||
|
|
||||||
|
|
||||||
|
class TestGunicornServeCommand(TestTemplateBuilds):
|
||||||
|
|
||||||
|
def create_virtualenv(self):
|
||||||
|
super(TestGunicornServeCommand, self).create_virtualenv()
|
||||||
|
|
||||||
|
# Install gunicorn
|
||||||
|
self.gunicorn_exe = self.install_dependency('gunicorn')
|
||||||
|
|
||||||
|
def install_dependency(self, name):
|
||||||
|
return super(
|
||||||
|
TestGunicornServeCommand,
|
||||||
|
self
|
||||||
|
).install_dependency(name).replace(
|
||||||
|
'gunicorn', 'gunicorn_pecan'
|
||||||
|
)
|
||||||
|
|
||||||
|
def poll_gunicorn(self, proc, port):
|
||||||
|
try:
|
||||||
|
self.poll(proc)
|
||||||
|
retries = 30
|
||||||
|
while True:
|
||||||
|
retries -= 1
|
||||||
|
if retries < 0: # pragma: nocover
|
||||||
|
raise RuntimeError(
|
||||||
|
"The gunicorn server has not replied within 3 seconds."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
# ...and that it's serving (valid) content...
|
||||||
|
resp = urllib2.urlopen('http://localhost:%d/' % port)
|
||||||
|
assert resp.getcode() == 200
|
||||||
|
assert 'This is a sample Pecan project.' in resp.read()
|
||||||
|
except urllib2.URLError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
time.sleep(.1)
|
||||||
|
finally:
|
||||||
|
proc.terminate()
|
||||||
|
|
||||||
|
@unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.')
|
||||||
|
@unittest.skipUnless(
|
||||||
|
getattr(pecan, '__run_all_tests__', False) is True,
|
||||||
|
'Skipping (slow). To run, `$ python setup.py test --functional.`'
|
||||||
|
)
|
||||||
|
def test_serve_from_config(self):
|
||||||
|
# Start the server
|
||||||
|
proc = subprocess.Popen([
|
||||||
|
self.gunicorn_exe,
|
||||||
|
'config.py'
|
||||||
|
])
|
||||||
|
|
||||||
|
self.poll_gunicorn(proc, 8080)
|
||||||
|
|
||||||
|
@unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.')
|
||||||
|
@unittest.skipUnless(
|
||||||
|
getattr(pecan, '__run_all_tests__', False) is True,
|
||||||
|
'Skipping (slow). To run, `$ python setup.py test --functional.`'
|
||||||
|
)
|
||||||
|
def test_serve_with_custom_bind(self):
|
||||||
|
# Start the server
|
||||||
|
proc = subprocess.Popen([
|
||||||
|
self.gunicorn_exe,
|
||||||
|
'--bind=0.0.0.0:9191',
|
||||||
|
'config.py'
|
||||||
|
])
|
||||||
|
|
||||||
|
self.poll_gunicorn(proc, 9191)
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -31,7 +31,8 @@ tests_require = requirements + [
|
|||||||
'virtualenv',
|
'virtualenv',
|
||||||
'Genshi',
|
'Genshi',
|
||||||
'Kajiki',
|
'Kajiki',
|
||||||
'Jinja'
|
'Jinja',
|
||||||
|
'gunicorn'
|
||||||
]
|
]
|
||||||
if sys.version_info < (2, 7):
|
if sys.version_info < (2, 7):
|
||||||
tests_require += ['unittest2']
|
tests_require += ['unittest2']
|
||||||
@@ -102,5 +103,6 @@ setup(
|
|||||||
base = pecan.scaffolds:BaseScaffold
|
base = pecan.scaffolds:BaseScaffold
|
||||||
[console_scripts]
|
[console_scripts]
|
||||||
pecan = pecan.commands:CommandRunner.handle_command_line
|
pecan = pecan.commands:CommandRunner.handle_command_line
|
||||||
|
gunicorn_pecan = pecan.commands.serve:gunicorn_run
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user