summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormarkmcclain <mark.mcclain@dreamhost.com>2013-01-11 07:44:33 -0800
committermarkmcclain <mark.mcclain@dreamhost.com>2013-01-11 07:44:33 -0800
commit39e8b17ec876d0dac6912f2b2b7fd0df57003274 (patch)
tree29a44be186363de8e989cf22ef5eed848cf3e115
parentc21a432703db0abfc8ee8e8deea9f22c2e9bd683 (diff)
parent4d3ff312e8edc120b0190ac185eefe930c4f53ba (diff)
downloadpecan-39e8b17ec876d0dac6912f2b2b7fd0df57003274.tar.gz
Merge pull request #161 from ryanpetrello/next
Add support for a ``gunicorn_pecan`` console script.
-rw-r--r--.travis.yml2
-rw-r--r--docs/source/deployment.rst11
-rw-r--r--pecan/commands/serve.py46
-rw-r--r--pecan/tests/test_scaffolds.py102
-rw-r--r--setup.py4
5 files changed, 142 insertions, 23 deletions
diff --git a/.travis.yml b/.travis.yml
index eecdb65..a6b1bc9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,5 +8,5 @@ branches:
only:
- master
- next
-install: pip install -r requirements.txt --use-mirrors
+install: pip install gunicorn && pip install -r requirements.txt --use-mirrors
script: python setup.py test --functional
diff --git a/docs/source/deployment.rst b/docs/source/deployment.rst
index 51dcfba..fd1fc25 100644
--- a/docs/source/deployment.rst
+++ b/docs/source/deployment.rst
@@ -184,13 +184,4 @@ Pecan's default project::
$ pip install gunicorn
$ pecan create simpleapp && cd simpleapp
$ python setup.py develop
-
-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
+ $ gunicorn_pecan config.py
diff --git a/pecan/commands/serve.py b/pecan/commands/serve.py
index bd161ab..119d8d7 100644
--- a/pecan/commands/serve.py
+++ b/pecan/commands/serve.py
@@ -132,3 +132,49 @@ class ServeCommand(BaseCommand):
print(' $ pip install watchdog')
else:
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()
diff --git a/pecan/tests/test_scaffolds.py b/pecan/tests/test_scaffolds.py
index de99807..1711337 100644
--- a/pecan/tests/test_scaffolds.py
+++ b/pecan/tests/test_scaffolds.py
@@ -182,13 +182,13 @@ class TestTemplateBuilds(unittest.TestCase):
def setUp(self):
# Make a temp install location and record the cwd
- self.install()
+ self.install_scaffolded_package()
def tearDown(self):
shutil.rmtree(self.install_dir)
os.chdir(self.cwd)
- def install(self):
+ def create_virtualenv(self):
# Create a new virtualenv in the temp install location
import virtualenv
virtualenv.create_environment(
@@ -198,6 +198,8 @@ class TestTemplateBuilds(unittest.TestCase):
# chdir into the pecan source
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')
pecan_exe = os.path.join(self.install_dir, 'bin', 'pecan')
@@ -219,6 +221,22 @@ class TestTemplateBuilds(unittest.TestCase):
'develop'
])
+ def install_dependency(self, name):
+ pip_exe = os.path.join(self.install_dir, 'bin', 'pip')
+ proc = subprocess.Popen([
+ pip_exe,
+ 'install',
+ '-U',
+ name
+ ])
+ proc.wait()
+
+ return os.path.join(
+ self.install_dir,
+ 'bin',
+ name
+ )
+
def poll(self, proc):
limit = 30
for i in range(limit):
@@ -228,7 +246,7 @@ class TestTemplateBuilds(unittest.TestCase):
if proc.returncode is None:
break
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)
@unittest.skipUnless(has_internet(), 'Internet connectivity unavailable.')
@@ -330,16 +348,9 @@ class TestTemplateBuilds(unittest.TestCase):
)
def test_project_passes_pep8(self):
# Install pep8
- pip_exe = os.path.join(self.install_dir, 'bin', 'pip')
- proc = subprocess.Popen([
- pip_exe,
- 'install',
- 'pep8'
- ])
- proc.wait()
+ pep8_exe = self.install_dependency('pep8')
# Run pep8 on setup.py and the project
- pep8_exe = os.path.join(self.install_dir, 'bin', 'pep8')
proc = subprocess.Popen([
pep8_exe,
'setup.py',
@@ -353,3 +364,72 @@ class TestTemplateBuilds(unittest.TestCase):
# No output == good
output = proc.stdout.read()
assert output == ''
+
+
+class TestGunicornServeCommand(TestTemplateBuilds):
+
+ def create_virtualenv(self):
+ super(TestGunicornServeCommand, self).create_virtualenv()
+
+ # Install gunicorn
+ self.install_dependency('gunicorn')
+
+ @property
+ def gunicorn_exe(self):
+ return os.path.join(
+ self.install_dir,
+ 'bin',
+ '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)
diff --git a/setup.py b/setup.py
index e4356c9..097fc12 100644
--- a/setup.py
+++ b/setup.py
@@ -31,7 +31,8 @@ tests_require = requirements + [
'virtualenv',
'Genshi',
'Kajiki',
- 'Jinja'
+ 'Jinja',
+ 'gunicorn'
]
if sys.version_info < (2, 7):
tests_require += ['unittest2']
@@ -102,5 +103,6 @@ setup(
base = pecan.scaffolds:BaseScaffold
[console_scripts]
pecan = pecan.commands:CommandRunner.handle_command_line
+ gunicorn_pecan = pecan.commands.serve:gunicorn_run
"""
)