1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/57ca00cec386/ changeset: 57ca00cec386 user: james_taylor date: 2013-01-30 21:26:00 summary: changeset: 8701:c3ac9849dd1b branch: unstable tag: tip user: James Taylor <james@jamestaylor.org> summary: core: pull the pieces of PasteScript we use into galaxy.util.pastescript. Remove all dependencies on PasteScript and PasteDeploy. Begin stripping down the serve command so we can start to enhance it for our needs. affected #: 15 files diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 eggs.ini --- a/eggs.ini +++ b/eggs.ini @@ -47,8 +47,6 @@ NoseHTML = 0.4.1 NoseTestDiff = 0.1 Paste = 1.6 -PasteDeploy = 1.3.3 -PasteScript = 1.7.3 pexpect = 2.4 python_openid = 2.2.5 python_daemon = 1.5.5 diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/jobs/runners/condor.py --- a/lib/galaxy/jobs/runners/condor.py +++ b/lib/galaxy/jobs/runners/condor.py @@ -4,7 +4,7 @@ from galaxy import model from galaxy.jobs.runners import BaseJobRunner -from paste.deploy.converters import asbool +from galaxy.util import asbool import pkg_resources diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/jobs/runners/drmaa.py --- a/lib/galaxy/jobs/runners/drmaa.py +++ b/lib/galaxy/jobs/runners/drmaa.py @@ -10,8 +10,6 @@ from galaxy import model from galaxy.jobs.runners import BaseJobRunner -from paste.deploy.converters import asbool - import pkg_resources diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/jobs/runners/pbs.py --- a/lib/galaxy/jobs/runners/pbs.py +++ b/lib/galaxy/jobs/runners/pbs.py @@ -7,8 +7,6 @@ from galaxy.util.bunch import Bunch from galaxy.jobs.runners import BaseJobRunner -from paste.deploy.converters import asbool - import pkg_resources egg_message = """ diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/util/__init__.py --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -333,6 +333,21 @@ # No luck, return empty string return '' +# asbool implementation pulled from PasteDeploy +truthy = frozenset(['true', 'yes', 'on', 'y', 't', '1']) +falsy = frozenset(['false', 'no', 'off', 'n', 'f', '0']) +def asbool(obj): + if isinstance(obj, basestring): + obj = obj.strip().lower() + if obj in truthy: + return True + elif obj in falsy: + return False + else: + raise ValueError("String is not true/false: %r" % obj) + return bool(obj) + + def string_as_bool( string ): if str( string ).lower() in ( 'true', 'yes', 'on' ): return True diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/util/pastescript/loadwsgi.py --- /dev/null +++ b/lib/galaxy/util/pastescript/loadwsgi.py @@ -0,0 +1,828 @@ +# (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 + +# Mostly taken from PasteDeploy and stripped down for Galaxy + +from __future__ import with_statement + +import inspect +import os +import sys +import re + +import pkg_resources + +__all__ = ['loadapp', 'loadserver', 'loadfilter', 'appconfig'] + +# ---- from paste.deploy.compat -------------------------------------- + +"""Python 2<->3 compatibility module""" + +def print_(template, *args, **kwargs): + template = str(template) + if args: + template = template % args + elif kwargs: + template = template % kwargs + sys.stdout.writelines(template) + +if sys.version_info < (3, 0): + basestring = basestring + from ConfigParser import ConfigParser + from urllib import unquote + iteritems = lambda d: d.iteritems() + dictkeys = lambda d: d.keys() + + def reraise(t, e, tb): + exec('raise t, e, tb', dict(t=t, e=e, tb=tb)) +else: + basestring = str + from configparser import ConfigParser + from urllib.parse import unquote + iteritems = lambda d: d.items() + dictkeys = lambda d: list(d.keys()) + + def reraise(t, e, tb): + exec('raise e from tb', dict(e=e, tb=tb)) + +# ---- from paste.deploy.util ---------------------------------------- + +def fix_type_error(exc_info, callable, varargs, kwargs): + """ + Given an exception, this will test if the exception was due to a + signature error, and annotate the error with better information if + so. + + Usage:: + + try: + val = callable(*args, **kw) + except TypeError: + exc_info = fix_type_error(None, callable, args, kw) + raise exc_info[0], exc_info[1], exc_info[2] + """ + if exc_info is None: + exc_info = sys.exc_info() + if (exc_info[0] != TypeError + or str(exc_info[1]).find('arguments') == -1 + or getattr(exc_info[1], '_type_error_fixed', False)): + return exc_info + exc_info[1]._type_error_fixed = True + argspec = inspect.formatargspec(*inspect.getargspec(callable)) + args = ', '.join(map(_short_repr, varargs)) + if kwargs and args: + args += ', ' + if kwargs: + kwargs = kwargs.items() + kwargs.sort() + args += ', '.join(['%s=...' % n for n, v in kwargs]) + gotspec = '(%s)' % args + msg = '%s; got %s, wanted %s' % (exc_info[1], gotspec, argspec) + exc_info[1].args = (msg,) + return exc_info + + +def _short_repr(v): + v = repr(v) + if len(v) > 12: + v = v[:8] + '...' + v[-4:] + return v + + +def fix_call(callable, *args, **kw): + """ + Call ``callable(*args, **kw)`` fixing any type errors that come out. + """ + try: + val = callable(*args, **kw) + except TypeError: + exc_info = fix_type_error(None, callable, args, kw) + reraise(*exc_info) + return val + + +def lookup_object(spec): + """ + Looks up a module or object from a some.module:func_name specification. + To just look up a module, omit the colon and everything after it. + """ + parts, target = spec.split(':') if ':' in spec else (spec, None) + module = __import__(parts) + + for part in parts.split('.')[1:] + ([target] if target else []): + module = getattr(module, part) + + return module + +# ---- from paste.deploy.loadwsgi ------------------------------------ + +############################################################ +## Utility functions +############################################################ + + +def import_string(s): + return pkg_resources.EntryPoint.parse("x=" + s).load(False) + + +def _aslist(obj): + """ + Turn object into a list; lists and tuples are left as-is, None + becomes [], and everything else turns into a one-element list. + """ + if obj is None: + return [] + elif isinstance(obj, (list, tuple)): + return obj + else: + return [obj] + + +def _flatten(lst): + """ + Flatten a nested list. + """ + if not isinstance(lst, (list, tuple)): + return [lst] + result = [] + for item in lst: + result.extend(_flatten(item)) + return result + + +class NicerConfigParser(ConfigParser): + + def __init__(self, filename, *args, **kw): + ConfigParser.__init__(self, *args, **kw) + self.filename = filename + if hasattr(self, '_interpolation'): + self._interpolation = self.InterpolateWrapper(self._interpolation) + + read_file = getattr(ConfigParser, 'read_file', ConfigParser.readfp) + + def defaults(self): + """Return the defaults, with their values interpolated (with the + defaults dict itself) + + Mainly to support defaults using values such as %(here)s + """ + defaults = ConfigParser.defaults(self).copy() + for key, val in iteritems(defaults): + defaults[key] = self.get('DEFAULT', key) or val + return defaults + + def _interpolate(self, section, option, rawval, vars): + # Python < 3.2 + try: + return ConfigParser._interpolate( + self, section, option, rawval, vars) + except Exception: + e = sys.exc_info()[1] + args = list(e.args) + args[0] = 'Error in file %s: %s' % (self.filename, e) + e.args = tuple(args) + e.message = args[0] + raise + + class InterpolateWrapper(object): + # Python >= 3.2 + def __init__(self, original): + self._original = original + + def __getattr__(self, name): + return getattr(self._original, name) + + def before_get(self, parser, section, option, value, defaults): + try: + return self._original.before_get(parser, section, option, + value, defaults) + except Exception: + e = sys.exc_info()[1] + args = list(e.args) + args[0] = 'Error in file %s: %s' % (parser.filename, e) + e.args = tuple(args) + e.message = args[0] + raise + + +############################################################ +## Object types +############################################################ + + +class _ObjectType(object): + + name = None + egg_protocols = None + config_prefixes = None + + def __init__(self): + # Normalize these variables: + self.egg_protocols = [_aslist(p) for p in _aslist(self.egg_protocols)] + self.config_prefixes = [_aslist(p) for p in _aslist(self.config_prefixes)] + + def __repr__(self): + return '<%s protocols=%r prefixes=%r>' % ( + self.name, self.egg_protocols, self.config_prefixes) + + def invoke(self, context): + assert context.protocol in _flatten(self.egg_protocols) + return fix_call(context.object, + context.global_conf, **context.local_conf) + + +class _App(_ObjectType): + + name = 'application' + egg_protocols = ['paste.app_factory', 'paste.composite_factory', + 'paste.composit_factory'] + config_prefixes = [['app', 'application'], ['composite', 'composit'], + 'pipeline', 'filter-app'] + + def invoke(self, context): + if context.protocol in ('paste.composit_factory', + 'paste.composite_factory'): + return fix_call(context.object, + context.loader, context.global_conf, + **context.local_conf) + elif context.protocol == 'paste.app_factory': + return fix_call(context.object, context.global_conf, **context.local_conf) + else: + assert 0, "Protocol %r unknown" % context.protocol + +APP = _App() + + +class _Filter(_ObjectType): + name = 'filter' + egg_protocols = [['paste.filter_factory', 'paste.filter_app_factory']] + config_prefixes = ['filter'] + + def invoke(self, context): + if context.protocol == 'paste.filter_factory': + return fix_call(context.object, + context.global_conf, **context.local_conf) + elif context.protocol == 'paste.filter_app_factory': + def filter_wrapper(wsgi_app): + # This should be an object, so it has a nicer __repr__ + return fix_call(context.object, + wsgi_app, context.global_conf, + **context.local_conf) + return filter_wrapper + else: + assert 0, "Protocol %r unknown" % context.protocol + +FILTER = _Filter() + + +class _Server(_ObjectType): + name = 'server' + egg_protocols = [['paste.server_factory', 'paste.server_runner']] + config_prefixes = ['server'] + + def invoke(self, context): + if context.protocol == 'paste.server_factory': + return fix_call(context.object, + context.global_conf, **context.local_conf) + elif context.protocol == 'paste.server_runner': + def server_wrapper(wsgi_app): + # This should be an object, so it has a nicer __repr__ + return fix_call(context.object, + wsgi_app, context.global_conf, + **context.local_conf) + return server_wrapper + else: + assert 0, "Protocol %r unknown" % context.protocol + +SERVER = _Server() + + +# Virtual type: (@@: There's clearly something crufty here; +# this probably could be more elegant) +class _PipeLine(_ObjectType): + name = 'pipeline' + + def invoke(self, context): + app = context.app_context.create() + filters = [c.create() for c in context.filter_contexts] + filters.reverse() + for filter in filters: + app = filter(app) + return app + +PIPELINE = _PipeLine() + + +class _FilterApp(_ObjectType): + name = 'filter_app' + + def invoke(self, context): + next_app = context.next_context.create() + filter = context.filter_context.create() + return filter(next_app) + +FILTER_APP = _FilterApp() + + +class _FilterWith(_App): + name = 'filtered_with' + + def invoke(self, context): + filter = context.filter_context.create() + filtered = context.next_context.create() + if context.next_context.object_type is APP: + return filter(filtered) + else: + # filtering a filter + def composed(app): + return filter(filtered(app)) + return composed + +FILTER_WITH = _FilterWith() + + +############################################################ +## Loaders +############################################################ + + +def loadapp(uri, name=None, **kw): + return loadobj(APP, uri, name=name, **kw) + + +def loadfilter(uri, name=None, **kw): + return loadobj(FILTER, uri, name=name, **kw) + + +def loadserver(uri, name=None, **kw): + return loadobj(SERVER, uri, name=name, **kw) + + +def appconfig(uri, name=None, relative_to=None, global_conf=None): + context = loadcontext(APP, uri, name=name, + relative_to=relative_to, + global_conf=global_conf) + return context.config() + +_loaders = {} + + +def loadobj(object_type, uri, name=None, relative_to=None, + global_conf=None): + context = loadcontext( + object_type, uri, name=name, relative_to=relative_to, + global_conf=global_conf) + return context.create() + + +def loadcontext(object_type, uri, name=None, relative_to=None, + global_conf=None): + if '#' in uri: + if name is None: + uri, name = uri.split('#', 1) + else: + # @@: Ignore fragment or error? + uri = uri.split('#', 1)[0] + if name is None: + name = 'main' + if ':' not in uri: + raise LookupError("URI has no scheme: %r" % uri) + scheme, path = uri.split(':', 1) + scheme = scheme.lower() + if scheme not in _loaders: + raise LookupError( + "URI scheme not known: %r (from %s)" + % (scheme, ', '.join(_loaders.keys()))) + return _loaders[scheme]( + object_type, + uri, path, name=name, relative_to=relative_to, + global_conf=global_conf) + + +def _loadconfig(object_type, uri, path, name, relative_to, + global_conf): + isabs = os.path.isabs(path) + # De-Windowsify the paths: + path = path.replace('\\', '/') + if not isabs: + if not relative_to: + raise ValueError( + "Cannot resolve relative uri %r; no relative_to keyword " + "argument given" % uri) + relative_to = relative_to.replace('\\', '/') + if relative_to.endswith('/'): + path = relative_to + path + else: + path = relative_to + '/' + path + if path.startswith('///'): + path = path[2:] + path = unquote(path) + loader = ConfigLoader(path) + if global_conf: + loader.update_defaults(global_conf, overwrite=False) + return loader.get_context(object_type, name, global_conf) + +_loaders['config'] = _loadconfig + + +def _loadegg(object_type, uri, spec, name, relative_to, + global_conf): + loader = EggLoader(spec) + return loader.get_context(object_type, name, global_conf) + +_loaders['egg'] = _loadegg + + +def _loadfunc(object_type, uri, spec, name, relative_to, + global_conf): + + loader = FuncLoader(spec) + return loader.get_context(object_type, name, global_conf) + +_loaders['call'] = _loadfunc + +############################################################ +## Loaders +############################################################ + + +class _Loader(object): + + def get_app(self, name=None, global_conf=None): + return self.app_context( + name=name, global_conf=global_conf).create() + + def get_filter(self, name=None, global_conf=None): + return self.filter_context( + name=name, global_conf=global_conf).create() + + def get_server(self, name=None, global_conf=None): + return self.server_context( + name=name, global_conf=global_conf).create() + + def app_context(self, name=None, global_conf=None): + return self.get_context( + APP, name=name, global_conf=global_conf) + + def filter_context(self, name=None, global_conf=None): + return self.get_context( + FILTER, name=name, global_conf=global_conf) + + def server_context(self, name=None, global_conf=None): + return self.get_context( + SERVER, name=name, global_conf=global_conf) + + _absolute_re = re.compile(r'^[a-zA-Z]+:') + + def absolute_name(self, name): + """ + Returns true if the name includes a scheme + """ + if name is None: + return False + return self._absolute_re.search(name) + + +class ConfigLoader(_Loader): + + def __init__(self, filename): + self.filename = filename = filename.strip() + defaults = { + 'here': os.path.dirname(os.path.abspath(filename)), + '__file__': os.path.abspath(filename) + } + self.parser = NicerConfigParser(filename, defaults=defaults) + self.parser.optionxform = str # Don't lower-case keys + with open(filename) as f: + self.parser.read_file(f) + + def update_defaults(self, new_defaults, overwrite=True): + for key, value in iteritems(new_defaults): + if not overwrite and key in self.parser._defaults: + continue + self.parser._defaults[key] = value + + def get_context(self, object_type, name=None, global_conf=None): + if self.absolute_name(name): + return loadcontext(object_type, name, + relative_to=os.path.dirname(self.filename), + global_conf=global_conf) + section = self.find_config_section( + object_type, name=name) + if global_conf is None: + global_conf = {} + else: + global_conf = global_conf.copy() + defaults = self.parser.defaults() + global_conf.update(defaults) + local_conf = {} + global_additions = {} + get_from_globals = {} + for option in self.parser.options(section): + if option.startswith('set '): + name = option[4:].strip() + global_additions[name] = global_conf[name] = ( + self.parser.get(section, option)) + elif option.startswith('get '): + name = option[4:].strip() + get_from_globals[name] = self.parser.get(section, option) + else: + if option in defaults: + # @@: It's a global option (?), so skip it + continue + local_conf[option] = self.parser.get(section, option) + for local_var, glob_var in get_from_globals.items(): + local_conf[local_var] = global_conf[glob_var] + if object_type in (APP, FILTER) and 'filter-with' in local_conf: + filter_with = local_conf.pop('filter-with') + else: + filter_with = None + if 'require' in local_conf: + for spec in local_conf['require'].split(): + pkg_resources.require(spec) + del local_conf['require'] + if section.startswith('filter-app:'): + context = self._filter_app_context( + object_type, section, name=name, + global_conf=global_conf, local_conf=local_conf, + global_additions=global_additions) + elif section.startswith('pipeline:'): + context = self._pipeline_app_context( + object_type, section, name=name, + global_conf=global_conf, local_conf=local_conf, + global_additions=global_additions) + elif 'use' in local_conf: + context = self._context_from_use( + object_type, local_conf, global_conf, global_additions, + section) + else: + context = self._context_from_explicit( + object_type, local_conf, global_conf, global_additions, + section) + if filter_with is not None: + filter_with_context = LoaderContext( + obj=None, + object_type=FILTER_WITH, + protocol=None, + global_conf=global_conf, local_conf=local_conf, + loader=self) + filter_with_context.filter_context = self.filter_context( + name=filter_with, global_conf=global_conf) + filter_with_context.next_context = context + return filter_with_context + return context + + def _context_from_use(self, object_type, local_conf, global_conf, + global_additions, section): + use = local_conf.pop('use') + context = self.get_context( + object_type, name=use, global_conf=global_conf) + context.global_conf.update(global_additions) + context.local_conf.update(local_conf) + if '__file__' in global_conf: + # use sections shouldn't overwrite the original __file__ + context.global_conf['__file__'] = global_conf['__file__'] + # @@: Should loader be overwritten? + context.loader = self + + if context.protocol is None: + # Determine protocol from section type + section_protocol = section.split(':', 1)[0] + if section_protocol in ('application', 'app'): + context.protocol = 'paste.app_factory' + elif section_protocol in ('composit', 'composite'): + context.protocol = 'paste.composit_factory' + else: + # This will work with 'server' and 'filter', otherwise it + # could fail but there is an error message already for + # bad protocols + context.protocol = 'paste.%s_factory' % section_protocol + + return context + + def _context_from_explicit(self, object_type, local_conf, global_conf, + global_addition, section): + possible = [] + for protocol_options in object_type.egg_protocols: + for protocol in protocol_options: + if protocol in local_conf: + possible.append((protocol, local_conf[protocol])) + break + if len(possible) > 1: + raise LookupError( + "Multiple protocols given in section %r: %s" + % (section, possible)) + if not possible: + raise LookupError( + "No loader given in section %r" % section) + found_protocol, found_expr = possible[0] + del local_conf[found_protocol] + value = import_string(found_expr) + context = LoaderContext( + value, object_type, found_protocol, + global_conf, local_conf, self) + return context + + def _filter_app_context(self, object_type, section, name, + global_conf, local_conf, global_additions): + if 'next' not in local_conf: + raise LookupError( + "The [%s] section in %s is missing a 'next' setting" + % (section, self.filename)) + next_name = local_conf.pop('next') + context = LoaderContext(None, FILTER_APP, None, global_conf, + local_conf, self) + context.next_context = self.get_context( + APP, next_name, global_conf) + if 'use' in local_conf: + context.filter_context = self._context_from_use( + FILTER, local_conf, global_conf, global_additions, + section) + else: + context.filter_context = self._context_from_explicit( + FILTER, local_conf, global_conf, global_additions, + section) + return context + + def _pipeline_app_context(self, object_type, section, name, + global_conf, local_conf, global_additions): + if 'pipeline' not in local_conf: + raise LookupError( + "The [%s] section in %s is missing a 'pipeline' setting" + % (section, self.filename)) + pipeline = local_conf.pop('pipeline').split() + if local_conf: + raise LookupError( + "The [%s] pipeline section in %s has extra " + "(disallowed) settings: %s" + % (', '.join(local_conf.keys()))) + context = LoaderContext(None, PIPELINE, None, global_conf, + local_conf, self) + context.app_context = self.get_context( + APP, pipeline[-1], global_conf) + context.filter_contexts = [ + self.get_context(FILTER, name, global_conf) + for name in pipeline[:-1]] + return context + + def find_config_section(self, object_type, name=None): + """ + Return the section name with the given name prefix (following the + same pattern as ``protocol_desc`` in ``config``. It must have the + given name, or for ``'main'`` an empty name is allowed. The + prefix must be followed by a ``:``. + + Case is *not* ignored. + """ + possible = [] + for name_options in object_type.config_prefixes: + for name_prefix in name_options: + found = self._find_sections( + self.parser.sections(), name_prefix, name) + if found: + possible.extend(found) + break + if not possible: + raise LookupError( + "No section %r (prefixed by %s) found in config %s" + % (name, + ' or '.join(map(repr, _flatten(object_type.config_prefixes))), + self.filename)) + if len(possible) > 1: + raise LookupError( + "Ambiguous section names %r for section %r (prefixed by %s) " + "found in config %s" + % (possible, name, + ' or '.join(map(repr, _flatten(object_type.config_prefixes))), + self.filename)) + return possible[0] + + def _find_sections(self, sections, name_prefix, name): + found = [] + if name is None: + if name_prefix in sections: + found.append(name_prefix) + name = 'main' + for section in sections: + if section.startswith(name_prefix + ':'): + if section[len(name_prefix) + 1:].strip() == name: + found.append(section) + return found + + +class EggLoader(_Loader): + + def __init__(self, spec): + self.spec = spec + + def get_context(self, object_type, name=None, global_conf=None): + if self.absolute_name(name): + return loadcontext(object_type, name, + global_conf=global_conf) + entry_point, protocol, ep_name = self.find_egg_entry_point( + object_type, name=name) + return LoaderContext( + entry_point, + object_type, + protocol, + global_conf or {}, {}, + self, + distribution=pkg_resources.get_distribution(self.spec), + entry_point_name=ep_name) + + def find_egg_entry_point(self, object_type, name=None): + """ + Returns the (entry_point, protocol) for the with the given + ``name``. + """ + if name is None: + name = 'main' + possible = [] + for protocol_options in object_type.egg_protocols: + for protocol in protocol_options: + pkg_resources.require(self.spec) + entry = pkg_resources.get_entry_info( + self.spec, + protocol, + name) + if entry is not None: + possible.append((entry.load(), protocol, entry.name)) + break + if not possible: + # Better exception + dist = pkg_resources.get_distribution(self.spec) + raise LookupError( + "Entry point %r not found in egg %r (dir: %s; protocols: %s; " + "entry_points: %s)" + % (name, self.spec, + dist.location, + ', '.join(_flatten(object_type.egg_protocols)), + ', '.join(_flatten([ + dictkeys(pkg_resources.get_entry_info(self.spec, prot, name) or {}) + for prot in protocol_options] or '(no entry points)')))) + if len(possible) > 1: + raise LookupError( + "Ambiguous entry points for %r in egg %r (protocols: %s)" + % (name, self.spec, ', '.join(_flatten(protocol_options)))) + return possible[0] + + +class FuncLoader(_Loader): + """ Loader that supports specifying functions inside modules, without + using eggs at all. Configuration should be in the format: + use = call:my.module.path:function_name + + Dot notation is supported in both the module and function name, e.g.: + use = call:my.module.path:object.method + """ + def __init__(self, spec): + self.spec = spec + if not ':' in spec: + raise LookupError("Configuration not in format module:function") + + def get_context(self, object_type, name=None, global_conf=None): + obj = lookup_object(self.spec) + return LoaderContext( + obj, + object_type, + None, # determine protocol from section type + global_conf or {}, + {}, + self, + ) + + +class LoaderContext(object): + + def __init__(self, obj, object_type, protocol, + global_conf, local_conf, loader, + distribution=None, entry_point_name=None): + self.object = obj + self.object_type = object_type + self.protocol = protocol + #assert protocol in _flatten(object_type.egg_protocols), ( + # "Bad protocol %r; should be one of %s" + # % (protocol, ', '.join(map(repr, _flatten(object_type.egg_protocols))))) + self.global_conf = global_conf + self.local_conf = local_conf + self.loader = loader + self.distribution = distribution + self.entry_point_name = entry_point_name + + def create(self): + return self.object_type.invoke(self) + + def config(self): + conf = AttrDict(self.global_conf) + conf.update(self.local_conf) + conf.local_conf = self.local_conf + conf.global_conf = self.global_conf + conf.context = self + return conf + + +class AttrDict(dict): + """ + A dictionary that can be assigned to. + """ + pass diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/util/pastescript/serve.py --- /dev/null +++ b/lib/galaxy/util/pastescript/serve.py @@ -0,0 +1,1066 @@ +# 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 +import ConfigParser +import atexit +import errno +import getpass +import logging +import optparse +import os +import re +import subprocess +import sys +import textwrap +import threading +import time +from logging.config import fileConfig + +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 + +# if sys.version_info >= (2, 6): +# from logging.config import fileConfig +# else: +# # Use our custom fileConfig -- 2.5.1's with a custom Formatter class +# # and less strict whitespace (which were incorporated into 2.6's) +# from paste.script.util.logging_config import fileConfig + +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): + #for name, value in os.environ.items(): + # print '%s: %s' % (name, value) + #print sys.argv + print ('Command %r not known (you may need to run setup.py egg_info)' + % self.command_name) + commands = get_commands().items() + 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: + if cmd == 'restart': + print "Could not stop daemon; aborting" + else: + print "Could not stop daemon" + 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, 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) + + 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), 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 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, 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) + +# ---- 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, e: + print e.message + exit_code = e.exit_code + sys.exit(exit_code) \ No newline at end of file diff -r e27d0dd127527b613ae77729b8b5b1e2696b09f2 -r 57ca00cec386a150a7d57b9e7f6a8c0d88367af6 lib/galaxy/web/framework/__init__.py --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -23,8 +23,8 @@ import helpers -pkg_resources.require( "PasteDeploy" ) -from paste.deploy.converters import asbool +from galaxy.util import asbool + import paste.httpexceptions pkg_resources.require( "Mako" ) This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.