2 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/65cd7c0eee89/ Changeset: 65cd7c0eee89 User: jmchilton Date: 2015-01-07 21:33:11+00:00 Summary: Update to brew_exts to latest platform-brew. Affected #: 1 file diff -r 8c57fc528cb7255ab11d20b03b5dd8509e3ea5a0 -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 lib/galaxy/tools/deps/brew_exts.py --- a/lib/galaxy/tools/deps/brew_exts.py +++ b/lib/galaxy/tools/deps/brew_exts.py @@ -28,6 +28,7 @@ import os import re import sys +import string import subprocess WHITESPACE_PATTERN = re.compile("[\s]+") @@ -43,6 +44,7 @@ CANNOT_DETERMINE_TAP_ERROR_MESSAGE = "Cannot determine tap of specified recipe - please use fully qualified recipe (e.g. homebrew/science/samtools)." VERBOSE = False RELAXED = False +BREW_ARGS = [] class BrewContext(object): @@ -104,6 +106,7 @@ def main(): global VERBOSE global RELAXED + global BREW_ARGS parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("--brew", help="Path to linuxbrew 'brew' executable to target") actions = ["vinstall", "vuninstall", "vdeps", "vinfo", "env"] @@ -114,11 +117,13 @@ parser.add_argument('version', metavar='version', help="Version for action (e.g. 0.1.19).") parser.add_argument('--relaxed', action='store_true', help="Relaxed processing - for instance allow use of env on non-vinstall-ed recipes.") parser.add_argument('--verbose', action='store_true', help="Verbose output") + parser.add_argument('restargs', nargs=argparse.REMAINDER) args = parser.parse_args() if args.verbose: VERBOSE = True if args.relaxed: RELAXED = True + BREW_ARGS = args.restargs if not action: action = args.action brew_context = BrewContext(args) @@ -159,7 +164,7 @@ return self.message -def versioned_install(recipe_context, package=None, version=None): +def versioned_install(recipe_context, package=None, version=None, installed_deps=[]): if package is None: package = recipe_context.recipe version = recipe_context.version @@ -176,10 +181,15 @@ versioned = version_info[2] if versioned: dep_to_version[dep] = dep_version + if dep in installed_deps: + continue versioned_install(recipe_context, dep, dep_version) + installed_deps.append(dep) else: # Install latest. dep_to_version[dep] = None + if dep in installed_deps: + continue unversioned_install(dep) try: for dep in deps: @@ -198,7 +208,16 @@ } deps_metadata.append(dep_metadata) - brew_execute(["install", package]) + cellar_root = recipe_context.brew_context.homebrew_cellar + cellar_path = recipe_context.cellar_path + env_actions = build_env_actions(deps_metadata, cellar_root, cellar_path, custom_only=True) + env = EnvAction.build_env(env_actions) + args = ["install"] + if VERBOSE: + args.append("--verbose") + args.extend(BREW_ARGS) + args.append(package) + brew_execute(args, env=env) deps = brew_execute(["deps", package]) deps = [d.strip() for d in deps.split("\n") if d] metadata = { @@ -278,10 +297,10 @@ pass -def brew_execute(args): +def brew_execute(args, env=None): os.environ["HOMEBREW_NO_EMOJI"] = "1" # simplify brew parsing. cmds = ["brew"] + args - return execute(cmds) + return execute(cmds, env=env) def build_env_statements_from_recipe_context(recipe_context, **kwds): @@ -290,11 +309,20 @@ return env_statements -def build_env_statements(cellar_root, cellar_path, relaxed=None): +def build_env_statements(cellar_root, cellar_path, relaxed=None, custom_only=False): deps = load_versioned_deps(cellar_path, relaxed=relaxed) + actions = build_env_actions(deps, cellar_root, cellar_path, relaxed, custom_only) + env_statements = [] + for action in actions: + env_statements.extend(action.to_statements()) + return "\n".join(env_statements) + + +def build_env_actions(deps, cellar_root, cellar_path, relaxed=None, custom_only=False): path_appends = [] ld_path_appends = [] + actions = [] def handle_keg(cellar_path): bin_path = os.path.join(cellar_path, "bin") @@ -303,6 +331,14 @@ lib_path = os.path.join(cellar_path, "lib") if os.path.isdir(lib_path): ld_path_appends.append(lib_path) + env_path = os.path.join(cellar_path, "platform_environment.json") + if os.path.exists(env_path): + with open(env_path, "r") as f: + env_metadata = json.load(f) + if "actions" in env_metadata: + def to_action(desc): + return EnvAction(cellar_path, desc) + actions.extend(map(to_action, env_metadata["actions"])) for dep in deps: package = dep['name'] @@ -311,14 +347,54 @@ handle_keg( dep_cellar_path ) handle_keg( cellar_path ) - env_statements = [] - if path_appends: - env_statements.append("PATH=" + ":".join(path_appends) + ":$PATH") - env_statements.append("export PATH") - if ld_path_appends: - env_statements.append("LD_LIBRARY_PATH=" + ":".join(ld_path_appends) + ":$LD_LIBRARY_PATH") - env_statements.append("export LD_LIBRARY_PATH") - return "\n".join(env_statements) + if not custom_only: + if path_appends: + actions.append(EnvAction(cellar_path, {"action": "prepend", "variable": "PATH", "value": ":".join(path_appends)})) + if ld_path_appends: + actions.append(EnvAction(cellar_path, {"action": "prepend", "variable": "LD_LIBRARY_PATH", "value": ":".join(path_appends)})) + return actions + + +class EnvAction(object): + + def __init__(self, keg_root, action_description): + self.variable = action_description["variable"] + self.action = action_description["action"] + self.value = string.Template(action_description["value"]).safe_substitute({ + 'KEG_ROOT': keg_root, + }) + + @staticmethod + def build_env(env_actions): + new_env = os.environ.copy() + map(lambda env_action: env_action.modify_environ(new_env), env_actions) + return new_env + + def modify_environ(self, environ): + if self.action == "set" or not environ.get(self.variable, ""): + environ[self.variable] = self.__eval("${value}") + elif self.action == "prepend": + environ[self.variable] = self.__eval("${value}:%s" % environ[self.variable]) + else: + environ[self.variable] = self.__eval("%s:${value}" % environ[self.variable]) + + def __eval(self, template): + return string.Template(template).safe_substitute( + variable=self.variable, + value=self.value, + ) + + def to_statements(self): + if self.action == "set": + template = '''${variable}="${value}"''' + elif self.action == "prepend": + template = '''${variable}="${value}:$$${variable}"''' + else: + template = '''${variable}="$$${variable}:${value}"''' + return [ + self.__eval(template), + "export %s" % self.variable + ] @contextlib.contextmanager @@ -350,8 +426,15 @@ return execute(cmds) -def execute(cmds): - p = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) +def execute(cmds, env=None): + subprocess_kwds = dict( + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if env: + subprocess_kwds["env"] = env + p = subprocess.Popen(cmds, **subprocess_kwds) #log = p.stdout.read() global VERBOSE stdout, stderr = p.communicate() @@ -363,7 +446,10 @@ def brew_deps(package): - stdout = brew_execute(["deps", package]) + args = ["deps"] + args.extend(BREW_ARGS) + args.append(package) + stdout = brew_execute(args) return [p.strip() for p in stdout.split("\n") if p] https://bitbucket.org/galaxy/galaxy-central/commits/63d901ca0e6e/ Changeset: 63d901ca0e6e User: jmchilton Date: 2015-01-07 21:33:11+00:00 Summary: Implement dependency resolver for Homebrew "tapped" tool shed packages (highly beta). Homebrew/Linuxbrew can be configured the same way the more vanilla Homebrew resolver works (added in 6b7782f). When this resolver is active, if the tool has been installed via the Tool Shed and the dependencies also installed it will use the database information to determine the dependent packages - otherwise it will just try to find the tool_dependencies.xml file on disk and recover the dependencies from that. This is for dependencies installed via Platform Brew (https://github.com/jmchilton/platform-brew) and converted from Tool Shed repositories usiing the shed2tap WIP code (e.g. https://github.com/jmchilton/homebrew-toolshed). Affected #: 7 files diff -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 -r 63d901ca0e6e9cfa405bae41f7088e3fa6bf243f lib/galaxy/tools/__init__.py --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1843,7 +1843,8 @@ """Return a list of commands to be run to populate the current environment to include this tools requirements.""" return self.app.toolbox.dependency_manager.dependency_shell_commands( self.requirements, - installed_tool_dependencies=self.installed_tool_dependencies + installed_tool_dependencies=self.installed_tool_dependencies, + tool_dir=self.tool_dir, ) @property diff -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 -r 63d901ca0e6e9cfa405bae41f7088e3fa6bf243f lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py --- /dev/null +++ b/lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py @@ -0,0 +1,150 @@ +""" +This dependency resolver resolves tool shed dependencies (those defined +tool_dependencies.xml) installed using Platform Homebrew and converted +via shed2tap (e.g. https://github.com/jmchilton/homebrew-toolshed). +""" +import logging +import os +from xml.etree import ElementTree as ET + +from .resolver_mixins import ( + UsesHomebrewMixin, + UsesToolDependencyDirMixin, + UsesInstalledRepositoriesMixin, +) +from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY + +log = logging.getLogger(__name__) + + +class HomebrewToolShedDependencyResolver( + DependencyResolver, + UsesHomebrewMixin, + UsesToolDependencyDirMixin, + UsesInstalledRepositoriesMixin, +): + resolver_type = "tool_shed_tap" + + def __init__(self, dependency_manager, **kwds): + self._init_homebrew(**kwds) + self._init_base_path(dependency_manager, **kwds) + + def resolve(self, name, version, type, **kwds): + if type != "package": + return INDETERMINATE_DEPENDENCY + + if version is None: + return INDETERMINATE_DEPENDENCY + + return self._find_tool_dependencies(name, version, type, **kwds) + + def _find_tool_dependencies(self, name, version, type, **kwds): + installed_tool_dependency = self._get_installed_dependency(name, type, version=version, **kwds) + if installed_tool_dependency: + return self._resolve_from_installed_tool_dependency(name, version, installed_tool_dependency) + + if "tool_dir" in kwds: + tool_directory = os.path.abspath(kwds["tool_dir"]) + tool_depenedencies_path = os.path.join(tool_directory, "tool_dependencies.xml") + if os.path.exists(tool_depenedencies_path): + return self._resolve_from_tool_dependencies_path(name, version, tool_depenedencies_path) + + return INDETERMINATE_DEPENDENCY + + def _resolve_from_installed_tool_dependency(self, name, version, installed_tool_dependency): + tool_shed_repository = installed_tool_dependency.tool_shed_repository + recipe_name = build_recipe_name( + package_name=name, + package_version=version, + repository_owner=tool_shed_repository.owner, + repository_name=tool_shed_repository.name, + ) + return self._find_dep_default(recipe_name, None) + + def _resolve_from_tool_dependencies_path(self, name, version, tool_dependencies_path): + try: + raw_dependencies = RawDependencies(tool_dependencies_path) + except Exception: + log.debug("Failed to parse dependencies in file %s" % tool_dependencies_path) + return INDETERMINATE_DEPENDENCY + + raw_dependency = raw_dependencies.find(name, version) + if not raw_dependency: + return INDETERMINATE_DEPENDENCY + + recipe_name = build_recipe_name( + package_name=name, + package_version=version, + repository_owner=raw_dependency.repository_owner, + repository_name=raw_dependency.repository_name + ) + dep = self._find_dep_default(recipe_name, None) + return dep + + +class RawDependencies(object): + + def __init__(self, dependencies_file): + self.root = ET.parse(dependencies_file).getroot() + dependencies = [] + package_els = self.root.findall("package") or [] + for package_el in package_els: + repository_el = package_el.find("repository") + if repository_el is None: + continue + dependency = RawDependency(self, package_el, repository_el) + dependencies.append(dependency) + self.dependencies = dependencies + + def find(self, package_name, package_version): + target_dependency = None + + for dependency in self.dependencies: + if dependency.package_name == package_name and dependency.package_version == package_version: + target_dependency = dependency + break + return target_dependency + + +class RawDependency(object): + + def __init__(self, dependencies, package_el, repository_el): + self.dependencies = dependencies + self.package_el = package_el + self.repository_el = repository_el + + def __repr__(self): + temp = "Dependency[package_name=%s,version=%s,dependent_package=%s]" + return temp % ( + self.package_el.attrib["name"], + self.package_el.attrib["version"], + self.repository_el.attrib["name"] + ) + + @property + def repository_owner(self): + return self.repository_el.attrib["owner"] + + @property + def repository_name(self): + return self.repository_el.attrib["name"] + + @property + def package_name(self): + return self.package_el.attrib["name"] + + @property + def package_version(self): + return self.package_el.attrib["version"] + + +def build_recipe_name(package_name, package_version, repository_owner, repository_name): + # TODO: Consider baking package_name and package_version into name? (would be more "correct") + owner = repository_owner.replace("-", "") + name = repository_name + name = name.replace("_", "").replace("-", "") + base = "%s_%s" % (owner, name) + return base + + +__all__ = [HomebrewToolShedDependencyResolver] diff -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 -r 63d901ca0e6e9cfa405bae41f7088e3fa6bf243f lib/galaxy/tools/deps/resolvers/galaxy_packages.py --- a/lib/galaxy/tools/deps/resolvers/galaxy_packages.py +++ b/lib/galaxy/tools/deps/resolvers/galaxy_packages.py @@ -1,12 +1,13 @@ -from os.path import join, islink, realpath, basename, exists, abspath +from os.path import join, islink, realpath, basename, exists from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY, Dependency +from .resolver_mixins import UsesToolDependencyDirMixin import logging log = logging.getLogger( __name__ ) -class GalaxyPackageDependencyResolver(DependencyResolver): +class GalaxyPackageDependencyResolver(DependencyResolver, UsesToolDependencyDirMixin): resolver_type = "galaxy_packages" def __init__(self, dependency_manager, **kwds): @@ -16,7 +17,7 @@ ## resolver that will just grab 'default' version of exact version ## unavailable. self.versionless = str(kwds.get('versionless', "false")).lower() == "true" - self.base_path = abspath( kwds.get('base_path', dependency_manager.default_base_path) ) + self._init_base_path( dependency_manager, **kwds ) def resolve( self, name, version, type, **kwds ): """ diff -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 -r 63d901ca0e6e9cfa405bae41f7088e3fa6bf243f lib/galaxy/tools/deps/resolvers/homebrew.py --- a/lib/galaxy/tools/deps/resolvers/homebrew.py +++ b/lib/galaxy/tools/deps/resolvers/homebrew.py @@ -12,20 +12,19 @@ incompatible changes coming. """ -import os -from ..brew_exts import DEFAULT_HOMEBREW_ROOT, recipe_cellar_path, build_env_statements -from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY, Dependency +from .resolver_mixins import UsesHomebrewMixin +from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY # TODO: Implement prefer version linked... PREFER_VERSION_LINKED = 'linked' PREFER_VERSION_LATEST = 'latest' -UNKNOWN_PREFER_VERSION_MESSAGE_TEMPLATE = "HomebrewDependencyResolver prefer_version must be latest %s" +UNKNOWN_PREFER_VERSION_MESSAGE_TEMPLATE = "HomebrewDependencyResolver prefer_version must be %s" UNKNOWN_PREFER_VERSION_MESSAGE = UNKNOWN_PREFER_VERSION_MESSAGE_TEMPLATE % (PREFER_VERSION_LATEST) DEFAULT_PREFER_VERSION = PREFER_VERSION_LATEST -class HomebrewDependencyResolver(DependencyResolver): +class HomebrewDependencyResolver(DependencyResolver, UsesHomebrewMixin): resolver_type = "homebrew" def __init__(self, dependency_manager, **kwds): @@ -38,11 +37,7 @@ if self.versionless and self.prefer_version not in [PREFER_VERSION_LATEST]: raise Exception(UNKNOWN_PREFER_VERSION_MESSAGE) - cellar_root = kwds.get('cellar', None) - if cellar_root is None: - cellar_root = os.path.join(DEFAULT_HOMEBREW_ROOT, "Cellar") - - self.cellar_root = cellar_root + self._init_homebrew(**kwds) def resolve(self, name, version, type, **kwds): if type != "package": @@ -53,41 +48,6 @@ else: return self._find_dep_versioned(name, version) - def _find_dep_versioned(self, name, version): - recipe_path = recipe_cellar_path(self.cellar_root, name, version) - if not os.path.exists(recipe_path) or not os.path.isdir(recipe_path): - return INDETERMINATE_DEPENDENCY - - commands = build_env_statements(self.cellar_root, recipe_path, relaxed=True) - return HomebrewDependency(commands) - - def _find_dep_default(self, name, version): - installed_versions = self._installed_versions(name) - if not installed_versions: - return INDETERMINATE_DEPENDENCY - - # Just grab newest installed version - may make sense some day to find - # the linked version instead. - default_version = sorted(installed_versions, reverse=True)[0] - return self._find_dep_versioned(name, default_version) - - def _installed_versions(self, recipe): - recipe_base_path = os.path.join(self.cellar_root, recipe) - if not os.path.exists(recipe_base_path): - return [] - - names = os.listdir(recipe_base_path) - return filter(lambda n: os.path.isdir(os.path.join(recipe_base_path, n)), names) - - -class HomebrewDependency(Dependency): - - def __init__(self, commands): - self.commands = commands - - def shell_commands(self, requirement): - return self.commands.replace("\n", ";") + "\n" - def _string_as_bool( value ): return str( value ).lower() == "true" diff -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 -r 63d901ca0e6e9cfa405bae41f7088e3fa6bf243f lib/galaxy/tools/deps/resolvers/resolver_mixins.py --- /dev/null +++ b/lib/galaxy/tools/deps/resolvers/resolver_mixins.py @@ -0,0 +1,73 @@ +import os +from ..brew_exts import DEFAULT_HOMEBREW_ROOT, recipe_cellar_path, build_env_statements +from ..resolvers import INDETERMINATE_DEPENDENCY, Dependency + + +class UsesHomebrewMixin: + + def _init_homebrew(self, **kwds): + cellar_root = kwds.get('cellar', None) + if cellar_root is None: + cellar_root = os.path.join(DEFAULT_HOMEBREW_ROOT, "Cellar") + + self.cellar_root = cellar_root + + def _find_dep_versioned(self, name, version): + recipe_path = recipe_cellar_path(self.cellar_root, name, version) + if not os.path.exists(recipe_path) or not os.path.isdir(recipe_path): + return INDETERMINATE_DEPENDENCY + + commands = build_env_statements(self.cellar_root, recipe_path, relaxed=True) + return HomebrewDependency(commands) + + def _find_dep_default(self, name, version): + installed_versions = self._installed_versions(name) + if not installed_versions: + return INDETERMINATE_DEPENDENCY + + # Just grab newest installed version - may make sense some day to find + # the linked version instead. + default_version = sorted(installed_versions, reverse=True)[0] + return self._find_dep_versioned(name, default_version) + + def _installed_versions(self, recipe): + recipe_base_path = os.path.join(self.cellar_root, recipe) + if not os.path.exists(recipe_base_path): + return [] + + names = os.listdir(recipe_base_path) + return filter(lambda n: os.path.isdir(os.path.join(recipe_base_path, n)), names) + + +class UsesToolDependencyDirMixin: + + def _init_base_path(self, dependency_manager, **kwds): + self.base_path = os.path.abspath( kwds.get('base_path', dependency_manager.default_base_path) ) + + +class UsesInstalledRepositoriesMixin: + + def _get_installed_dependency( self, name, type, version=None, **kwds ): + installed_tool_dependencies = kwds.get("installed_tool_dependencies", []) + for installed_tool_dependency in (installed_tool_dependencies or []): + name_and_type_equal = installed_tool_dependency.name == name and installed_tool_dependency.type == type + if version: + if name_and_type_equal and installed_tool_dependency.version == version: + return installed_tool_dependency + else: + if name_and_type_equal: + return installed_tool_dependency + return None + + +class HomebrewDependency(Dependency): + + def __init__(self, commands): + self.commands = commands + + def shell_commands(self, requirement): + raw_commands = self.commands.replace("\n", ";") + return raw_commands + + def __repr__(self): + return "PlatformBrewDependency[commands=%s]" % self.commands diff -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 -r 63d901ca0e6e9cfa405bae41f7088e3fa6bf243f lib/galaxy/tools/deps/resolvers/tool_shed_packages.py --- a/lib/galaxy/tools/deps/resolvers/tool_shed_packages.py +++ b/lib/galaxy/tools/deps/resolvers/tool_shed_packages.py @@ -1,10 +1,11 @@ from os.path import abspath, join, exists +from .resolver_mixins import UsesInstalledRepositoriesMixin from .galaxy_packages import GalaxyPackageDependencyResolver, GalaxyPackageDependency from ..resolvers import INDETERMINATE_DEPENDENCY -class ToolShedPackageDependencyResolver(GalaxyPackageDependencyResolver): +class ToolShedPackageDependencyResolver(GalaxyPackageDependencyResolver, UsesInstalledRepositoriesMixin): resolver_type = "tool_shed_packages" def __init__(self, dependency_manager, **kwds): @@ -12,9 +13,8 @@ def _find_dep_versioned( self, name, version, type='package', **kwds ): installed_tool_dependency = self._get_installed_dependency( name, type, version=version, **kwds ) - base_path = self.base_path if installed_tool_dependency: - path = self._get_package_installed_dependency_path( installed_tool_dependency, base_path, name, version ) + path = self._get_package_installed_dependency_path( installed_tool_dependency, name, version ) return self._galaxy_package_dep(path, version) else: return INDETERMINATE_DEPENDENCY @@ -29,26 +29,17 @@ return GalaxyPackageDependency(dependency.script, dependency.path, None) return INDETERMINATE_DEPENDENCY - def _get_installed_dependency( self, name, type, version=None, **kwds ): - installed_tool_dependencies = kwds.get("installed_tool_dependencies", []) - for installed_tool_dependency in (installed_tool_dependencies or []): - name_and_type_equal = installed_tool_dependency.name == name and installed_tool_dependency.type == type - if version: - if name_and_type_equal and installed_tool_dependency.version == version: - return installed_tool_dependency - else: - if name_and_type_equal: - return installed_tool_dependency - return None - - def _get_package_installed_dependency_path( self, installed_tool_dependency, base_path, name, version ): + def _get_package_installed_dependency_path( self, installed_tool_dependency, name, version ): tool_shed_repository = installed_tool_dependency.tool_shed_repository - return join( base_path, - name, - version, - tool_shed_repository.owner, - tool_shed_repository.name, - tool_shed_repository.installed_changeset_revision ) + base_path = self.base_path + return join( + base_path, + name, + version, + tool_shed_repository.owner, + tool_shed_repository.name, + tool_shed_repository.installed_changeset_revision + ) def _get_set_environment_installed_dependency_script_path( self, installed_tool_dependency, name ): tool_shed_repository = installed_tool_dependency.tool_shed_repository @@ -64,4 +55,5 @@ return GalaxyPackageDependency(script, path, None) return INDETERMINATE_DEPENDENCY + __all__ = [ToolShedPackageDependencyResolver] diff -r 65cd7c0eee89e2d32a3369b1eb88742f23034560 -r 63d901ca0e6e9cfa405bae41f7088e3fa6bf243f lib/galaxy/util/plugin_config.py --- a/lib/galaxy/util/plugin_config.py +++ b/lib/galaxy/util/plugin_config.py @@ -24,7 +24,7 @@ for plugin_module in submodules( module ): # FIXME: this is not how one is suppose to use __all__ why did you do # this past John? - for clazz in plugin_module.__all__: + for clazz in getattr( plugin_module, "__all__", [] ): plugin_type = getattr( clazz, plugin_type_identifier, None ) if plugin_type: plugin_dict[ plugin_type ] = clazz 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.