Adding (optional) file monitoring functionality to automatically reload pecan apps when .py files change (similar to paster --reload server config.ini). Now in your project, you can do:

pecan.conf.app = {'reload': True, ...}

Additionally, the pecan quick start template has been configured to auto-reload by default.
This commit is contained in:
Ryan Petrello
2010-12-15 12:48:44 -05:00
parent e7e2066b50
commit 8ec6f6f5ff
6 changed files with 86 additions and 1 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
pecan.egg-info
*.pyc

Binary file not shown.

77
pecan/monitor.py Normal file
View File

@@ -0,0 +1,77 @@
import sys, os
import subprocess
class MonitorableProcess(object):
_reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
def start_monitoring(self):
if os.environ.get(self._reloader_environ_key):
from paste import reloader
reloader.install()
else:
return self.restart_with_reloader()
def restart_with_reloader(self):
print 'Starting subprocess with file monitor'
while 1:
args = [self.quote_first_command_arg(sys.executable)] + sys.argv
new_environ = os.environ.copy()
new_environ[self._reloader_environ_key] = 'true'
proc = None
try:
try:
_turn_sigterm_into_systemexit()
proc = subprocess.Popen(args, env=new_environ)
exit_code = proc.wait()
proc = None
except KeyboardInterrupt:
print '^C caught in monitor process'
raise
finally:
if (proc is not None
and hasattr(os, 'kill')):
import signal
try:
os.kill(proc.pid, signal.SIGTERM)
except (OSError, IOError):
pass
# Reloader always exits with code 3; but if we are
# a monitor, any exit code will restart
if exit_code != 3:
return exit_code
print '-'*20, 'Restarting', '-'*20
def quote_first_command_arg(self, arg):
"""
There's a bug in Windows when running an executable that's
located inside a path with a space in it. This method handles
that case, or on non-Windows systems or an executable with no
spaces, it just leaves well enough alone.
"""
if (sys.platform != 'win32'
or ' ' not in arg):
# Problem does not apply:
return arg
try:
import win32api
except ImportError:
raise ValueError(
"The executable %r contains a space, and in order to "
"handle this issue you must have the win32api module "
"installed" % arg)
arg = win32api.GetShortPathName(arg)
return arg
def _turn_sigterm_into_systemexit():
"""
Attempts to turn a SIGTERM exception into a SystemExit exception.
"""
try:
import signal
except ImportError:
return
def handle_term(signo, frame):
raise SystemExit
signal.signal(signal.SIGTERM, handle_term)

View File

@@ -1,3 +1,5 @@
from configuration import _runtime_conf
from monitor import MonitorableProcess
from templating import renderers
from routing import lookup_controller
@@ -50,7 +52,7 @@ def error_for(field):
return request.validation_error.error_dict.get(field, '')
class Pecan(object):
class Pecan(MonitorableProcess):
def __init__(self, root,
renderers = renderers,
default_renderer = 'kajiki',
@@ -62,6 +64,10 @@ class Pecan(object):
self.default_renderer = default_renderer
self.hooks = hooks
self.template_path = template_path
MonitorableProcess.__init__(self)
if getattr(_runtime_conf.app, 'reload', False) is True:
self.start_monitoring()
def get_content_type(self, format):
return {

Binary file not shown.

View File

@@ -12,6 +12,7 @@ app = {
'root' : RootController(),
'static_root' : 'public',
'template_path' : '${egg}/templates',
'reload': True,
'debug' : True
}