""" Serve command for Pecan. """ from __future__ import print_function import os import sys import time import subprocess from pecan.commands import BaseCommand class ServeCommand(BaseCommand): """ Serves a Pecan web application. This command serves a Pecan web application using the provided configuration file for the server and application. """ arguments = BaseCommand.arguments + ({ 'name': '--reload', 'help': 'Watch for changes and automatically reload.', 'default': False, 'action': 'store_true' },) def run(self, args): super(ServeCommand, self).run(args) app = self.load_app() self.serve(app, app.config) def create_subprocess(self): self.server_process = subprocess.Popen( [arg for arg in sys.argv if arg != '--reload'], stdout=sys.stdout, stderr=sys.stderr ) def watch_and_spawn(self, conf): from watchdog.observers import Observer from watchdog.events import ( FileSystemEventHandler, FileSystemMovedEvent, FileModifiedEvent, DirModifiedEvent ) print('Monitoring for changes...') self.create_subprocess() parent = self class AggressiveEventHandler(FileSystemEventHandler): def should_reload(self, event): for t in ( FileSystemMovedEvent, FileModifiedEvent, DirModifiedEvent ): if isinstance(event, t): return True return False def on_modified(self, event): if self.should_reload(event): parent.server_process.kill() parent.create_subprocess() # Determine a list of file paths to monitor paths = self.paths_to_monitor(conf) event_handler = AggressiveEventHandler() for path, recurse in paths: observer = Observer() observer.schedule( event_handler, path=path, recursive=recurse ) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: pass def paths_to_monitor(self, conf): paths = [] for package_name in getattr(conf.app, 'modules', []): module = __import__(package_name, fromlist=['app']) if hasattr(module, 'app') and hasattr(module.app, 'setup_app'): paths.append(( os.path.dirname(module.__file__), True )) break paths.append((os.path.dirname(conf.__file__), False)) return paths def _serve(self, app, conf): from wsgiref.simple_server import make_server host, port = conf.server.host, int(conf.server.port) srv = make_server(host, port, app) print('Starting server in PID %s' % os.getpid()) if host == '0.0.0.0': print( 'serving on 0.0.0.0:%s, view at http://127.0.0.1:%s' % (port, port) ) else: print("serving on http://%s:%s" % (host, port)) try: srv.serve_forever() except KeyboardInterrupt: # allow CTRL+C to shutdown pass def serve(self, app, conf): """ A very simple approach for a WSGI server. """ if self.args.reload: try: self.watch_and_spawn(conf) except ImportError: print('The `--reload` option requires `watchdog` to be ' 'installed.') 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()