diff options
| author | Michael Merickel <michael@merickel.org> | 2016-11-21 00:14:26 -0600 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2016-11-21 00:57:57 -0600 |
| commit | 3f130937bcce073e9933e28c332e59c559025e07 (patch) | |
| tree | 6842711ea8a9b50c4a68500745795750231d373a | |
| parent | 52bfe0a8f2f6d305f0e3d8cead290427fd71e926 (diff) | |
| download | pyramid-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.rst | 20 | ||||
| -rw-r--r-- | pyramid/scripts/pserve.py | 58 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/test_pserve.py | 24 |
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 |
