refactor of commands to more robust server
This commit is contained in:
parent
a6ede2c096
commit
a7a4ce77f1
|
@ -1,315 +0,0 @@
|
|||
"""
|
||||
PasteScript commands for Pecan.
|
||||
"""
|
||||
from configuration import _runtime_conf, set_config
|
||||
from paste.script import command as paste_command
|
||||
from paste.script.create_distro import CreateDistroCommand
|
||||
from webtest import TestApp
|
||||
|
||||
from templates import DEFAULT_TEMPLATE
|
||||
|
||||
import copy
|
||||
import optparse
|
||||
import os
|
||||
import pkg_resources
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
|
||||
class CommandRunner(object):
|
||||
"""
|
||||
Dispatches command execution requests.
|
||||
|
||||
This is a custom PasteScript command runner that is specific to Pecan
|
||||
commands. For a command to show up, its name must begin with "pecan-".
|
||||
It is also recommended that its group name be set to "Pecan" so that it
|
||||
shows up under that group when using ``paster`` directly.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# set up the parser
|
||||
self.parser = optparse.OptionParser(add_help_option=False,
|
||||
version='Pecan %s' % self.get_version(),
|
||||
usage='%prog [options] COMMAND [command_options]')
|
||||
self.parser.disable_interspersed_args()
|
||||
self.parser.add_option('-h', '--help',
|
||||
action='store_true',
|
||||
dest='show_help',
|
||||
help='show detailed help message')
|
||||
|
||||
# suppress BaseException.message warnings for BadCommand
|
||||
if sys.version_info < (2, 7):
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
'BaseException\.message has been deprecated as of Python 2\.6',
|
||||
DeprecationWarning,
|
||||
paste_command.__name__.replace('.', '\\.'))
|
||||
|
||||
# register Pecan as a system plugin when using the custom runner
|
||||
paste_command.system_plugins.append('Pecan')
|
||||
|
||||
def get_command_template(self, command_names):
|
||||
if not command_names:
|
||||
max_length = 10
|
||||
else:
|
||||
max_length = max([len(name) for name in command_names])
|
||||
return ' %%-%ds %%s\n' % max_length
|
||||
|
||||
def get_commands(self):
|
||||
commands = {}
|
||||
for name, command in paste_command.get_commands().iteritems():
|
||||
if name.startswith('pecan-'):
|
||||
commands[name[6:]] = command.load()
|
||||
return commands
|
||||
|
||||
def get_version(self):
|
||||
try:
|
||||
dist = pkg_resources.get_distribution('Pecan')
|
||||
if os.path.dirname(os.path.dirname(__file__)) == dist.location:
|
||||
return dist.version
|
||||
else:
|
||||
return '(development)'
|
||||
except:
|
||||
return '(development)'
|
||||
|
||||
def print_usage(self, file=sys.stdout):
|
||||
self.parser.print_help(file=file)
|
||||
file.write('\n')
|
||||
command_groups = {}
|
||||
commands = self.get_commands()
|
||||
if not commands:
|
||||
file.write('No commands registered.\n')
|
||||
return
|
||||
command_template = self.get_command_template(commands.keys())
|
||||
for name, command in commands.iteritems():
|
||||
group_name = command.group_name
|
||||
if group_name.lower() == 'pecan':
|
||||
group_name = ''
|
||||
command_groups.setdefault(group_name, {})[name] = command
|
||||
command_groups = sorted(command_groups.items())
|
||||
for i, (group, commands) in enumerate(command_groups):
|
||||
file.write('%s:\n' % (group or 'Commands'))
|
||||
for name, command in sorted(commands.items()):
|
||||
file.write(command_template % (name, command.summary))
|
||||
if i + 1 < len(command_groups):
|
||||
file.write('\n')
|
||||
|
||||
def print_known_commands(self, file=sys.stderr):
|
||||
commands = self.get_commands()
|
||||
command_names = sorted(commands.keys())
|
||||
if not command_names:
|
||||
file.write('No commands registered.\n')
|
||||
return
|
||||
file.write('Known commands:\n')
|
||||
command_template = self.get_command_template(command_names)
|
||||
for name in command_names:
|
||||
file.write(command_template % (name, commands[name].summary))
|
||||
|
||||
def run(self, args):
|
||||
options, args = self.parser.parse_args(args)
|
||||
if not args:
|
||||
self.print_usage()
|
||||
return 0
|
||||
command_name = args.pop(0)
|
||||
commands = self.get_commands()
|
||||
if command_name not in commands:
|
||||
sys.stderr.write('Command %s not known\n\n' % command_name)
|
||||
self.print_known_commands()
|
||||
return 1
|
||||
else:
|
||||
command = commands[command_name](command_name)
|
||||
if options.show_help:
|
||||
return command.run(['-h'])
|
||||
else:
|
||||
return command.run(args)
|
||||
|
||||
@classmethod
|
||||
def handle_command_line(cls):
|
||||
try:
|
||||
runner = CommandRunner()
|
||||
exit_code = runner.run(sys.argv[1:])
|
||||
except paste_command.BadCommand, ex:
|
||||
sys.stderr.write('%s\n' % ex)
|
||||
exit_code = ex.exit_code
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
class Command(paste_command.Command):
|
||||
"""
|
||||
Base class for Pecan commands.
|
||||
|
||||
This provides some standard functionality for interacting with Pecan
|
||||
applications and handles some of the basic PasteScript command cruft.
|
||||
|
||||
See ``paste.script.command.Command`` for more information.
|
||||
"""
|
||||
|
||||
# command information
|
||||
group_name = 'Pecan'
|
||||
summary = ''
|
||||
|
||||
# command parser
|
||||
parser = paste_command.Command.standard_parser()
|
||||
|
||||
def run(self, args):
|
||||
try:
|
||||
return paste_command.Command.run(self, args)
|
||||
except paste_command.BadCommand, ex:
|
||||
ex.args[0] = self.parser.error(ex.args[0])
|
||||
raise
|
||||
|
||||
def can_import(self, name):
|
||||
try:
|
||||
__import__(name)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def get_package_names(self, config):
|
||||
if not hasattr(config.app, 'modules'):
|
||||
return []
|
||||
return [module.__name__ for module in config.app.modules if hasattr(module, '__name__')]
|
||||
|
||||
def load_configuration(self, name):
|
||||
set_config(name)
|
||||
return _runtime_conf
|
||||
|
||||
def load_app(self, config):
|
||||
for package_name in self.get_package_names(config):
|
||||
module_name = '%s.app' % package_name
|
||||
if self.can_import(module_name):
|
||||
module = sys.modules[module_name]
|
||||
if hasattr(module, 'setup_app'):
|
||||
return module.setup_app(config)
|
||||
raise paste_command.BadCommand('No app.setup_app found in any of the configured app.modules')
|
||||
|
||||
def load_model(self, config):
|
||||
for package_name in self.get_package_names(config):
|
||||
module_name = '%s.model' % package_name
|
||||
if self.can_import(module_name):
|
||||
return sys.modules[module_name]
|
||||
return None
|
||||
|
||||
def command(self):
|
||||
pass
|
||||
|
||||
|
||||
class ServeCommand(Command):
|
||||
"""
|
||||
Serve the described application.
|
||||
"""
|
||||
|
||||
# command information
|
||||
usage = 'CONFIG_NAME'
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
|
||||
# command options/arguments
|
||||
min_args = 1
|
||||
max_args = 1
|
||||
|
||||
def command(self):
|
||||
|
||||
# load the application
|
||||
config = self.load_configuration(self.args[0])
|
||||
app = self.load_app(config)
|
||||
|
||||
from paste import httpserver
|
||||
try:
|
||||
httpserver.serve(app, config.server.host, config.server.port)
|
||||
except KeyboardInterrupt:
|
||||
print '^C'
|
||||
|
||||
|
||||
class ShellCommand(Command):
|
||||
"""
|
||||
Open an interactive shell with the Pecan app loaded.
|
||||
"""
|
||||
|
||||
# command information
|
||||
usage = 'CONFIG_NAME'
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
|
||||
# command options/arguments
|
||||
min_args = 1
|
||||
max_args = 1
|
||||
|
||||
def command(self):
|
||||
|
||||
# load the application
|
||||
config = self.load_configuration(self.args[0])
|
||||
setattr(config.app, 'reload', False)
|
||||
app = self.load_app(config)
|
||||
|
||||
# prepare the locals
|
||||
locs = dict(__name__='pecan-admin')
|
||||
locs['wsgiapp'] = app
|
||||
locs['app'] = TestApp(app)
|
||||
|
||||
# find the model for the app
|
||||
model = self.load_model(config)
|
||||
if model:
|
||||
locs['model'] = model
|
||||
|
||||
# insert the pecan locals
|
||||
exec('from pecan import abort, conf, redirect, request, response') in locs
|
||||
|
||||
# prepare the banner
|
||||
banner = ' The following objects are available:\n'
|
||||
banner += ' %-10s - This project\'s WSGI App instance\n' % 'wsgiapp'
|
||||
banner += ' %-10s - webtest.TestApp wrapped around wsgiapp\n' % 'app'
|
||||
if model:
|
||||
model_name = getattr(model, '__module__', getattr(model, '__name__', 'model'))
|
||||
banner += ' %-10s - Models from %s\n' % ('model', model_name)
|
||||
|
||||
# launch the shell, using IPython if available
|
||||
try:
|
||||
from IPython.Shell import IPShellEmbed
|
||||
shell = IPShellEmbed(argv=self.args)
|
||||
shell.set_banner(shell.IP.BANNER + '\n\n' + banner)
|
||||
shell(local_ns=locs, global_ns={})
|
||||
except ImportError:
|
||||
import code
|
||||
py_prefix = sys.platform.startswith('java') and 'J' or 'P'
|
||||
shell_banner = 'Pecan Interactive Shell\n%sython %s\n\n' % \
|
||||
(py_prefix, sys.version)
|
||||
shell = code.InteractiveConsole(locals=locs)
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
pass
|
||||
shell.interact(shell_banner + banner)
|
||||
|
||||
|
||||
class CreateCommand(CreateDistroCommand, Command):
|
||||
"""
|
||||
Creates the file layout for a new Pecan distribution.
|
||||
|
||||
For a template to show up when using this command, its name must begin
|
||||
with "pecan-". Although not required, it should also include the "Pecan"
|
||||
egg plugin for user convenience.
|
||||
"""
|
||||
|
||||
# command information
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
description = None
|
||||
|
||||
def command(self):
|
||||
if not self.options.list_templates:
|
||||
if not self.options.templates:
|
||||
self.options.templates = [DEFAULT_TEMPLATE]
|
||||
try:
|
||||
return CreateDistroCommand.command(self)
|
||||
except LookupError, ex:
|
||||
sys.stderr.write('%s\n\n' % ex)
|
||||
CreateDistroCommand.list_templates(self)
|
||||
return 2
|
||||
|
||||
def all_entry_points(self):
|
||||
entry_points = []
|
||||
for entry in CreateDistroCommand.all_entry_points(self):
|
||||
if entry.name.startswith('pecan-'):
|
||||
entry = copy.copy(entry)
|
||||
entry_points.append(entry)
|
||||
entry.name = entry.name[6:]
|
||||
return entry_points
|
|
@ -0,0 +1,8 @@
|
|||
"""
|
||||
PasteScript commands for Pecan.
|
||||
"""
|
||||
|
||||
from runner import CommandRunner
|
||||
from create import CreateCommand
|
||||
from shell import ShellCommand
|
||||
from serve import ServeCommand
|
|
@ -0,0 +1,67 @@
|
|||
"""
|
||||
PasteScript base commands for Pecan.
|
||||
"""
|
||||
from pecan.configuration import _runtime_conf, set_config
|
||||
from paste.script import command as paste_command
|
||||
|
||||
import sys
|
||||
|
||||
class Command(paste_command.Command):
|
||||
"""
|
||||
Base class for Pecan commands.
|
||||
|
||||
This provides some standard functionality for interacting with Pecan
|
||||
applications and handles some of the basic PasteScript command cruft.
|
||||
|
||||
See ``paste.script.command.Command`` for more information.
|
||||
"""
|
||||
|
||||
# command information
|
||||
group_name = 'Pecan'
|
||||
summary = ''
|
||||
|
||||
# command parser
|
||||
parser = paste_command.Command.standard_parser()
|
||||
|
||||
def run(self, args):
|
||||
try:
|
||||
return paste_command.Command.run(self, args)
|
||||
except paste_command.BadCommand, ex:
|
||||
ex.args[0] = self.parser.error(ex.args[0])
|
||||
raise
|
||||
|
||||
def can_import(self, name):
|
||||
try:
|
||||
__import__(name)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def get_package_names(self, config):
|
||||
if not hasattr(config.app, 'modules'):
|
||||
return []
|
||||
return [module.__name__ for module in config.app.modules if hasattr(module, '__name__')]
|
||||
|
||||
def load_configuration(self, name):
|
||||
set_config(name)
|
||||
return _runtime_conf
|
||||
|
||||
def load_app(self, config):
|
||||
for package_name in self.get_package_names(config):
|
||||
module_name = '%s.app' % package_name
|
||||
if self.can_import(module_name):
|
||||
module = sys.modules[module_name]
|
||||
if hasattr(module, 'setup_app'):
|
||||
return module.setup_app(config)
|
||||
raise paste_command.BadCommand('No app.setup_app found in any of the configured app.modules')
|
||||
|
||||
def load_model(self, config):
|
||||
for package_name in self.get_package_names(config):
|
||||
module_name = '%s.model' % package_name
|
||||
if self.can_import(module_name):
|
||||
return sys.modules[module_name]
|
||||
return None
|
||||
|
||||
def command(self):
|
||||
pass
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
PasteScript commands for Pecan.
|
||||
"""
|
||||
from paste.script.create_distro import CreateDistroCommand
|
||||
|
||||
from pecan.templates import DEFAULT_TEMPLATE
|
||||
from base import Command
|
||||
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
|
||||
class CreateCommand(CreateDistroCommand, Command):
|
||||
"""
|
||||
Creates the file layout for a new Pecan distribution.
|
||||
|
||||
For a template to show up when using this command, its name must begin
|
||||
with "pecan-". Although not required, it should also include the "Pecan"
|
||||
egg plugin for user convenience.
|
||||
"""
|
||||
|
||||
# command information
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
description = None
|
||||
|
||||
def command(self):
|
||||
if not self.options.list_templates:
|
||||
if not self.options.templates:
|
||||
self.options.templates = [DEFAULT_TEMPLATE]
|
||||
try:
|
||||
return CreateDistroCommand.command(self)
|
||||
except LookupError, ex:
|
||||
sys.stderr.write('%s\n\n' % ex)
|
||||
CreateDistroCommand.list_templates(self)
|
||||
return 2
|
||||
|
||||
def all_entry_points(self):
|
||||
entry_points = []
|
||||
for entry in CreateDistroCommand.all_entry_points(self):
|
||||
if entry.name.startswith('pecan-'):
|
||||
entry = copy.copy(entry)
|
||||
entry_points.append(entry)
|
||||
entry.name = entry.name[6:]
|
||||
return entry_points
|
|
@ -0,0 +1,129 @@
|
|||
"""
|
||||
PasteScript Command Runner
|
||||
"""
|
||||
from paste.script import command as paste_command
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import pkg_resources
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
|
||||
class CommandRunner(object):
|
||||
"""
|
||||
Dispatches command execution requests.
|
||||
|
||||
This is a custom PasteScript command runner that is specific to Pecan
|
||||
commands. For a command to show up, its name must begin with "pecan-".
|
||||
It is also recommended that its group name be set to "Pecan" so that it
|
||||
shows up under that group when using ``paster`` directly.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# set up the parser
|
||||
self.parser = optparse.OptionParser(add_help_option=False,
|
||||
version='Pecan %s' % self.get_version(),
|
||||
usage='%prog [options] COMMAND [command_options]')
|
||||
self.parser.disable_interspersed_args()
|
||||
self.parser.add_option('-h', '--help',
|
||||
action='store_true',
|
||||
dest='show_help',
|
||||
help='show detailed help message')
|
||||
|
||||
# suppress BaseException.message warnings for BadCommand
|
||||
if sys.version_info < (2, 7):
|
||||
warnings.filterwarnings(
|
||||
'ignore',
|
||||
'BaseException\.message has been deprecated as of Python 2\.6',
|
||||
DeprecationWarning,
|
||||
paste_command.__name__.replace('.', '\\.'))
|
||||
|
||||
# register Pecan as a system plugin when using the custom runner
|
||||
paste_command.system_plugins.append('Pecan')
|
||||
|
||||
def get_command_template(self, command_names):
|
||||
if not command_names:
|
||||
max_length = 10
|
||||
else:
|
||||
max_length = max([len(name) for name in command_names])
|
||||
return ' %%-%ds %%s\n' % max_length
|
||||
|
||||
def get_commands(self):
|
||||
commands = {}
|
||||
for name, command in paste_command.get_commands().iteritems():
|
||||
if name.startswith('pecan-'):
|
||||
commands[name[6:]] = command.load()
|
||||
return commands
|
||||
|
||||
def get_version(self):
|
||||
try:
|
||||
dist = pkg_resources.get_distribution('Pecan')
|
||||
if os.path.dirname(os.path.dirname(__file__)) == dist.location:
|
||||
return dist.version
|
||||
else:
|
||||
return '(development)'
|
||||
except:
|
||||
return '(development)'
|
||||
|
||||
def print_usage(self, file=sys.stdout):
|
||||
self.parser.print_help(file=file)
|
||||
file.write('\n')
|
||||
command_groups = {}
|
||||
commands = self.get_commands()
|
||||
if not commands:
|
||||
file.write('No commands registered.\n')
|
||||
return
|
||||
command_template = self.get_command_template(commands.keys())
|
||||
for name, command in commands.iteritems():
|
||||
group_name = command.group_name
|
||||
if group_name.lower() == 'pecan':
|
||||
group_name = ''
|
||||
command_groups.setdefault(group_name, {})[name] = command
|
||||
command_groups = sorted(command_groups.items())
|
||||
for i, (group, commands) in enumerate(command_groups):
|
||||
file.write('%s:\n' % (group or 'Commands'))
|
||||
for name, command in sorted(commands.items()):
|
||||
file.write(command_template % (name, command.summary))
|
||||
if i + 1 < len(command_groups):
|
||||
file.write('\n')
|
||||
|
||||
def print_known_commands(self, file=sys.stderr):
|
||||
commands = self.get_commands()
|
||||
command_names = sorted(commands.keys())
|
||||
if not command_names:
|
||||
file.write('No commands registered.\n')
|
||||
return
|
||||
file.write('Known commands:\n')
|
||||
command_template = self.get_command_template(command_names)
|
||||
for name in command_names:
|
||||
file.write(command_template % (name, commands[name].summary))
|
||||
|
||||
def run(self, args):
|
||||
options, args = self.parser.parse_args(args)
|
||||
if not args:
|
||||
self.print_usage()
|
||||
return 0
|
||||
command_name = args.pop(0)
|
||||
commands = self.get_commands()
|
||||
if command_name not in commands:
|
||||
sys.stderr.write('Command %s not known\n\n' % command_name)
|
||||
self.print_known_commands()
|
||||
return 1
|
||||
else:
|
||||
command = commands[command_name](command_name)
|
||||
if options.show_help:
|
||||
return command.run(['-h'])
|
||||
else:
|
||||
return command.run(args)
|
||||
|
||||
@classmethod
|
||||
def handle_command_line(cls):
|
||||
try:
|
||||
runner = CommandRunner()
|
||||
exit_code = runner.run(sys.argv[1:])
|
||||
except paste_command.BadCommand, ex:
|
||||
sys.stderr.write('%s\n' % ex)
|
||||
exit_code = ex.exit_code
|
||||
sys.exit(exit_code)
|
|
@ -0,0 +1,594 @@
|
|||
# Code heavily borrowed from:
|
||||
# (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
|
||||
#
|
||||
# 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
|
||||
import re
|
||||
import os
|
||||
import errno
|
||||
import sys
|
||||
import time
|
||||
from paste import httpserver
|
||||
try:
|
||||
import subprocess
|
||||
except ImportError:
|
||||
from paste.util import subprocess24 as subprocess
|
||||
import threading
|
||||
import atexit
|
||||
import logging
|
||||
|
||||
from base import Command
|
||||
|
||||
MAXFD = 1024
|
||||
|
||||
jython = sys.platform.startswith('java')
|
||||
|
||||
class DaemonizeException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServeCommand(Command):
|
||||
min_args = 0
|
||||
usage = 'CONFIG_FILE [start|stop|restart|status]'
|
||||
summary = "Serve the pecan web application"
|
||||
description = """\
|
||||
This command serves a pecan web application using the provided
|
||||
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.
|
||||
"""
|
||||
|
||||
# used by subclasses that configure apps and servers differently
|
||||
requires_config_file = True
|
||||
|
||||
parser = Command.standard_parser(quiet=True)
|
||||
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")
|
||||
|
||||
|
||||
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[:]
|
||||
|
||||
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:
|
||||
if cmd == 'restart':
|
||||
print "Could not stop daemon; aborting"
|
||||
else:
|
||||
print "Could not stop daemon"
|
||||
return result
|
||||
if cmd == 'stop':
|
||||
return result
|
||||
|
||||
vars = self.parse_vars(restvars)
|
||||
|
||||
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, 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, 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, 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)
|
||||
|
||||
# load the application
|
||||
config = self.load_configuration(self.args[0])
|
||||
app = self.load_app(config)
|
||||
|
||||
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:
|
||||
httpserver.serve(app, config.server.host, config.server.port)
|
||||
except (SystemExit, KeyboardInterrupt), e:
|
||||
if self.verbose > 1:
|
||||
raise
|
||||
if str(e):
|
||||
msg = ' '+str(e)
|
||||
else:
|
||||
msg = ''
|
||||
print 'Exiting%s (-v to see traceback)' % msg
|
||||
|
||||
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?
|
||||
|
||||
import resource # Resource usage information.
|
||||
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), e:
|
||||
print "Could not delete: %s" % e
|
||||
return 2
|
||||
return 1
|
||||
for j in range(10):
|
||||
if not live_pidfile(pid_file):
|
||||
break
|
||||
import signal
|
||||
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')):
|
||||
import signal
|
||||
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
|
||||
import pwd, grp
|
||||
uid = gid = None
|
||||
if group:
|
||||
try:
|
||||
gid = int(group)
|
||||
group = grp.getgrgid(gid).gr_name
|
||||
except ValueError:
|
||||
import grp
|
||||
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 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, 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, 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, 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.
|
||||
import socket
|
||||
import errno
|
||||
for bound_address in bound_addresses:
|
||||
for attempt in range(maxtries):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.connect(bound_address)
|
||||
except socket.error, e:
|
||||
if e.args[0] != 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.
|
||||
"""
|
||||
try:
|
||||
import signal
|
||||
except ImportError:
|
||||
return
|
||||
def handle_term(signo, frame):
|
||||
raise SystemExit
|
||||
signal.signal(signal.SIGTERM, handle_term)
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
"""
|
||||
PasteScript shell command for Pecan.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from webtest import TestApp
|
||||
|
||||
from base import Command
|
||||
|
||||
|
||||
class ShellCommand(Command):
|
||||
"""
|
||||
Open an interactive shell with the Pecan app loaded.
|
||||
"""
|
||||
|
||||
# command information
|
||||
usage = 'CONFIG_NAME'
|
||||
summary = __doc__.strip().splitlines()[0].rstrip('.')
|
||||
|
||||
# command options/arguments
|
||||
min_args = 1
|
||||
max_args = 1
|
||||
|
||||
def command(self):
|
||||
|
||||
# load the application
|
||||
config = self.load_configuration(self.args[0])
|
||||
setattr(config.app, 'reload', False)
|
||||
app = self.load_app(config)
|
||||
|
||||
# prepare the locals
|
||||
locs = dict(__name__='pecan-admin')
|
||||
locs['wsgiapp'] = app
|
||||
locs['app'] = TestApp(app)
|
||||
|
||||
# find the model for the app
|
||||
model = self.load_model(config)
|
||||
if model:
|
||||
locs['model'] = model
|
||||
|
||||
# insert the pecan locals
|
||||
exec('from pecan import abort, conf, redirect, request, response') in locs
|
||||
|
||||
# prepare the banner
|
||||
banner = ' The following objects are available:\n'
|
||||
banner += ' %-10s - This project\'s WSGI App instance\n' % 'wsgiapp'
|
||||
banner += ' %-10s - The current configuration\n' % 'conf'
|
||||
banner += ' %-10s - webtest.TestApp wrapped around wsgiapp\n' % 'app'
|
||||
if model:
|
||||
model_name = getattr(model, '__module__', getattr(model, '__name__', 'model'))
|
||||
banner += ' %-10s - Models from %s\n' % ('model', model_name)
|
||||
|
||||
# launch the shell, using IPython if available
|
||||
try:
|
||||
from IPython.Shell import IPShellEmbed
|
||||
shell = IPShellEmbed(argv=self.args)
|
||||
shell.set_banner(shell.IP.BANNER + '\n\n' + banner)
|
||||
shell(local_ns=locs, global_ns={})
|
||||
except ImportError:
|
||||
import code
|
||||
py_prefix = sys.platform.startswith('java') and 'J' or 'P'
|
||||
shell_banner = 'Pecan Interactive Shell\n%sython %s\n\n' % \
|
||||
(py_prefix, sys.version)
|
||||
shell = code.InteractiveConsole(locals=locs)
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
pass
|
||||
shell.interact(shell_banner + banner)
|
|
@ -1,5 +1,4 @@
|
|||
from configuration import _runtime_conf
|
||||
from monitor import MonitorableProcess
|
||||
from templating import RendererFactory
|
||||
from routing import lookup_controller
|
||||
|
||||
|
@ -59,7 +58,7 @@ def error_for(field):
|
|||
return request.validation_error.error_dict.get(field, '')
|
||||
|
||||
|
||||
class Pecan(MonitorableProcess):
|
||||
class Pecan(object):
|
||||
def __init__(self, root,
|
||||
default_renderer = 'mako',
|
||||
template_path = 'templates',
|
||||
|
@ -74,10 +73,6 @@ class Pecan(MonitorableProcess):
|
|||
self.hooks = hooks
|
||||
self.template_path = template_path
|
||||
|
||||
MonitorableProcess.__init__(self)
|
||||
if getattr(_runtime_conf, 'app', None) and getattr(_runtime_conf.app, 'reload', False) is True:
|
||||
self.start_monitoring()
|
||||
|
||||
def get_content_type(self, format):
|
||||
return {
|
||||
'html' : 'text/html',
|
||||
|
@ -223,7 +218,7 @@ class Pecan(MonitorableProcess):
|
|||
return
|
||||
|
||||
raw_namespace = result
|
||||
|
||||
|
||||
# pull the template out based upon content type and handle overrides
|
||||
template = controller.pecan.get('content_types', {}).get(state.content_type)
|
||||
template = getattr(request, 'override_template', template)
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
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)
|
Loading…
Reference in New Issue