summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2016-11-21 00:14:26 -0600
committerMichael Merickel <michael@merickel.org>2016-11-21 00:57:57 -0600
commit3f130937bcce073e9933e28c332e59c559025e07 (patch)
tree6842711ea8a9b50c4a68500745795750231d373a
parent52bfe0a8f2f6d305f0e3d8cead290427fd71e926 (diff)
downloadpyramid-3f130937bcce073e9933e28c332e59c559025e07.tar.gz
pyramid-3f130937bcce073e9933e28c332e59c559025e07.tar.bz2
pyramid-3f130937bcce073e9933e28c332e59c559025e07.zip
support a [pserve] config section with a list of files to watch
fixes #2732
-rw-r--r--docs/narr/project.rst20
-rw-r--r--pyramid/scripts/pserve.py58
-rw-r--r--pyramid/tests/test_scripts/test_pserve.py24
3 files changed, 73 insertions, 29 deletions
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 6c42881f4..b4ad6948e 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -1065,3 +1065,23 @@ hard drive, you should install the
`watchdog <http://pythonhosted.org/watchdog/>` package in development.
``hupper`` will automatically use ``watchdog`` to more efficiently poll the
filesystem.
+
+Monitoring Custom Files
+~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, ``pserve --reload`` will monitor all imported Python code
+(everything in ``sys.modules``) as well as the config file passed to
+``pserve`` (e.g. ``development.ini``). You can instruct ``pserve`` to watch
+other files for changes as well by defining a ``[pserve]`` section in your
+configuration file. For example, let's say your application loads the
+``favicon.ico`` file at startup and stores it in memory to efficiently
+serve it many times. When you change it you want ``pserve`` to restart:
+
+.. code-block:: ini
+
+ [pserve]
+ watch_files =
+ myapp/static/favicon.ico
+
+Paths are relative to the configuration file and are passed to ``hupper``
+to watch. Currently it does not support globs but this may change.
diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py
index 969bc07f1..c67dc86ba 100644
--- a/pyramid/scripts/pserve.py
+++ b/pyramid/scripts/pserve.py
@@ -28,9 +28,11 @@ from paste.deploy.loadwsgi import (
)
from pyramid.compat import PY2
+from pyramid.compat import configparser
from pyramid.scripts.common import parse_vars
from pyramid.scripts.common import setup_logging
+from pyramid.settings import aslist
def main(argv=sys.argv, quiet=False):
command = PServeCommand(argv, quiet=quiet)
@@ -97,12 +99,17 @@ class PServeCommand(object):
dest='verbose',
help="Suppress verbose output")
+ ConfigParser = configparser.ConfigParser # testing
+ loadapp = staticmethod(loadapp) # testing
+ loadserver = staticmethod(loadserver) # testing
+
_scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
def __init__(self, argv, quiet=False):
self.options, self.args = self.parser.parse_args(argv[1:])
if quiet:
self.options.verbose = 0
+ self.watch_files = []
def out(self, msg): # pragma: no cover
if self.options.verbose > 0:
@@ -112,6 +119,24 @@ class PServeCommand(object):
restvars = self.args[1:]
return parse_vars(restvars)
+ def pserve_file_config(self, filename):
+ config = self.ConfigParser()
+ config.optionxform = str
+ config.read(filename)
+ try:
+ items = dict(config.items('pserve'))
+ except configparser.NoSectionError:
+ return
+
+ watch_files = aslist(items.get('watch_files', ''), flatten=False)
+
+ # track file paths relative to the ini file
+ basedir = os.path.dirname(filename)
+ for file in watch_files:
+ if not os.path.isabs(file):
+ file = os.path.join(basedir, file)
+ self.watch_files.append(os.path.normpath(file))
+
def run(self): # pragma: no cover
if not self.args:
self.out('You must give a config file')
@@ -121,8 +146,12 @@ class PServeCommand(object):
vars = self.get_options()
app_name = self.options.app_name
+ base = os.getcwd()
if not self._scheme_re.search(app_spec):
+ config_path = os.path.join(base, app_spec)
app_spec = 'config:' + app_spec
+ else:
+ config_path = None
server_name = self.options.server_name
if self.options.server:
server_spec = 'egg:pyramid'
@@ -130,7 +159,6 @@ class PServeCommand(object):
server_name = self.options.server
else:
server_spec = app_spec
- base = os.getcwd()
# do not open the browser on each reload so check hupper first
if self.options.browser and not hupper.is_active():
@@ -155,22 +183,17 @@ class PServeCommand(object):
)
return 0
+ if config_path:
+ setup_logging(config_path, global_conf=vars)
+ self.watch_files.append(config_path)
+ self.pserve_file_config(config_path)
+
if hupper.is_active():
reloader = hupper.get_reloader()
- if app_spec.startswith('config:'):
- reloader.watch_files([app_spec[len('config:'):]])
-
- log_fn = app_spec
- if log_fn.startswith('config:'):
- log_fn = app_spec[len('config:'):]
- elif log_fn.startswith('egg:'):
- log_fn = None
- if log_fn:
- log_fn = os.path.join(base, log_fn)
- setup_logging(log_fn, global_conf=vars)
+ reloader.watch_files(self.watch_files)
- server = self.loadserver(server_spec, name=server_name,
- relative_to=base, global_conf=vars)
+ server = self.loadserver(
+ server_spec, name=server_name, relative_to=base, global_conf=vars)
app = self.loadapp(
app_spec, name=app_name, relative_to=base, global_conf=vars)
@@ -196,13 +219,6 @@ class PServeCommand(object):
serve()
- def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover
- return loadapp(app_spec, name=name, relative_to=relative_to, **kw)
-
- def loadserver(self, server_spec, name, relative_to, **kw):# pragma:no cover
- return loadserver(
- server_spec, name=name, relative_to=relative_to, **kw)
-
# For paste.deploy server instantiation (egg:pyramid#wsgiref)
def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover
from wsgiref.simple_server import make_server
diff --git a/pyramid/tests/test_scripts/test_pserve.py b/pyramid/tests/test_scripts/test_pserve.py
index e84de92d4..cf9426943 100644
--- a/pyramid/tests/test_scripts/test_pserve.py
+++ b/pyramid/tests/test_scripts/test_pserve.py
@@ -1,9 +1,12 @@
+import os
import unittest
+from pyramid.tests.test_scripts import dummy
class TestPServeCommand(unittest.TestCase):
def setUp(self):
from pyramid.compat import NativeIO
self.out_ = NativeIO()
+ self.config_factory = dummy.DummyConfigParserFactory()
def out(self, msg):
self.out_.write(msg)
@@ -11,7 +14,6 @@ class TestPServeCommand(unittest.TestCase):
def _get_server(*args, **kwargs):
def server(app):
return ''
-
return server
def _getTargetClass(self):
@@ -23,6 +25,7 @@ class TestPServeCommand(unittest.TestCase):
effargs.extend(args)
cmd = self._getTargetClass()(effargs)
cmd.out = self.out
+ cmd.ConfigParser = self.config_factory
return cmd
def test_run_no_args(self):
@@ -38,20 +41,15 @@ class TestPServeCommand(unittest.TestCase):
self.assertEqual(result, {'a': '1', 'b': '2'})
def test_parse_vars_good(self):
- from pyramid.tests.test_scripts.dummy import DummyApp
-
inst = self._makeOne('development.ini', 'a=1', 'b=2')
inst.loadserver = self._get_server
-
- app = DummyApp()
-
+ app = dummy.DummyApp()
def get_app(*args, **kwargs):
app.global_conf = kwargs.get('global_conf', None)
-
inst.loadapp = get_app
- inst.run()
+ inst.run()
self.assertEqual(app.global_conf, {'a': '1', 'b': '2'})
def test_parse_vars_bad(self):
@@ -59,6 +57,16 @@ class TestPServeCommand(unittest.TestCase):
inst.loadserver = self._get_server
self.assertRaises(ValueError, inst.run)
+ def test_config_file_finds_watch_files(self):
+ inst = self._makeOne('development.ini')
+ self.config_factory.items = [('watch_files', 'foo\nbar\n/baz')]
+ inst.pserve_file_config('/base/path.ini')
+ self.assertEqual(inst.watch_files, [
+ os.path.normpath('/base/foo'),
+ os.path.normpath('/base/bar'),
+ os.path.normpath('/baz'),
+ ])
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.pserve import main