diff options
author | markmcclain <mark.mcclain@dreamhost.com> | 2013-01-11 07:44:33 -0800 |
---|---|---|
committer | markmcclain <mark.mcclain@dreamhost.com> | 2013-01-11 07:44:33 -0800 |
commit | 39e8b17ec876d0dac6912f2b2b7fd0df57003274 (patch) | |
tree | 29a44be186363de8e989cf22ef5eed848cf3e115 | |
parent | c21a432703db0abfc8ee8e8deea9f22c2e9bd683 (diff) | |
parent | 4d3ff312e8edc120b0190ac185eefe930c4f53ba (diff) | |
download | pecan-39e8b17ec876d0dac6912f2b2b7fd0df57003274.tar.gz |
Merge pull request #161 from ryanpetrello/next
Add support for a ``gunicorn_pecan`` console script.
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | docs/source/deployment.rst | 11 | ||||
-rw-r--r-- | pecan/commands/serve.py | 46 | ||||
-rw-r--r-- | pecan/tests/test_scaffolds.py | 102 | ||||
-rw-r--r-- | setup.py | 4 |
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) @@ -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 """ ) |