Mercurial > repos > guerler > springsuite
diff planemo/lib/python3.7/site-packages/galaxy/util/pastescript/serve.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:32:28 -0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/planemo/lib/python3.7/site-packages/galaxy/util/pastescript/serve.py Fri Jul 31 00:32:28 2020 -0400 @@ -0,0 +1,1062 @@ +# Most of this code is: + +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +# The server command includes the additional header: + +# For discussion of daemonizing: +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 +# Code taken also from QP: +# http://www.mems-exchange.org/software/qp/ +# From lib/site.py + +# Galaxy originally used PasteScript and PasteDeploy for application +# loading, to maintain compatibility we've internalized some of that +# code here, stripping out uneeded functionality. + +# All top level imports from each package moved here and organized +from __future__ import absolute_import +from __future__ import print_function + +import atexit +import errno +import grp +import logging +import optparse +import os +import pwd +import re +import resource +import signal +import socket +import subprocess +import sys +import textwrap +import threading +import time +from logging.config import fileConfig + +from six.moves import configparser + +from .loadwsgi import loadapp, loadserver + + +difflib = None + +# ---- from paste.script.bool_optparse -------------------------------- + +""" +A subclass of ``optparse.OptionParser`` that allows boolean long +options (like ``--verbose``) to also take arguments (like +``--verbose=true``). Arguments *must* use ``=``. +""" + +try: + _ = optparse._ +except AttributeError: + from gettext import gettext as _ + + +class BoolOptionParser(optparse.OptionParser): + + def _process_long_opt(self, rargs, values): + arg = rargs.pop(0) + + # Value explicitly attached to arg? Pretend it's the next + # argument. + if "=" in arg: + (opt, next_arg) = arg.split("=", 1) + rargs.insert(0, next_arg) + had_explicit_value = True + else: + opt = arg + had_explicit_value = False + + opt = self._match_long_opt(opt) + option = self._long_opt[opt] + if option.takes_value(): + nargs = option.nargs + if len(rargs) < nargs: + if nargs == 1: + self.error(_("%s option requires an argument") % opt) + else: + self.error(_("%s option requires %d arguments") + % (opt, nargs)) + elif nargs == 1: + value = rargs.pop(0) + else: + value = tuple(rargs[0:nargs]) + del rargs[0:nargs] + + elif had_explicit_value: + value = rargs[0].lower().strip() + del rargs[0:1] + if value in ('true', 'yes', 'on', '1', 'y', 't'): + value = None + elif value in ('false', 'no', 'off', '0', 'n', 'f'): + # Don't process + return + else: + self.error(_('%s option takes a boolean value only (true/false)') % opt) + + else: + value = None + + option.process(opt, value, values, self) + +# ---- from paste.script.command -------------------------------------- + +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + + +class BadCommand(Exception): + + def __init__(self, message, exit_code=2): + self.message = message + self.exit_code = exit_code + Exception.__init__(self, message) + + def _get_message(self): + """Getter for 'message'; needed only to override deprecation + in BaseException.""" + return self.__message + + def _set_message(self, value): + """Setter for 'message'; needed only to override deprecation + in BaseException.""" + self.__message = value + + # BaseException.message has been deprecated since Python 2.6. + # To prevent DeprecationWarning from popping up over this + # pre-existing attribute, use a new property that takes lookup + # precedence. + message = property(_get_message, _set_message) + + +class NoDefault(object): + pass + + +# run and invoke methods moved below ServeCommand +class Command(object): + + def __init__(self, name): + self.command_name = name + + max_args = None + max_args_error = 'You must provide no more than %(max_args)s arguments' + min_args = None + min_args_error = 'You must provide at least %(min_args)s arguments' + required_args = None + # If this command takes a configuration file, set this to 1 or -1 + # Then if invoked through #! the config file will be put into the positional + # arguments -- at the beginning with 1, at the end with -1 + takes_config_file = None + + # Grouped in help messages by this: + group_name = '' + + required_args = () + description = None + usage = '' + hidden = False + # This is the default verbosity level; --quiet subtracts, + # --verbose adds: + default_verbosity = 0 + # This is the default interactive state: + default_interactive = 0 + return_code = 0 + + BadCommand = BadCommand + + # Must define: + # parser + # summary + # command() + + def run(self, args): + self.parse_args(args) + + # Setup defaults: + for name, default in [('verbose', 0), + ('quiet', 0), + ('interactive', False), + ('overwrite', False)]: + if not hasattr(self.options, name): + setattr(self.options, name, default) + if getattr(self.options, 'simulate', False): + self.options.verbose = max(self.options.verbose, 1) + self.interactive = self.default_interactive + if getattr(self.options, 'interactive', False): + self.interactive += self.options.interactive + if getattr(self.options, 'no_interactive', False): + self.interactive = False + self.verbose = self.default_verbosity + self.verbose += self.options.verbose + self.verbose -= self.options.quiet + self.simulate = getattr(self.options, 'simulate', False) + + # For #! situations: + if os.environ.get('PASTE_CONFIG_FILE') and self.takes_config_file is not None: + take = self.takes_config_file + filename = os.environ.get('PASTE_CONFIG_FILE') + if take == 1: + self.args.insert(0, filename) + elif take == -1: + self.args.append(filename) + else: + assert 0, ( + "Value takes_config_file must be None, 1, or -1 (not %r)" + % take) + + if os.environ.get('PASTE_DEFAULT_QUIET'): + self.verbose = 0 + + # Validate: + if self.min_args is not None and len(self.args) < self.min_args: + raise BadCommand( + self.min_args_error % {'min_args': self.min_args, + 'actual_args': len(self.args)}) + if self.max_args is not None and len(self.args) > self.max_args: + raise BadCommand( + self.max_args_error % {'max_args': self.max_args, + 'actual_args': len(self.args)}) + for var_name, option_name in self.required_args: + if not getattr(self.options, var_name, None): + raise BadCommand( + 'You must provide the option %s' % option_name) + result = self.command() + if result is None: + return self.return_code + else: + return result + + def parse_args(self, args): + if self.usage: + usage = ' ' + self.usage + else: + usage = '' + self.parser.usage = "%%prog [options]%s\n%s" % ( + usage, self.summary) + self.parser.prog = self._prog_name() + if self.description: + desc = self.description + desc = textwrap.dedent(desc) + self.parser.description = desc + self.options, self.args = self.parser.parse_args(args) + + def _prog_name(self): + return '%s %s' % (os.path.basename(sys.argv[0]), self.command_name) + + ######################################## + # Utility methods + ######################################## + + def pad(self, s, length, dir='left'): + if len(s) >= length: + return s + if dir == 'left': + return s + ' ' * (length - len(s)) + else: + return ' ' * (length - len(s)) + s + + def standard_parser(cls, verbose=True, + interactive=False, + no_interactive=False, + simulate=False, + quiet=False, + overwrite=False): + """ + Create a standard ``OptionParser`` instance. + + Typically used like:: + + class MyCommand(Command): + parser = Command.standard_parser() + + Subclasses may redefine ``standard_parser``, so use the + nearest superclass's class method. + """ + parser = BoolOptionParser() + if verbose: + parser.add_option('-v', '--verbose', + action='count', + dest='verbose', + default=0) + if quiet: + parser.add_option('-q', '--quiet', + action='count', + dest='quiet', + default=0) + if no_interactive: + parser.add_option('--no-interactive', + action="count", + dest="no_interactive", + default=0) + if interactive: + parser.add_option('-i', '--interactive', + action='count', + dest='interactive', + default=0) + if simulate: + parser.add_option('-n', '--simulate', + action='store_true', + dest='simulate', + default=False) + if overwrite: + parser.add_option('-f', '--overwrite', + dest="overwrite", + action="store_true", + help="Overwrite files (warnings will be emitted for non-matching files otherwise)") + return parser + + standard_parser = classmethod(standard_parser) + + 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 parse_vars(self, args): + """ + Given variables like ``['a=b', 'c=d']`` turns it into ``{'a': + 'b', 'c': 'd'}`` + """ + result = {} + for arg in args: + if '=' not in arg: + raise BadCommand( + 'Variable assignment %r invalid (no "=")' + % arg) + name, value = arg.split('=', 1) + result[name] = value + return result + + def logging_file_config(self, config_file): + """ + Setup logging via the logging module's fileConfig function with the + specified ``config_file``, if applicable. + + ConfigParser defaults are specified for the special ``__file__`` + and ``here`` variables, similar to PasteDeploy config loading. + """ + parser = configparser.ConfigParser() + parser.read([config_file]) + if parser.has_section('loggers'): + config_file = os.path.abspath(config_file) + fileConfig(config_file, dict(__file__=config_file, + here=os.path.dirname(config_file))) + + +class NotFoundCommand(Command): + + def run(self, args): + print('Command %r not known (you may need to run setup.py egg_info)' + % self.command_name) + commands = list() + commands.sort() + if not commands: + print('No commands registered.') + print('Have you installed Paste Script?') + print('(try running python setup.py develop)') + return 2 + print('Known commands:') + longest = max([len(n) for n, c in commands]) + for name, command in commands: + print(' %s %s' % (self.pad(name, length=longest), + command.load().summary)) + return 2 + + +# ---- From paste.script.serve ---------------------------------------- + +MAXFD = 1024 + +jython = sys.platform.startswith('java') + + +class DaemonizeException(Exception): + pass + + +class ServeCommand(Command): + + min_args = 0 + usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]' + takes_config_file = 1 + summary = "Serve the described application" + description = """\ + This command serves a web application that uses a paste.deploy + configuration file for the server and application. + + If start/stop/restart is given, then --daemon is implied, and it will + start (normal operation), stop (--stop-daemon), or do both. + + You can also include variable assignments like 'http_port=8080' + and then use %(http_port)s in your config files. + """ + + # used by subclasses that configure apps and servers differently + requires_config_file = True + + parser = Command.standard_parser(quiet=True) + parser.add_option('-n', '--app-name', + dest='app_name', + metavar='NAME', + help="Load the named application (default main)") + parser.add_option('-s', '--server', + dest='server', + metavar='SERVER_TYPE', + help="Use the named server.") + parser.add_option('--server-name', + dest='server_name', + metavar='SECTION_NAME', + help="Use the named server as defined in the configuration file (default: main)") + if hasattr(os, 'fork'): + parser.add_option('--daemon', + dest="daemon", + action="store_true", + help="Run in daemon (background) mode") + parser.add_option('--pid-file', + dest='pid_file', + metavar='FILENAME', + help="Save PID to file (default to paster.pid if running in daemon mode)") + parser.add_option('--log-file', + dest='log_file', + metavar='LOG_FILE', + help="Save output to the given log file (redirects stdout)") + parser.add_option('--reload', + dest='reload', + action='store_true', + help="Use auto-restart file monitor") + parser.add_option('--reload-interval', + dest='reload_interval', + default=1, + help="Seconds between checking files (low number can cause significant CPU usage)") + parser.add_option('--monitor-restart', + dest='monitor_restart', + action='store_true', + help="Auto-restart server if it dies") + parser.add_option('--status', + action='store_true', + dest='show_status', + help="Show the status of the (presumably daemonized) server") + + if hasattr(os, 'setuid'): + # I don't think these are available on Windows + parser.add_option('--user', + dest='set_user', + metavar="USERNAME", + help="Set the user (usually only possible when run as root)") + parser.add_option('--group', + dest='set_group', + metavar="GROUP", + help="Set the group (usually only possible when run as root)") + + parser.add_option('--stop-daemon', + dest='stop_daemon', + action='store_true', + help='Stop a daemonized server (given a PID file, or default paster.pid file)') + + if jython: + parser.add_option('--disable-jython-reloader', + action='store_true', + dest='disable_jython_reloader', + help="Disable the Jython reloader") + + _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) + + default_verbosity = 1 + + _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN' + _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN' + + possible_subcommands = ('start', 'stop', 'restart', 'status') + + def command(self): + if self.options.stop_daemon: + return self.stop_daemon() + + if not hasattr(self.options, 'set_user'): + # Windows case: + self.options.set_user = self.options.set_group = None + # @@: Is this the right stage to set the user at? + self.change_user_group( + self.options.set_user, self.options.set_group) + + if self.requires_config_file: + if not self.args: + raise BadCommand('You must give a config file') + app_spec = self.args[0] + if len(self.args) > 1 and self.args[1] in self.possible_subcommands: + cmd = self.args[1] + restvars = self.args[2:] + else: + cmd = None + restvars = self.args[1:] + else: + app_spec = "" + if self.args and self.args[0] in self.possible_subcommands: + cmd = self.args[0] + restvars = self.args[1:] + else: + cmd = None + restvars = self.args[:] + + if (getattr(self.options, 'daemon', False) and + getattr(self.options, 'reload', False)): + raise BadCommand('The --daemon and --reload options may not be used together') + + jython_monitor = False + if self.options.reload: + if jython and not self.options.disable_jython_reloader: + # JythonMonitor raises the special SystemRestart + # exception that'll cause the Jython interpreter to + # reload in the existing Java process (avoiding + # subprocess startup time) + try: + from paste.reloader import JythonMonitor + except ImportError: + pass + else: + jython_monitor = JythonMonitor(poll_interval=int( + self.options.reload_interval)) + if self.requires_config_file: + jython_monitor.watch_file(self.args[0]) + + if not jython_monitor: + if os.environ.get(self._reloader_environ_key): + from paste import reloader + if self.verbose > 1: + print('Running reloading file monitor') + reloader.install(int(self.options.reload_interval)) + if self.requires_config_file: + reloader.watch_file(self.args[0]) + else: + return self.restart_with_reloader() + + if cmd not in (None, 'start', 'stop', 'restart', 'status'): + raise BadCommand( + 'Error: must give start|stop|restart (not %s)' % cmd) + + if cmd == 'status' or self.options.show_status: + return self.show_status() + + if cmd == 'restart' or cmd == 'stop': + result = self.stop_daemon() + if result: + print("Could not stop daemon") + # It's ok to continue trying to restart if stop_daemon returns + # a 1, otherwise shortcut and return. + if cmd == 'restart' and result != 1: + return result + if cmd == 'stop': + return result + self.options.daemon = True + + if cmd == 'start': + self.options.daemon = True + + app_name = self.options.app_name + vars = self.parse_vars(restvars) + if not self._scheme_re.search(app_spec): + app_spec = 'config:' + app_spec + server_name = self.options.server_name + if self.options.server: + server_spec = 'egg:PasteScript' + assert server_name is None + server_name = self.options.server + else: + server_spec = app_spec + base = os.getcwd() + + if getattr(self.options, 'daemon', False): + if not self.options.pid_file: + self.options.pid_file = 'paster.pid' + if not self.options.log_file: + self.options.log_file = 'paster.log' + + # Ensure the log file is writeable + if self.options.log_file: + try: + writeable_log_file = open(self.options.log_file, 'a') + except IOError as ioe: + msg = 'Error: Unable to write to log file: %s' % ioe + raise BadCommand(msg) + writeable_log_file.close() + + # Ensure the pid file is writeable + if self.options.pid_file: + try: + writeable_pid_file = open(self.options.pid_file, 'a') + except IOError as ioe: + msg = 'Error: Unable to write to pid file: %s' % ioe + raise BadCommand(msg) + writeable_pid_file.close() + + if getattr(self.options, 'daemon', False): + try: + self.daemonize() + except DaemonizeException as ex: + if self.verbose > 0: + print(str(ex)) + return + + if (self.options.monitor_restart and not + os.environ.get(self._monitor_environ_key)): + return self.restart_with_monitor() + + if self.options.pid_file: + self.record_pid(self.options.pid_file) + + if self.options.log_file: + stdout_log = LazyWriter(self.options.log_file, 'a') + sys.stdout = stdout_log + sys.stderr = stdout_log + logging.basicConfig(stream=stdout_log) + + 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) + self.logging_file_config(log_fn) + + server = loadserver(server_spec, name=server_name, relative_to=base, global_conf=vars) + + app = loadapp(app_spec, name=app_name, relative_to=base, global_conf=vars) + + if self.verbose > 0: + if hasattr(os, 'getpid'): + msg = 'Starting server in PID %i.' % os.getpid() + else: + msg = 'Starting server.' + print(msg) + + def serve(): + try: + server(app) + except (SystemExit, KeyboardInterrupt) as e: + if self.verbose > 1: + raise + if str(e): + msg = ' ' + str(e) + else: + msg = '' + print('Exiting%s (-v to see traceback)' % msg) + except AttributeError as e: + # Capturing bad error response from paste + if str(e) == "'WSGIThreadPoolServer' object has no attribute 'thread_pool'": + raise socket.error(98, 'Address already in use') + else: + raise AttributeError(e) + + if jython_monitor: + # JythonMonitor has to be ran from the main thread + threading.Thread(target=serve).start() + print('Starting Jython file monitor') + jython_monitor.periodic_reload() + else: + serve() + + def daemonize(self): + pid = live_pidfile(self.options.pid_file) + if pid: + raise DaemonizeException( + "Daemon is already running (PID: %s from PID file %s)" + % (pid, self.options.pid_file)) + + if self.verbose > 0: + print('Entering daemon mode') + pid = os.fork() + if pid: + # The forked process also has a handle on resources, so we + # *don't* want proper termination of the process, we just + # want to exit quick (which os._exit() does) + os._exit(0) + # Make this the session leader + os.setsid() + # Fork again for good measure! + pid = os.fork() + if pid: + os._exit(0) + + # @@: Should we set the umask and cwd now? + + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if maxfd == resource.RLIM_INFINITY: + maxfd = MAXFD + # Iterate through and close all file descriptors. + for fd in range(0, maxfd): + try: + os.close(fd) + except OSError: # ERROR, fd wasn't open to begin with (ignored) + pass + + if hasattr(os, "devnull"): + REDIRECT_TO = os.devnull + else: + REDIRECT_TO = "/dev/null" + os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) + # Duplicate standard input to standard output and standard error. + os.dup2(0, 1) # standard output (1) + os.dup2(0, 2) # standard error (2) + + def record_pid(self, pid_file): + pid = os.getpid() + if self.verbose > 1: + print('Writing PID %s to %s' % (pid, pid_file)) + f = open(pid_file, 'w') + f.write(str(pid)) + f.close() + atexit.register(_remove_pid_file, pid, pid_file, self.verbose) + + def stop_daemon(self): + pid_file = self.options.pid_file or 'paster.pid' + if not os.path.exists(pid_file): + print('No PID file exists in %s' % pid_file) + return 1 + pid = read_pidfile(pid_file) + if not pid: + print("Not a valid PID file in %s" % pid_file) + return 1 + pid = live_pidfile(pid_file) + if not pid: + print("PID in %s is not valid (deleting)" % pid_file) + try: + os.unlink(pid_file) + except (OSError, IOError) as e: + print("Could not delete: %s" % e) + return 2 + return 1 + for _i in range(10): + if not live_pidfile(pid_file): + break + os.kill(pid, signal.SIGTERM) + time.sleep(1) + else: + print("failed to kill web process %s" % pid) + return 3 + if os.path.exists(pid_file): + os.unlink(pid_file) + return 0 + + def show_status(self): + pid_file = self.options.pid_file or 'paster.pid' + if not os.path.exists(pid_file): + print('No PID file %s' % pid_file) + return 1 + pid = read_pidfile(pid_file) + if not pid: + print('No PID in file %s' % pid_file) + return 1 + pid = live_pidfile(pid_file) + if not pid: + print('PID %s in %s is not running' % (pid, pid_file)) + return 1 + print('Server running in PID %s' % pid) + return 0 + + def restart_with_reloader(self): + self.restart_with_monitor(reloader=True) + + def restart_with_monitor(self, reloader=False): + if self.verbose > 0: + if reloader: + print('Starting subprocess with file monitor') + else: + print('Starting subprocess with monitor parent') + while 1: + args = [self.quote_first_command_arg(sys.executable)] + sys.argv + new_environ = os.environ.copy() + if reloader: + new_environ[self._reloader_environ_key] = 'true' + else: + new_environ[self._monitor_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') + if self.verbose > 1: + raise + return 1 + finally: + if proc is not None and hasattr(os, 'kill'): + try: + os.kill(proc.pid, signal.SIGTERM) + except (OSError, IOError): + pass + + if reloader: + # Reloader always exits with code 3; but if we are + # a monitor, any exit code will restart + if exit_code != 3: + return exit_code + if self.verbose > 0: + print('-' * 20, 'Restarting', '-' * 20) + + def change_user_group(self, user, group): + if not user and not group: + return + uid = gid = None + if group: + try: + gid = int(group) + group = grp.getgrgid(gid).gr_name + except ValueError: + try: + entry = grp.getgrnam(group) + except KeyError: + raise BadCommand( + "Bad group: %r; no such group exists" % group) + gid = entry.gr_gid + try: + uid = int(user) + user = pwd.getpwuid(uid).pw_name + except ValueError: + try: + entry = pwd.getpwnam(user) + except KeyError: + raise BadCommand( + "Bad username: %r; no such user exists" % user) + if not gid: + gid = entry.pw_gid + uid = entry.pw_uid + if self.verbose > 0: + print('Changing user to %s:%s (%s:%s)' % ( + user, group or '(unknown)', uid, gid)) + if hasattr(os, 'initgroups'): + os.initgroups(user, gid) + else: + os.setgroups([e.gr_gid for e in grp.getgrall() + if user in e.gr_mem] + [gid]) + if gid: + os.setgid(gid) + if uid: + os.setuid(uid) + + +class LazyWriter(object): + + """ + File-like object that opens a file lazily when it is first written + to. + """ + + def __init__(self, filename, mode='w'): + self.filename = filename + self.fileobj = None + self.lock = threading.Lock() + self.mode = mode + + def open(self): + if self.fileobj is None: + self.lock.acquire() + try: + if self.fileobj is None: + self.fileobj = open(self.filename, self.mode) + finally: + self.lock.release() + return self.fileobj + + def write(self, text): + fileobj = self.open() + fileobj.write(text) + fileobj.flush() + + def writelines(self, text): + fileobj = self.open() + fileobj.writelines(text) + fileobj.flush() + + def flush(self): + self.open().flush() + + +def live_pidfile(pidfile): + """(pidfile:str) -> int | None + Returns an int found in the named file, if there is one, + and if there is a running process with that process id. + Return None if no such process exists. + """ + pid = read_pidfile(pidfile) + if pid: + try: + os.kill(int(pid), 0) + return pid + except OSError as e: + if e.errno == errno.EPERM: + return pid + return None + + +def read_pidfile(filename): + if os.path.exists(filename): + try: + f = open(filename) + content = f.read() + f.close() + return int(content.strip()) + except (ValueError, IOError): + return None + else: + return None + + +def _remove_pid_file(written_pid, filename, verbosity): + current_pid = os.getpid() + if written_pid != current_pid: + # A forked process must be exiting, not the process that + # wrote the PID file + return + if not os.path.exists(filename): + return + f = open(filename) + content = f.read().strip() + f.close() + try: + pid_in_file = int(content) + except ValueError: + pass + else: + if pid_in_file != current_pid: + print("PID file %s contains %s, not expected PID %s" % ( + filename, pid_in_file, current_pid)) + return + if verbosity > 0: + print("Removing PID file %s" % filename) + try: + os.unlink(filename) + return + except OSError as e: + # Record, but don't give traceback + print("Cannot remove PID file: %s" % e) + # well, at least lets not leave the invalid PID around... + try: + f = open(filename, 'w') + f.write('') + f.close() + except OSError as e: + print('Stale PID left in file: %s (%e)' % (filename, e)) + else: + print('Stale PID removed') + + +def ensure_port_cleanup(bound_addresses, maxtries=30, sleeptime=2): + """ + This makes sure any open ports are closed. + + Does this by connecting to them until they give connection + refused. Servers should call like:: + + import paste.script + ensure_port_cleanup([80, 443]) + """ + atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries, + sleeptime=sleeptime) + + +def _cleanup_ports(bound_addresses, maxtries=30, sleeptime=2): + # Wait for the server to bind to the port. + for bound_address in bound_addresses: + for _i in range(maxtries): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect(bound_address) + except socket.error as e: + if e.errno != errno.ECONNREFUSED: + raise + break + else: + time.sleep(sleeptime) + else: + raise SystemExit('Timeout waiting for port.') + sock.close() + + +def _turn_sigterm_into_systemexit(): + """ + Attempts to turn a SIGTERM exception into a SystemExit exception. + """ + + def handle_term(signo, frame): + raise SystemExit + signal.signal(signal.SIGTERM, handle_term) + + +# ---- from paste.script.command -------------------------------------- +python_version = sys.version.splitlines()[0].strip() + +parser = optparse.OptionParser(add_help_option=False, + # version='%s from %s (python %s)' + # % (dist, dist.location, python_version), + usage='%prog [paster_options] COMMAND [command_options]') + +parser.add_option( + '-h', '--help', + action='store_true', + dest='do_help', + help="Show this help message") +parser.disable_interspersed_args() + +# @@: Add an option to run this in another Python interpreter + +commands = { + 'serve': ServeCommand +} + + +def run(args=None): + if (not args and len(sys.argv) >= 2 and os.environ.get('_') and + sys.argv[0] != os.environ['_'] and os.environ['_'] == sys.argv[1]): + # probably it's an exe execution + args = ['exe', os.environ['_']] + sys.argv[2:] + if args is None: + args = sys.argv[1:] + options, args = parser.parse_args(args) + options.base_parser = parser + if options.do_help: + args = ['help'] + args + if not args: + print('Usage: %s COMMAND' % sys.argv[0]) + args = ['help'] + command_name = args[0] + if command_name not in commands: + command = NotFoundCommand + else: + command = commands[command_name] + invoke(command, command_name, options, args[1:]) + + +def invoke(command, command_name, options, args): + try: + runner = command(command_name) + exit_code = runner.run(args) + except BadCommand as e: + print(e) + exit_code = e.exit_code + sys.exit(exit_code)