Merge pull request #14810 from jmchilton/cleanup_ts_controllers_3

More tool shed dependency and dead code cleanup.
This commit is contained in:
Björn Grüning
2022-10-19 20:11:08 +02:00
committed by GitHub
11 changed files with 338 additions and 1327 deletions
@@ -0,0 +1,292 @@
import logging
import os
from galaxy import util
from galaxy.tool_shed.util import utility_container_manager
from galaxy.util.tool_shed.common_util import parse_repository_dependency_tuple
log = logging.getLogger(__name__)
class DependencyDisplayer:
def __init__(self, app):
self.app = app
def _add_installation_directories_to_tool_dependencies(self, tool_dependencies):
"""
Determine the path to the installation directory for each of the received
tool dependencies. This path will be displayed within the tool dependencies
container on the select_tool_panel_section or reselect_tool_panel_section
pages when installing or reinstalling repositories that contain tools with
the defined tool dependencies. The list of tool dependencies may be associated
with more than a single repository.
"""
for dependency_key, requirements_dict in tool_dependencies.items():
if dependency_key in ["set_environment"]:
continue
repository_name = requirements_dict.get("repository_name", "unknown")
repository_owner = requirements_dict.get("repository_owner", "unknown")
changeset_revision = requirements_dict.get("changeset_revision", "unknown")
dependency_name = requirements_dict["name"]
version = requirements_dict["version"]
if self.app.tool_dependency_dir:
root_dir = self.app.tool_dependency_dir
else:
root_dir = "<set your tool_dependency_dir in your Galaxy configuration file>"
install_dir = os.path.join(
root_dir, dependency_name, version, repository_owner, repository_name, changeset_revision
)
requirements_dict["install_dir"] = install_dir
tool_dependencies[dependency_key] = requirements_dict
return tool_dependencies
def generate_message_for_invalid_repository_dependencies(self, metadata_dict, error_from_tuple=False):
"""
Get or generate and return an error message associated with an invalid repository dependency.
"""
message = ""
if metadata_dict:
if error_from_tuple:
# Return the error messages associated with a set of one or more invalid repository
# dependency tuples.
invalid_repository_dependencies_dict = metadata_dict.get("invalid_repository_dependencies", None)
if invalid_repository_dependencies_dict is not None:
invalid_repository_dependencies = invalid_repository_dependencies_dict.get(
"invalid_repository_dependencies", []
)
for repository_dependency_tup in invalid_repository_dependencies:
(
toolshed,
name,
owner,
changeset_revision,
prior_installation_required,
only_if_compiling_contained_td,
error,
) = parse_repository_dependency_tuple(repository_dependency_tup, contains_error=True)
if error:
message += f"{error} "
else:
# The complete dependency hierarchy could not be determined for a repository being installed into
# Galaxy. This is likely due to invalid repository dependency definitions, so we'll get them from
# the metadata and parse them for display in an error message. This will hopefully communicate the
# problem to the user in such a way that a resolution can be determined.
message += (
"The complete dependency hierarchy could not be determined for this repository, so no required "
)
message += "repositories will not be installed. This is likely due to invalid repository dependency definitions. "
repository_dependencies_dict = metadata_dict.get("repository_dependencies", None)
if repository_dependencies_dict is not None:
rd_tups = repository_dependencies_dict.get("repository_dependencies", None)
if rd_tups is not None:
message += "Here are the attributes of the dependencies defined for this repository to help determine the "
message += "cause of this problem.<br/>"
message += '<table cellpadding="2" cellspacing="2">'
message += (
"<tr><th>Tool shed</th><th>Repository name</th><th>Owner</th><th>Changeset revision</th>"
)
message += "<th>Prior install required</th></tr>"
for rd_tup in rd_tups:
(
tool_shed,
name,
owner,
changeset_revision,
pir,
oicct,
) = parse_repository_dependency_tuple(rd_tup)
if util.asbool(pir):
pir_str = "True"
else:
pir_str = ""
message += f"<tr><td>{tool_shed}</td><td>{name}</td><td>{owner}</td><td>{changeset_revision}</td><td>{pir_str}</td></tr>"
message += "</table>"
return message
def generate_message_for_invalid_tool_dependencies(self, metadata_dict):
"""
Tool dependency definitions can only be invalid if they include a definition for a complex
repository dependency and the repository dependency definition is invalid. This method
retrieves the error message associated with the invalid tool dependency for display in the
caller.
"""
message = ""
if metadata_dict:
invalid_tool_dependencies = metadata_dict.get("invalid_tool_dependencies", None)
if invalid_tool_dependencies:
for requirement_dict in invalid_tool_dependencies.values():
error = requirement_dict.get("error", None)
if error:
message = f"{error} "
return message
def generate_message_for_orphan_tool_dependencies(self, repository, metadata_dict):
"""
The designation of a ToolDependency into the "orphan" category has evolved over time,
and is significantly restricted since the introduction of the TOOL_DEPENDENCY_DEFINITION
repository type. This designation is still critical, however, in that it handles the
case where a repository contains both tools and a tool_dependencies.xml file, but the
definition in the tool_dependencies.xml file is in no way related to anything defined
by any of the contained tool's requirements tag sets. This is important in that it is
often a result of a typo (e.g., dependency name or version) that differs between the tool
dependency definition within the tool_dependencies.xml file and what is defined in the
tool config's <requirements> tag sets. In these cases, the user should be presented with
a warning message, and this warning message is is in fact displayed if the following
is_orphan attribute is True. This is tricky because in some cases it may be intentional,
and tool dependencies that are categorized as "orphan" are in fact valid.
"""
has_orphan_package_dependencies = False
has_orphan_set_environment_dependencies = False
message = ""
package_orphans_str = ""
set_environment_orphans_str = ""
# Tool dependencies are categorized as orphan only if the repository contains tools.
if metadata_dict:
tools = metadata_dict.get("tools", [])
tool_dependencies = metadata_dict.get("tool_dependencies", {})
# The use of the orphan_tool_dependencies category in metadata has been deprecated,
# but we still need to check in case the metadata is out of date.
orphan_tool_dependencies = metadata_dict.get("orphan_tool_dependencies", {})
# Updating should cause no problems here since a tool dependency cannot be included
# in both dictionaries.
tool_dependencies.update(orphan_tool_dependencies)
if tool_dependencies and tools:
for td_key, requirements_dict in tool_dependencies.items():
if td_key == "set_environment":
# "set_environment": [{"name": "R_SCRIPT_PATH", "type": "set_environment"}]
for env_requirements_dict in requirements_dict:
name = env_requirements_dict["name"]
type = env_requirements_dict["type"]
if self._tool_dependency_is_orphan(type, name, None, tools):
if not has_orphan_set_environment_dependencies:
has_orphan_set_environment_dependencies = True
set_environment_orphans_str += f"<b>* name:</b> {name}, <b>type:</b> {type}<br/>"
else:
# "R/2.15.1": {"name": "R", "readme": "some string", "type": "package", "version": "2.15.1"}
name = requirements_dict["name"]
type = requirements_dict["type"]
version = requirements_dict["version"]
if self._tool_dependency_is_orphan(type, name, version, tools):
if not has_orphan_package_dependencies:
has_orphan_package_dependencies = True
package_orphans_str += (
f"<b>* name:</b> {name}, <b>type:</b> {type}, <b>version:</b> {version}<br/>"
)
if has_orphan_package_dependencies:
message += "The settings for <b>name</b>, <b>version</b> and <b>type</b> from a "
message += "contained tool configuration file's <b>requirement</b> tag does not match "
message += "the information for the following tool dependency definitions in the "
message += "<b>tool_dependencies.xml</b> file, so these tool dependencies have no "
message += "relationship with any tools within this repository.<br/>"
message += package_orphans_str
if has_orphan_set_environment_dependencies:
message += "The settings for <b>name</b> and <b>type</b> from a contained tool "
message += "configuration file's <b>requirement</b> tag does not match the information "
message += "for the following tool dependency definitions in the <b>tool_dependencies.xml</b> "
message += "file, so these tool dependencies have no relationship with any tools within "
message += "this repository.<br/>"
message += set_environment_orphans_str
return message
def populate_containers_dict_from_repository_metadata(self, repository):
"""
Retrieve necessary information from the received repository's metadata to populate the
containers_dict for display. This method is called only from Galaxy (not the tool shed)
when displaying repository dependencies for installed repositories and when displaying
them for uninstalled repositories that are being reinstalled.
"""
metadata = repository.metadata_
if metadata:
# Handle repository dependencies.
(
installed_repository_dependencies,
missing_repository_dependencies,
) = self.app.installed_repository_manager.get_installed_and_missing_repository_dependencies(repository)
# Handle the current repository's tool dependencies.
repository_tool_dependencies = metadata.get("tool_dependencies", None)
# Make sure to display missing tool dependencies as well.
repository_invalid_tool_dependencies = metadata.get("invalid_tool_dependencies", None)
if repository_invalid_tool_dependencies is not None:
if repository_tool_dependencies is None:
repository_tool_dependencies = {}
repository_tool_dependencies.update(repository_invalid_tool_dependencies)
gucm = GalaxyUtilityContainerManager(self.app)
containers_dict = gucm.build_repository_containers(
missing_repository_dependencies=missing_repository_dependencies,
repository_dependencies=installed_repository_dependencies,
)
else:
containers_dict = dict(
repository_dependencies=None,
)
return containers_dict
def _tool_dependency_is_orphan(self, type, name, version, tools):
"""
Determine if the combination of the received type, name and version is defined in the <requirement>
tag for at least one tool in the received list of tools. If not, the tool dependency defined by the
combination is considered an orphan in its repository in the tool shed.
"""
if type == "package":
if name and version:
for tool_dict in tools:
requirements = tool_dict.get("requirements", [])
for requirement_dict in requirements:
req_name = requirement_dict.get("name", None)
req_version = requirement_dict.get("version", None)
req_type = requirement_dict.get("type", None)
if req_name == name and req_version == version and req_type == type:
return False
elif type == "set_environment":
if name:
for tool_dict in tools:
requirements = tool_dict.get("requirements", [])
for requirement_dict in requirements:
req_name = requirement_dict.get("name", None)
req_type = requirement_dict.get("type", None)
if req_name == name and req_type == type:
return False
return True
class GalaxyUtilityContainerManager(utility_container_manager.UtilityContainerManager):
def __init__(self, app):
self.app = app
def build_repository_containers(
self,
missing_repository_dependencies,
repository_dependencies,
):
"""
Return a dictionary of containers for the received repository's dependencies and readme files for
display during installation to Galaxy.
"""
containers_dict = dict(
repository_dependencies=None,
missing_repository_dependencies=None,
)
# Some of the tool dependency folders will include links to display tool dependency information, and
# some of these links require the repository id. However we need to be careful because sometimes the
# repository object is None.
try:
folder_id = 0
# Installed repository dependencies container.
if repository_dependencies:
label = "Installed repository dependencies"
folder_id, repository_dependencies_root_folder = self.build_repository_dependencies_folder(
folder_id=folder_id, repository_dependencies=repository_dependencies, label=label, installed=True
)
containers_dict["repository_dependencies"] = repository_dependencies_root_folder
# Missing repository dependencies container.
if missing_repository_dependencies:
folder_id, missing_repository_dependencies_root_folder = self.build_repository_dependencies_folder(
folder_id=folder_id,
repository_dependencies=missing_repository_dependencies,
label="Missing repository dependencies",
installed=False,
)
containers_dict["missing_repository_dependencies"] = missing_repository_dependencies_root_folder
except Exception as e:
log.debug(f"Exception in build_repository_containers: {str(e)}")
return containers_dict
@@ -12,13 +12,19 @@ from galaxy.exceptions import ConfigDoesNotAllowException
from galaxy.tool_shed.galaxy_install import install_manager
from galaxy.tool_shed.galaxy_install.repository_dependencies import repository_dependency_manager
from galaxy.tool_shed.galaxy_install.tools import tool_panel_manager
from galaxy.tool_shed.util import tool_util
from galaxy.tool_shed.util import dependency_display
from galaxy.tool_shed.util.repository_util import (
create_or_update_tool_shed_repository,
get_absolute_path_to_file_in_repository,
get_installed_tool_shed_repository,
get_tool_shed_repository_by_id,
)
from galaxy.tool_shed.util.shed_util_common import (
clean_dependency_relationships,
get_ctx_rev,
get_tool_panel_config_tool_path_install_dir,
have_shed_tool_conf_for_install,
)
from galaxy.tool_util.deps import views
from galaxy.util import unicodify
from galaxy.util.tool_shed import (
@@ -26,9 +32,12 @@ from galaxy.util.tool_shed import (
encoding_util,
)
from galaxy.web.form_builder import CheckboxField
from tool_shed.galaxy_install import dependency_display
from tool_shed.util import shed_util_common as suc
from tool_shed.util.repository_util import create_repo_info_dict
from tool_shed.util.shed_util_common import (
get_repository_type_from_tool_shed,
get_requirements_from_repository,
get_tool_dependency_definition_metadata_from_tool_shed,
)
from tool_shed.util.web_util import escape
from .admin import AdminGalaxy
@@ -151,10 +160,8 @@ class AdminToolshed(AdminGalaxy):
repository = get_installed_tool_shed_repository(trans.app, repository_id)
if repository is None:
return trans.show_error_message("Invalid repository specified.")
tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(trans.app, str(repository.tool_shed))
description = kwd.get("description", repository.description)
status = "ok"
_, tool_path, _ = suc.get_tool_panel_config_tool_path_install_dir(trans.app, repository)
if repository.in_error_state:
message = "This repository is not installed correctly (see the <b>Repository installation error</b> below). Choose "
message += "<b>Reset to install</b> from the <b>Repository Actions</b> menu, correct problems if necessary and try "
@@ -171,14 +178,10 @@ class AdminToolshed(AdminGalaxy):
message = "The repository information has been updated."
dd = dependency_display.DependencyDisplayer(trans.app)
containers_dict = dd.populate_containers_dict_from_repository_metadata(
tool_shed_url=tool_shed_url,
tool_path=tool_path,
repository=repository,
reinstalling=False,
required_repo_info_dicts=None,
)
view = views.DependencyResolversView(self.app)
tool_requirements_d = suc.get_requirements_from_repository(repository)
tool_requirements_d = get_requirements_from_repository(repository)
requirements_status = view.get_requirements_status(tool_requirements_d, repository.installed_tool_dependencies)
management_dict = {
"status": status,
@@ -211,9 +214,9 @@ class AdminToolshed(AdminGalaxy):
install_repository_dependencies = CheckboxField.is_checked(kwd.get("install_repository_dependencies", ""))
install_tool_dependencies = CheckboxField.is_checked(kwd.get("install_tool_dependencies", ""))
install_resolver_dependencies = CheckboxField.is_checked(kwd.get("install_resolver_dependencies", ""))
if not suc.have_shed_tool_conf_for_install(trans.app):
if not have_shed_tool_conf_for_install(trans.app):
raise Exception("No valid shed tool configuration file available, please configure one")
shed_tool_conf, tool_path, relative_install_dir = suc.get_tool_panel_config_tool_path_install_dir(
shed_tool_conf, tool_path, relative_install_dir = get_tool_panel_config_tool_path_install_dir(
trans.app, tool_shed_repository
)
repository_clone_url = common_util.generate_clone_url_for_installed_repository(
@@ -245,19 +248,19 @@ class AdminToolshed(AdminGalaxy):
# Just in case the tool_section.id differs from tool_panel_section_id, which it shouldn't...
tool_panel_section_id = str(tool_section.id)
if tool_shed_repository.status == trans.install_model.ToolShedRepository.installation_status.UNINSTALLED:
repository_type = suc.get_repository_type_from_tool_shed(
repository_type = get_repository_type_from_tool_shed(
trans.app, tool_shed_url, tool_shed_repository.name, tool_shed_repository.owner
)
if repository_type == rt_util.TOOL_DEPENDENCY_DEFINITION:
# Repositories of type tool_dependency_definition must get the latest
# metadata from the Tool Shed since they have only a single installable
# revision.
raw_text = suc.get_tool_dependency_definition_metadata_from_tool_shed(
raw_text = get_tool_dependency_definition_metadata_from_tool_shed(
trans.app, tool_shed_url, tool_shed_repository.name, tool_shed_repository.owner
)
new_meta = json.loads(raw_text)
# Clean up old repository dependency and tool dependency relationships.
suc.clean_dependency_relationships(trans, new_meta, tool_shed_repository, tool_shed_url)
clean_dependency_relationships(trans, new_meta, tool_shed_repository, tool_shed_url)
# The repository's status must be updated from 'Uninstalled' to 'New' when initiating reinstall
# so the repository_installation_updater will function.
tool_shed_repository = create_or_update_tool_shed_repository(
@@ -273,7 +276,7 @@ class AdminToolshed(AdminGalaxy):
owner=tool_shed_repository.owner,
dist_to_shed=tool_shed_repository.dist_to_shed,
)
ctx_rev = suc.get_ctx_rev(
ctx_rev = get_ctx_rev(
trans.app,
tool_shed_url,
tool_shed_repository.name,
@@ -368,172 +371,3 @@ class AdminToolshed(AdminGalaxy):
except Exception:
log.exception("Problem you know in here somewhere...")
raise
@web.expose
@web.require_admin
@legacy_tool_shed_endpoint
def reselect_tool_panel_section(self, trans, **kwd):
"""
Select or change the tool panel section to contain the tools included in the tool shed repository
being reinstalled. If there are updates available for the repository in the tool shed, the
tool_dependencies and repository_dependencies associated with the updated changeset revision will
have been retrieved from the tool shed and passed in the received kwd. In this case, the stored
tool shed repository metadata from the Galaxy database will not be used since it is outdated.
"""
message = ""
status = "done"
repository_id = kwd.get("id", None)
latest_changeset_revision = kwd.get("latest_changeset_revision", None)
latest_ctx_rev = kwd.get("latest_ctx_rev", None)
tool_shed_repository = get_installed_tool_shed_repository(trans.app, repository_id)
repository_clone_url = common_util.generate_clone_url_for_installed_repository(trans.app, tool_shed_repository)
metadata = tool_shed_repository.metadata_
tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(
trans.app, str(tool_shed_repository.tool_shed)
)
tool_path = tool_shed_repository.get_tool_relative_path(trans.app)[0]
if latest_changeset_revision and latest_ctx_rev:
# There are updates available in the tool shed for the repository, so use the received
# dependency information which was retrieved from the tool shed.
encoded_updated_repo_info_dict = kwd.get("updated_repo_info_dict", None)
updated_repo_info_dict = encoding_util.tool_shed_decode(encoded_updated_repo_info_dict)
readme_files_dict = updated_repo_info_dict.get("readme_files_dict", None)
includes_data_managers = updated_repo_info_dict.get("includes_data_managers", False)
includes_tool_dependencies = updated_repo_info_dict.get("includes_tool_dependencies", False)
repo_info_dict = updated_repo_info_dict["repo_info_dict"]
else:
# There are no updates available from the tool shed for the repository, so use its locally stored metadata.
includes_data_managers = False
includes_tool_dependencies = False
readme_files_dict = None
tool_dependencies = None
if metadata:
if "data_manager" in metadata:
includes_data_managers = True
if "tool_dependencies" in metadata:
includes_tool_dependencies = True
# Since we're reinstalling, we need to send a request to the tool shed to get the README files.
params = dict(
name=tool_shed_repository.name,
owner=tool_shed_repository.owner,
changeset_revision=tool_shed_repository.installed_changeset_revision,
)
pathspec = ["repository", "get_readme_files"]
raw_text = util.url_get(
tool_shed_url,
auth=self.app.tool_shed_registry.url_auth(tool_shed_url),
pathspec=pathspec,
params=params,
)
readme_files_dict = json.loads(raw_text)
tool_dependencies = metadata.get("tool_dependencies", None)
rdim = repository_dependency_manager.RepositoryDependencyInstallManager(trans.app)
repository_dependencies = rdim.get_repository_dependencies_for_installed_tool_shed_repository(
trans.app, tool_shed_repository
)
repo_info_dict = create_repo_info_dict(
trans.app,
repository_clone_url=repository_clone_url,
changeset_revision=tool_shed_repository.installed_changeset_revision,
ctx_rev=tool_shed_repository.ctx_rev,
repository_owner=tool_shed_repository.owner,
repository_name=tool_shed_repository.name,
tool_dependencies=tool_dependencies,
repository_dependencies=repository_dependencies,
)
irm = trans.app.installed_repository_manager
dependencies_for_repository_dict = irm.get_dependencies_for_repository(
tool_shed_url, repo_info_dict, includes_tool_dependencies, updating=True
)
includes_tool_dependencies = dependencies_for_repository_dict.get("includes_tool_dependencies", False)
includes_tools = dependencies_for_repository_dict.get("includes_tools", False)
includes_tools_for_display_in_tool_panel = dependencies_for_repository_dict.get(
"includes_tools_for_display_in_tool_panel", False
)
installed_repository_dependencies = dependencies_for_repository_dict.get(
"installed_repository_dependencies", None
)
installed_tool_dependencies = dependencies_for_repository_dict.get("installed_tool_dependencies", None)
missing_repository_dependencies = dependencies_for_repository_dict.get("missing_repository_dependencies", None)
missing_tool_dependencies = dependencies_for_repository_dict.get("missing_tool_dependencies", None)
if installed_repository_dependencies or missing_repository_dependencies:
has_repository_dependencies = True
else:
has_repository_dependencies = False
if includes_tools_for_display_in_tool_panel:
# Get the location in the tool panel in which the tools were originally loaded.
if "tool_panel_section" in metadata:
tool_panel_dict = metadata["tool_panel_section"]
if tool_panel_dict:
if tool_util.panel_entry_per_tool(tool_panel_dict):
# The following forces everything to be loaded into 1 section (or no section) in the tool panel.
tool_section_dicts = tool_panel_dict[next(iter(tool_panel_dict.keys()))]
tool_section_dict = tool_section_dicts[0]
original_section_name = tool_section_dict["name"]
else:
original_section_name = tool_panel_dict["name"]
else:
original_section_name = ""
else:
original_section_name = ""
tool_panel_section_select_field = tool_util.build_tool_panel_section_select_field(trans.app)
no_changes_check_box = CheckboxField("no_changes", value=True)
if original_section_name:
message += (
"The tools contained in your <b>%s</b> repository were last loaded into the tool panel section <b>%s</b>. "
% (escape(tool_shed_repository.name), original_section_name)
)
message += "Uncheck the <b>No changes</b> check box and select a different tool panel section to load the tools in a "
message += "different section in the tool panel. "
status = "warning"
else:
message += f"The tools contained in your <b>{escape(tool_shed_repository.name)}</b> repository were last loaded into the tool panel outside of any sections. "
message += "Uncheck the <b>No changes</b> check box and select a tool panel section to load the tools into that section. "
status = "warning"
else:
no_changes_check_box = None
original_section_name = ""
tool_panel_section_select_field = None
shed_tool_conf_select_field = tool_util.build_shed_tool_conf_select_field(trans.app)
dd = dependency_display.DependencyDisplayer(trans.app)
containers_dict = dd.populate_containers_dict_for_new_install(
tool_shed_url=tool_shed_url,
tool_path=tool_path,
readme_files_dict=readme_files_dict,
installed_repository_dependencies=installed_repository_dependencies,
missing_repository_dependencies=missing_repository_dependencies,
installed_tool_dependencies=installed_tool_dependencies,
missing_tool_dependencies=missing_tool_dependencies,
updating=False,
)
# Since we're reinstalling we'll merge the list of missing repository dependencies into the list of
# installed repository dependencies since each displayed repository dependency will display a status,
# whether installed or missing.
containers_dict = dd.merge_missing_repository_dependencies_to_installed_container(containers_dict)
# Handle repository dependencies check box.
install_repository_dependencies_check_box = CheckboxField("install_repository_dependencies", value=True)
view = views.DependencyResolversView(self.app)
if view.installable_resolvers:
install_resolver_dependencies_check_box = CheckboxField("install_resolver_dependencies", value=True)
else:
install_resolver_dependencies_check_box = None
return trans.fill_template(
"/admin/tool_shed_repository/reselect_tool_panel_section.mako",
repository=tool_shed_repository,
no_changes_check_box=no_changes_check_box,
original_section_name=original_section_name,
includes_data_managers=includes_data_managers,
includes_tools=includes_tools,
includes_tools_for_display_in_tool_panel=includes_tools_for_display_in_tool_panel,
includes_tool_dependencies=includes_tool_dependencies,
has_repository_dependencies=has_repository_dependencies,
install_repository_dependencies_check_box=install_repository_dependencies_check_box,
install_resolver_dependencies_check_box=install_resolver_dependencies_check_box,
containers_dict=containers_dict,
tool_panel_section_select_field=tool_panel_section_select_field,
shed_tool_conf_select_field=shed_tool_conf_select_field,
encoded_repo_info_dict=encoding_util.tool_shed_encode(repo_info_dict),
repo_info_dict=repo_info_dict,
message=message,
status=status,
)
@@ -1,686 +0,0 @@
import json
import logging
import os
import threading
from galaxy import util
from tool_shed.galaxy_install.utility_containers import GalaxyUtilityContainerManager
from tool_shed.util import (
common_util,
container_util,
readme_util,
repository_util,
tool_dependency_util,
)
from tool_shed.utility_containers import utility_container_manager
log = logging.getLogger(__name__)
class DependencyDisplayer:
def __init__(self, app):
self.app = app
def add_installation_directories_to_tool_dependencies(self, tool_dependencies):
"""
Determine the path to the installation directory for each of the received
tool dependencies. This path will be displayed within the tool dependencies
container on the select_tool_panel_section or reselect_tool_panel_section
pages when installing or reinstalling repositories that contain tools with
the defined tool dependencies. The list of tool dependencies may be associated
with more than a single repository.
"""
for dependency_key, requirements_dict in tool_dependencies.items():
if dependency_key in ["set_environment"]:
continue
repository_name = requirements_dict.get("repository_name", "unknown")
repository_owner = requirements_dict.get("repository_owner", "unknown")
changeset_revision = requirements_dict.get("changeset_revision", "unknown")
dependency_name = requirements_dict["name"]
version = requirements_dict["version"]
if self.app.tool_dependency_dir:
root_dir = self.app.tool_dependency_dir
else:
root_dir = "<set your tool_dependency_dir in your Galaxy configuration file>"
install_dir = os.path.join(
root_dir, dependency_name, version, repository_owner, repository_name, changeset_revision
)
requirements_dict["install_dir"] = install_dir
tool_dependencies[dependency_key] = requirements_dict
return tool_dependencies
def generate_message_for_invalid_repository_dependencies(self, metadata_dict, error_from_tuple=False):
"""
Get or generate and return an error message associated with an invalid repository dependency.
"""
message = ""
if metadata_dict:
if error_from_tuple:
# Return the error messages associated with a set of one or more invalid repository
# dependency tuples.
invalid_repository_dependencies_dict = metadata_dict.get("invalid_repository_dependencies", None)
if invalid_repository_dependencies_dict is not None:
invalid_repository_dependencies = invalid_repository_dependencies_dict.get(
"invalid_repository_dependencies", []
)
for repository_dependency_tup in invalid_repository_dependencies:
(
toolshed,
name,
owner,
changeset_revision,
prior_installation_required,
only_if_compiling_contained_td,
error,
) = common_util.parse_repository_dependency_tuple(
repository_dependency_tup, contains_error=True
)
if error:
message += f"{error} "
else:
# The complete dependency hierarchy could not be determined for a repository being installed into
# Galaxy. This is likely due to invalid repository dependency definitions, so we'll get them from
# the metadata and parse them for display in an error message. This will hopefully communicate the
# problem to the user in such a way that a resolution can be determined.
message += (
"The complete dependency hierarchy could not be determined for this repository, so no required "
)
message += "repositories will not be installed. This is likely due to invalid repository dependency definitions. "
repository_dependencies_dict = metadata_dict.get("repository_dependencies", None)
if repository_dependencies_dict is not None:
rd_tups = repository_dependencies_dict.get("repository_dependencies", None)
if rd_tups is not None:
message += "Here are the attributes of the dependencies defined for this repository to help determine the "
message += "cause of this problem.<br/>"
message += '<table cellpadding="2" cellspacing="2">'
message += (
"<tr><th>Tool shed</th><th>Repository name</th><th>Owner</th><th>Changeset revision</th>"
)
message += "<th>Prior install required</th></tr>"
for rd_tup in rd_tups:
(
tool_shed,
name,
owner,
changeset_revision,
pir,
oicct,
) = common_util.parse_repository_dependency_tuple(rd_tup)
if util.asbool(pir):
pir_str = "True"
else:
pir_str = ""
message += f"<tr><td>{tool_shed}</td><td>{name}</td><td>{owner}</td><td>{changeset_revision}</td><td>{pir_str}</td></tr>"
message += "</table>"
return message
def generate_message_for_invalid_tool_dependencies(self, metadata_dict):
"""
Tool dependency definitions can only be invalid if they include a definition for a complex
repository dependency and the repository dependency definition is invalid. This method
retrieves the error message associated with the invalid tool dependency for display in the
caller.
"""
message = ""
if metadata_dict:
invalid_tool_dependencies = metadata_dict.get("invalid_tool_dependencies", None)
if invalid_tool_dependencies:
for requirement_dict in invalid_tool_dependencies.values():
error = requirement_dict.get("error", None)
if error:
message = f"{error} "
return message
def generate_message_for_orphan_tool_dependencies(self, repository, metadata_dict):
"""
The designation of a ToolDependency into the "orphan" category has evolved over time,
and is significantly restricted since the introduction of the TOOL_DEPENDENCY_DEFINITION
repository type. This designation is still critical, however, in that it handles the
case where a repository contains both tools and a tool_dependencies.xml file, but the
definition in the tool_dependencies.xml file is in no way related to anything defined
by any of the contained tool's requirements tag sets. This is important in that it is
often a result of a typo (e.g., dependency name or version) that differs between the tool
dependency definition within the tool_dependencies.xml file and what is defined in the
tool config's <requirements> tag sets. In these cases, the user should be presented with
a warning message, and this warning message is is in fact displayed if the following
is_orphan attribute is True. This is tricky because in some cases it may be intentional,
and tool dependencies that are categorized as "orphan" are in fact valid.
"""
has_orphan_package_dependencies = False
has_orphan_set_environment_dependencies = False
message = ""
package_orphans_str = ""
set_environment_orphans_str = ""
# Tool dependencies are categorized as orphan only if the repository contains tools.
if metadata_dict:
tools = metadata_dict.get("tools", [])
tool_dependencies = metadata_dict.get("tool_dependencies", {})
# The use of the orphan_tool_dependencies category in metadata has been deprecated,
# but we still need to check in case the metadata is out of date.
orphan_tool_dependencies = metadata_dict.get("orphan_tool_dependencies", {})
# Updating should cause no problems here since a tool dependency cannot be included
# in both dictionaries.
tool_dependencies.update(orphan_tool_dependencies)
if tool_dependencies and tools:
for td_key, requirements_dict in tool_dependencies.items():
if td_key == "set_environment":
# "set_environment": [{"name": "R_SCRIPT_PATH", "type": "set_environment"}]
for env_requirements_dict in requirements_dict:
name = env_requirements_dict["name"]
type = env_requirements_dict["type"]
if self.tool_dependency_is_orphan(type, name, None, tools):
if not has_orphan_set_environment_dependencies:
has_orphan_set_environment_dependencies = True
set_environment_orphans_str += f"<b>* name:</b> {name}, <b>type:</b> {type}<br/>"
else:
# "R/2.15.1": {"name": "R", "readme": "some string", "type": "package", "version": "2.15.1"}
name = requirements_dict["name"]
type = requirements_dict["type"]
version = requirements_dict["version"]
if self.tool_dependency_is_orphan(type, name, version, tools):
if not has_orphan_package_dependencies:
has_orphan_package_dependencies = True
package_orphans_str += (
f"<b>* name:</b> {name}, <b>type:</b> {type}, <b>version:</b> {version}<br/>"
)
if has_orphan_package_dependencies:
message += "The settings for <b>name</b>, <b>version</b> and <b>type</b> from a "
message += "contained tool configuration file's <b>requirement</b> tag does not match "
message += "the information for the following tool dependency definitions in the "
message += "<b>tool_dependencies.xml</b> file, so these tool dependencies have no "
message += "relationship with any tools within this repository.<br/>"
message += package_orphans_str
if has_orphan_set_environment_dependencies:
message += "The settings for <b>name</b> and <b>type</b> from a contained tool "
message += "configuration file's <b>requirement</b> tag does not match the information "
message += "for the following tool dependency definitions in the <b>tool_dependencies.xml</b> "
message += "file, so these tool dependencies have no relationship with any tools within "
message += "this repository.<br/>"
message += set_environment_orphans_str
return message
def get_installed_and_missing_tool_dependencies_for_installed_repository(self, repository, all_tool_dependencies):
"""
Return the lists of installed tool dependencies and missing tool dependencies for a Tool Shed
repository that has been installed into Galaxy.
"""
if all_tool_dependencies:
tool_dependencies = {}
missing_tool_dependencies = {}
for td_key, val in all_tool_dependencies.items():
if td_key in ["set_environment"]:
for index, td_info_dict in enumerate(val):
name = td_info_dict["name"]
version = None
type = td_info_dict["type"]
tool_dependency = tool_dependency_util.get_tool_dependency_by_name_type_repository(
self.app, repository, name, type
)
if tool_dependency:
td_info_dict["repository_id"] = repository.id
td_info_dict["tool_dependency_id"] = tool_dependency.id
if tool_dependency.status:
tool_dependency_status = str(tool_dependency.status)
else:
tool_dependency_status = "Never installed"
td_info_dict["status"] = tool_dependency_status
val[index] = td_info_dict
if (
tool_dependency.status
== self.app.install_model.ToolDependency.installation_status.INSTALLED
):
tool_dependencies[td_key] = val
else:
missing_tool_dependencies[td_key] = val
else:
name = val["name"]
version = val["version"]
type = val["type"]
tool_dependency = tool_dependency_util.get_tool_dependency_by_name_version_type_repository(
self.app, repository, name, version, type
)
if tool_dependency:
val["repository_id"] = repository.id
val["tool_dependency_id"] = tool_dependency.id
if tool_dependency.status:
tool_dependency_status = str(tool_dependency.status)
else:
tool_dependency_status = "Never installed"
val["status"] = tool_dependency_status
if (
tool_dependency.status
== self.app.install_model.ToolDependency.installation_status.INSTALLED
):
tool_dependencies[td_key] = val
else:
missing_tool_dependencies[td_key] = val
else:
tool_dependencies = None
missing_tool_dependencies = None
return tool_dependencies, missing_tool_dependencies
def merge_containers_dicts_for_new_install(self, containers_dicts):
"""
When installing one or more tool shed repositories for the first time, the received list of
containers_dicts contains a containers_dict for each repository being installed. Since the
repositories are being installed for the first time, all entries are None except the repository
dependencies and tool dependencies. The entries for missing dependencies are all None since
they have previously been merged into the installed dependencies. This method will merge the
dependencies entries into a single container and return it for display.
"""
new_containers_dict = dict(
readme_files=None,
datatypes=None,
missing_repository_dependencies=None,
repository_dependencies=None,
missing_tool_dependencies=None,
tool_dependencies=None,
invalid_tools=None,
valid_tools=None,
workflows=None,
)
if containers_dicts:
lock = threading.Lock()
lock.acquire(True)
try:
repository_dependencies_root_folder = None
tool_dependencies_root_folder = None
# Use a unique folder id (hopefully the following is).
folder_id = 867
for old_container_dict in containers_dicts:
# Merge repository_dependencies.
old_container_repository_dependencies_root = old_container_dict["repository_dependencies"]
if old_container_repository_dependencies_root:
if repository_dependencies_root_folder is None:
repository_dependencies_root_folder = utility_container_manager.Folder(
id=folder_id, key="root", label="root", parent=None
)
folder_id += 1
repository_dependencies_folder = utility_container_manager.Folder(
id=folder_id,
key="merged",
label="Repository dependencies",
parent=repository_dependencies_root_folder,
)
folder_id += 1
# The old_container_repository_dependencies_root will be a root folder containing a single sub_folder.
old_container_repository_dependencies_folder = (
old_container_repository_dependencies_root.folders[0]
)
# Change the folder id so it won't confict with others being merged.
old_container_repository_dependencies_folder.id = folder_id
folder_id += 1
repository_components_tuple = container_util.get_components_from_key(
old_container_repository_dependencies_folder.key
)
components_list = repository_util.extract_components_from_tuple(repository_components_tuple)
name = components_list[1]
# Generate the label by retrieving the repository name.
old_container_repository_dependencies_folder.label = str(name)
repository_dependencies_folder.folders.append(old_container_repository_dependencies_folder)
# Merge tool_dependencies.
old_container_tool_dependencies_root = old_container_dict["tool_dependencies"]
if old_container_tool_dependencies_root:
if tool_dependencies_root_folder is None:
tool_dependencies_root_folder = utility_container_manager.Folder(
id=folder_id, key="root", label="root", parent=None
)
folder_id += 1
tool_dependencies_folder = utility_container_manager.Folder(
id=folder_id,
key="merged",
label="Tool dependencies",
parent=tool_dependencies_root_folder,
)
folder_id += 1
else:
td_list = [td.listify for td in tool_dependencies_folder.tool_dependencies]
# The old_container_tool_dependencies_root will be a root folder containing a single sub_folder.
old_container_tool_dependencies_folder = old_container_tool_dependencies_root.folders[0]
for td in old_container_tool_dependencies_folder.tool_dependencies:
if td.listify not in td_list:
tool_dependencies_folder.tool_dependencies.append(td)
if repository_dependencies_root_folder:
repository_dependencies_root_folder.folders.append(repository_dependencies_folder)
new_containers_dict["repository_dependencies"] = repository_dependencies_root_folder
if tool_dependencies_root_folder:
tool_dependencies_root_folder.folders.append(tool_dependencies_folder)
new_containers_dict["tool_dependencies"] = tool_dependencies_root_folder
except Exception as e:
log.debug(f"Exception in merge_containers_dicts_for_new_install: {e}")
finally:
lock.release()
return new_containers_dict
def merge_missing_repository_dependencies_to_installed_container(self, containers_dict):
"""
Merge the list of missing repository dependencies into the list of installed
repository dependencies.
"""
missing_rd_container_root = containers_dict.get("missing_repository_dependencies", None)
if missing_rd_container_root:
# The missing_rd_container_root will be a root folder containing a single sub_folder.
missing_rd_container = missing_rd_container_root.folders[0]
installed_rd_container_root = containers_dict.get("repository_dependencies", None)
# The installed_rd_container_root will be a root folder containing a single sub_folder.
if installed_rd_container_root:
installed_rd_container = installed_rd_container_root.folders[0]
installed_rd_container.label = "Repository dependencies"
for index, rd in enumerate(missing_rd_container.repository_dependencies):
# Skip the header row.
if index == 0:
continue
installed_rd_container.repository_dependencies.append(rd)
installed_rd_container_root.folders = [installed_rd_container]
containers_dict["repository_dependencies"] = installed_rd_container_root
else:
# Change the folder label from 'Missing repository dependencies' to be
# 'Repository dependencies' for display.
root_container = containers_dict["missing_repository_dependencies"]
for sub_container in root_container.folders:
# There should only be 1 sub-folder.
sub_container.label = "Repository dependencies"
containers_dict["repository_dependencies"] = root_container
containers_dict["missing_repository_dependencies"] = None
return containers_dict
def merge_missing_tool_dependencies_to_installed_container(self, containers_dict):
"""
Merge the list of missing tool dependencies into the list of installed tool
dependencies.
"""
missing_td_container_root = containers_dict.get("missing_tool_dependencies", None)
if missing_td_container_root:
# The missing_td_container_root will be a root folder containing a single sub_folder.
missing_td_container = missing_td_container_root.folders[0]
installed_td_container_root = containers_dict.get("tool_dependencies", None)
# The installed_td_container_root will be a root folder containing a single sub_folder.
if installed_td_container_root:
installed_td_container = installed_td_container_root.folders[0]
installed_td_container.label = "Tool dependencies"
for index, td in enumerate(missing_td_container.tool_dependencies):
# Skip the header row.
if index == 0:
continue
installed_td_container.tool_dependencies.append(td)
installed_td_container_root.folders = [installed_td_container]
containers_dict["tool_dependencies"] = installed_td_container_root
else:
# Change the folder label from 'Missing tool dependencies' to be
# 'Tool dependencies' for display.
root_container = containers_dict["missing_tool_dependencies"]
for sub_container in root_container.folders:
# There should only be 1 subfolder.
sub_container.label = "Tool dependencies"
containers_dict["tool_dependencies"] = root_container
containers_dict["missing_tool_dependencies"] = None
return containers_dict
def populate_containers_dict_for_new_install(
self,
tool_shed_url,
tool_path,
readme_files_dict,
installed_repository_dependencies,
missing_repository_dependencies,
installed_tool_dependencies,
missing_tool_dependencies,
updating=False,
):
"""
Return the populated containers for a repository being installed for the first time
or for an installed repository that is being updated and the updates include newly
defined repository (and possibly tool) dependencies.
"""
installed_tool_dependencies, missing_tool_dependencies = self.populate_tool_dependencies_dicts(
tool_shed_url=tool_shed_url,
tool_path=tool_path,
repository_installed_tool_dependencies=installed_tool_dependencies,
repository_missing_tool_dependencies=missing_tool_dependencies,
required_repo_info_dicts=None,
)
# Most of the repository contents are set to None since we don't yet know what they are.
gucm = GalaxyUtilityContainerManager(self.app)
containers_dict = gucm.build_repository_containers(
repository=None,
invalid_tools=None,
missing_repository_dependencies=missing_repository_dependencies,
missing_tool_dependencies=missing_tool_dependencies,
readme_files_dict=readme_files_dict,
repository_dependencies=installed_repository_dependencies,
tool_dependencies=installed_tool_dependencies,
valid_tools=None,
valid_data_managers=None,
invalid_data_managers=None,
data_managers_errors=None,
new_install=True,
reinstalling=False,
)
if not updating:
# If we installing a new repository and not updaing an installed repository, we can merge
# the missing_repository_dependencies container contents to the installed_repository_dependencies
# container. When updating an installed repository, merging will result in losing newly defined
# dependencies included in the updates.
containers_dict = self.merge_missing_repository_dependencies_to_installed_container(containers_dict)
# Merge the missing_tool_dependencies container contents to the installed_tool_dependencies container.
containers_dict = self.merge_missing_tool_dependencies_to_installed_container(containers_dict)
return containers_dict
def populate_containers_dict_from_repository_metadata(
self, tool_shed_url, tool_path, repository, reinstalling=False, required_repo_info_dicts=None
):
"""
Retrieve necessary information from the received repository's metadata to populate the
containers_dict for display. This method is called only from Galaxy (not the tool shed)
when displaying repository dependencies for installed repositories and when displaying
them for uninstalled repositories that are being reinstalled.
"""
metadata = repository.metadata_
if metadata:
# Handle invalid tools.
invalid_tools = metadata.get("invalid_tools", None)
# Handle README files.
if repository.has_readme_files:
if reinstalling or repository.status not in [
self.app.install_model.ToolShedRepository.installation_status.DEACTIVATED,
self.app.install_model.ToolShedRepository.installation_status.INSTALLED,
]:
# Since we're reinstalling, we need to send a request to the tool shed to get the README files.
tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(self.app, tool_shed_url)
params = dict(
name=str(repository.name),
owner=str(repository.owner),
changeset_revision=str(repository.installed_changeset_revision),
)
pathspec = ["repository", "get_readme_files"]
raw_text = util.url_get(
tool_shed_url,
auth=self.app.tool_shed_registry.url_auth(tool_shed_url),
pathspec=pathspec,
params=params,
)
readme_files_dict = json.loads(raw_text)
else:
readme_files_dict = readme_util.build_readme_files_dict(
self.app, repository, repository.changeset_revision, repository.metadata_, tool_path
)
else:
readme_files_dict = None
# Handle repository dependencies.
(
installed_repository_dependencies,
missing_repository_dependencies,
) = self.app.installed_repository_manager.get_installed_and_missing_repository_dependencies(repository)
# Handle the current repository's tool dependencies.
repository_tool_dependencies = metadata.get("tool_dependencies", None)
# Make sure to display missing tool dependencies as well.
repository_invalid_tool_dependencies = metadata.get("invalid_tool_dependencies", None)
if repository_invalid_tool_dependencies is not None:
if repository_tool_dependencies is None:
repository_tool_dependencies = {}
repository_tool_dependencies.update(repository_invalid_tool_dependencies)
(
repository_installed_tool_dependencies,
repository_missing_tool_dependencies,
) = self.get_installed_and_missing_tool_dependencies_for_installed_repository(
repository, repository_tool_dependencies
)
if reinstalling:
installed_tool_dependencies, missing_tool_dependencies = self.populate_tool_dependencies_dicts(
tool_shed_url,
tool_path,
repository_installed_tool_dependencies,
repository_missing_tool_dependencies,
required_repo_info_dicts,
)
else:
installed_tool_dependencies = repository_installed_tool_dependencies
missing_tool_dependencies = repository_missing_tool_dependencies
# Handle valid tools.
valid_tools = metadata.get("tools", None)
# Handle Data Managers
valid_data_managers = None
invalid_data_managers = None
data_managers_errors = None
if "data_manager" in metadata:
valid_data_managers = metadata["data_manager"].get("data_managers", None)
invalid_data_managers = metadata["data_manager"].get("invalid_data_managers", None)
data_managers_errors = metadata["data_manager"].get("messages", None)
gucm = GalaxyUtilityContainerManager(self.app)
containers_dict = gucm.build_repository_containers(
repository=repository,
invalid_tools=invalid_tools,
missing_repository_dependencies=missing_repository_dependencies,
missing_tool_dependencies=missing_tool_dependencies,
readme_files_dict=readme_files_dict,
repository_dependencies=installed_repository_dependencies,
tool_dependencies=installed_tool_dependencies,
valid_tools=valid_tools,
valid_data_managers=valid_data_managers,
invalid_data_managers=invalid_data_managers,
data_managers_errors=data_managers_errors,
new_install=False,
reinstalling=reinstalling,
)
else:
containers_dict = dict(
datatypes=None,
invalid_tools=None,
readme_files_dict=None,
repository_dependencies=None,
tool_dependencies=None,
valid_tools=None,
workflows=None,
)
return containers_dict
def populate_tool_dependencies_dicts(
self,
tool_shed_url,
tool_path,
repository_installed_tool_dependencies,
repository_missing_tool_dependencies,
required_repo_info_dicts,
):
"""
Return the populated installed_tool_dependencies and missing_tool_dependencies dictionaries
for all repositories defined by entries in the received required_repo_info_dicts.
"""
installed_tool_dependencies = None
missing_tool_dependencies = None
if repository_installed_tool_dependencies is None:
repository_installed_tool_dependencies = {}
else:
# Add the install_dir attribute to the tool_dependencies.
repository_installed_tool_dependencies = self.add_installation_directories_to_tool_dependencies(
repository_installed_tool_dependencies
)
if repository_missing_tool_dependencies is None:
repository_missing_tool_dependencies = {}
else:
# Add the install_dir attribute to the tool_dependencies.
repository_missing_tool_dependencies = self.add_installation_directories_to_tool_dependencies(
repository_missing_tool_dependencies
)
if required_repo_info_dicts:
# Handle the tool dependencies defined for each of the repository's repository dependencies.
for rid in required_repo_info_dicts:
for name, repo_info_tuple in rid.items():
(
description,
repository_clone_url,
changeset_revision,
ctx_rev,
repository_owner,
repository_dependencies,
tool_dependencies,
) = repository_util.get_repo_info_tuple_contents(repo_info_tuple)
if tool_dependencies:
# Add the install_dir attribute to the tool_dependencies.
tool_dependencies = self.add_installation_directories_to_tool_dependencies(tool_dependencies)
# The required_repository may have been installed with a different changeset revision.
(
required_repository,
installed_changeset_revision,
) = repository_util.repository_was_previously_installed(
self.app, tool_shed_url, name, repo_info_tuple, from_tip=False
)
if required_repository:
(
required_repository_installed_tool_dependencies,
required_repository_missing_tool_dependencies,
) = self.get_installed_and_missing_tool_dependencies_for_installed_repository(
required_repository, tool_dependencies
)
if required_repository_installed_tool_dependencies:
# Add the install_dir attribute to the tool_dependencies.
required_repository_installed_tool_dependencies = (
self.add_installation_directories_to_tool_dependencies(
required_repository_installed_tool_dependencies
)
)
for td_key, td_dict in required_repository_installed_tool_dependencies.items():
if td_key not in repository_installed_tool_dependencies:
repository_installed_tool_dependencies[td_key] = td_dict
if required_repository_missing_tool_dependencies:
# Add the install_dir attribute to the tool_dependencies.
required_repository_missing_tool_dependencies = (
self.add_installation_directories_to_tool_dependencies(
required_repository_missing_tool_dependencies
)
)
for td_key, td_dict in required_repository_missing_tool_dependencies.items():
if td_key not in repository_missing_tool_dependencies:
repository_missing_tool_dependencies[td_key] = td_dict
if repository_installed_tool_dependencies:
installed_tool_dependencies = repository_installed_tool_dependencies
if repository_missing_tool_dependencies:
missing_tool_dependencies = repository_missing_tool_dependencies
return installed_tool_dependencies, missing_tool_dependencies
def tool_dependency_is_orphan(self, type, name, version, tools):
"""
Determine if the combination of the received type, name and version is defined in the <requirement>
tag for at least one tool in the received list of tools. If not, the tool dependency defined by the
combination is considered an orphan in its repository in the tool shed.
"""
if type == "package":
if name and version:
for tool_dict in tools:
requirements = tool_dict.get("requirements", [])
for requirement_dict in requirements:
req_name = requirement_dict.get("name", None)
req_version = requirement_dict.get("version", None)
req_type = requirement_dict.get("type", None)
if req_name == name and req_version == version and req_type == type:
return False
elif type == "set_environment":
if name:
for tool_dict in tools:
requirements = tool_dict.get("requirements", [])
for requirement_dict in requirements:
req_name = requirement_dict.get("name", None)
req_type = requirement_dict.get("type", None)
if req_name == name and req_type == type:
return False
return True
@@ -1,138 +0,0 @@
import logging
import threading
from tool_shed.utility_containers import utility_container_manager
log = logging.getLogger(__name__)
class GalaxyUtilityContainerManager(utility_container_manager.UtilityContainerManager):
def __init__(self, app):
self.app = app
def build_repository_containers(
self,
repository,
invalid_tools,
missing_repository_dependencies,
missing_tool_dependencies,
readme_files_dict,
repository_dependencies,
tool_dependencies,
valid_tools,
valid_data_managers,
invalid_data_managers,
data_managers_errors,
new_install=False,
reinstalling=False,
):
"""
Return a dictionary of containers for the received repository's dependencies and readme files for
display during installation to Galaxy.
"""
containers_dict = dict(
datatypes=None,
invalid_tools=None,
missing_tool_dependencies=None,
readme_files=None,
repository_dependencies=None,
missing_repository_dependencies=None,
tool_dependencies=None,
valid_tools=None,
workflows=None,
valid_data_managers=None,
invalid_data_managers=None,
)
# Some of the tool dependency folders will include links to display tool dependency information, and
# some of these links require the repository id. However we need to be careful because sometimes the
# repository object is None.
if repository:
changeset_revision = repository.changeset_revision
else:
changeset_revision = None
lock = threading.Lock()
lock.acquire(True)
try:
folder_id = 0
# Invalid tools container.
if invalid_tools:
folder_id, invalid_tools_root_folder = self.build_invalid_tools_folder(
folder_id, invalid_tools, changeset_revision, repository=repository, label="Invalid tools"
)
containers_dict["invalid_tools"] = invalid_tools_root_folder
# Readme files container.
if readme_files_dict:
folder_id, readme_files_root_folder = self.build_readme_files_folder(folder_id, readme_files_dict)
containers_dict["readme_files"] = readme_files_root_folder
# Installed repository dependencies container.
if repository_dependencies:
if new_install:
label = "Repository dependencies"
else:
label = "Installed repository dependencies"
folder_id, repository_dependencies_root_folder = self.build_repository_dependencies_folder(
folder_id=folder_id, repository_dependencies=repository_dependencies, label=label, installed=True
)
containers_dict["repository_dependencies"] = repository_dependencies_root_folder
# Missing repository dependencies container.
if missing_repository_dependencies:
folder_id, missing_repository_dependencies_root_folder = self.build_repository_dependencies_folder(
folder_id=folder_id,
repository_dependencies=missing_repository_dependencies,
label="Missing repository dependencies",
installed=False,
)
containers_dict["missing_repository_dependencies"] = missing_repository_dependencies_root_folder
# Installed tool dependencies container.
if tool_dependencies:
if new_install:
label = "Tool dependencies"
else:
label = "Installed tool dependencies"
# We only want to display the Status column if the tool_dependency is missing.
folder_id, tool_dependencies_root_folder = self.build_tool_dependencies_folder(
folder_id,
tool_dependencies,
label=label,
missing=False,
new_install=new_install,
reinstalling=reinstalling,
)
containers_dict["tool_dependencies"] = tool_dependencies_root_folder
# Missing tool dependencies container.
if missing_tool_dependencies:
# We only want to display the Status column if the tool_dependency is missing.
folder_id, missing_tool_dependencies_root_folder = self.build_tool_dependencies_folder(
folder_id,
missing_tool_dependencies,
label="Missing tool dependencies",
missing=True,
new_install=new_install,
reinstalling=reinstalling,
)
containers_dict["missing_tool_dependencies"] = missing_tool_dependencies_root_folder
# Valid tools container.
if valid_tools:
folder_id, valid_tools_root_folder = self.build_tools_folder(
folder_id, valid_tools, repository, changeset_revision, label="Valid tools"
)
containers_dict["valid_tools"] = valid_tools_root_folder
# Workflows container.
if valid_data_managers:
folder_id, valid_data_managers_root_folder = self.build_data_managers_folder(
folder_id=folder_id, data_managers=valid_data_managers, label="Valid Data Managers"
)
containers_dict["valid_data_managers"] = valid_data_managers_root_folder
if invalid_data_managers or data_managers_errors:
folder_id, invalid_data_managers_root_folder = self.build_invalid_data_managers_folder(
folder_id=folder_id,
data_managers=invalid_data_managers,
error_messages=data_managers_errors,
label="Invalid Data Managers",
)
containers_dict["invalid_data_managers"] = invalid_data_managers_root_folder
except Exception as e:
log.debug(f"Exception in build_repository_containers: {str(e)}")
finally:
lock.release()
return containers_dict
+15 -16
View File
@@ -4,20 +4,21 @@ import os
from mako.template import Template
import tool_shed.util.shed_util_common as suc
from galaxy import web
from galaxy.tool_shed.util import basic_util
from galaxy.tool_shed.util.hg_util import (
get_changectx_for_changeset,
get_file_context_from_ctx,
)
from galaxy.tool_shed.util.repository_util import get_repo_info_tuple_contents
from galaxy.tool_shed.util.shed_util_common import set_image_paths
from galaxy.util import (
rst_to_html,
unicodify,
url_get,
)
from tool_shed.util import (
basic_util,
common_util,
hg_util,
metadata_util,
repository_util,
)
from galaxy.util.tool_shed.common_util import get_tool_shed_url_from_tool_shed_registry
from tool_shed.util.metadata_util import get_latest_downloadable_changeset_revision
log = logging.getLogger(__name__)
@@ -31,9 +32,7 @@ def build_readme_files_dict(app, repository, changeset_revision, metadata, tool_
if app.name == "galaxy":
can_use_disk_files = True
else:
latest_downloadable_changeset_revision = metadata_util.get_latest_downloadable_changeset_revision(
app, repository
)
latest_downloadable_changeset_revision = get_latest_downloadable_changeset_revision(app, repository)
can_use_disk_files = changeset_revision == latest_downloadable_changeset_revision
readme_files_dict = {}
if metadata:
@@ -59,7 +58,7 @@ def build_readme_files_dict(app, repository, changeset_revision, metadata, tool_
if text_of_reasonable_length.find(".. image:: ") >= 0:
# Handle image display for README files that are contained in repositories in the tool shed or installed into Galaxy.
try:
text_of_reasonable_length = suc.set_image_paths(
text_of_reasonable_length = set_image_paths(
app,
text_of_reasonable_length,
encoded_repository_id=app.security.encode_id(repository.id),
@@ -85,9 +84,9 @@ def build_readme_files_dict(app, repository, changeset_revision, metadata, tool_
else:
# We must be in the tool shed and have an old changeset_revision, so we need to retrieve the file contents from the repository manifest.
repo = repository.hg_repo
ctx = hg_util.get_changectx_for_changeset(repo, changeset_revision)
ctx = get_changectx_for_changeset(repo, changeset_revision)
if ctx:
fctx = hg_util.get_file_context_from_ctx(ctx, readme_file_name)
fctx = get_file_context_from_ctx(ctx, readme_file_name)
if fctx and fctx not in ["DELETED"]:
try:
text = unicodify(fctx.data())
@@ -115,9 +114,9 @@ def get_readme_files_dict_for_display(app, tool_shed_url, repo_info_dict):
repository_owner,
repository_dependencies,
installed_td,
) = repository_util.get_repo_info_tuple_contents(repo_info_tuple)
) = get_repo_info_tuple_contents(repo_info_tuple)
# Handle changing HTTP protocols over time.
tool_shed_url = common_util.get_tool_shed_url_from_tool_shed_registry(app, tool_shed_url)
tool_shed_url = get_tool_shed_url_from_tool_shed_registry(app, tool_shed_url)
params = dict(name=name, owner=repository_owner, changeset_revision=changeset_revision)
pathspec = ["repository", "get_readme_files"]
raw_text = url_get(
+10 -11
View File
@@ -2,12 +2,13 @@ import logging
import threading
from galaxy import util
from tool_shed.util import (
common_util,
container_util,
readme_util,
from galaxy.tool_shed.util import utility_container_manager
from galaxy.tool_shed.util.container_util import (
generate_repository_dependencies_key_for_repository,
STRSEP,
)
from . import utility_container_manager
from galaxy.util.tool_shed.common_util import parse_repository_dependency_tuple
from tool_shed.util.readme_util import build_readme_files_dict
log = logging.getLogger(__name__)
@@ -166,8 +167,8 @@ class ToolShedUtilityContainerManager(utility_container_manager.UtilityContainer
prior_installation_required,
only_if_compiling_contained_td,
error,
) = common_util.parse_repository_dependency_tuple(invalid_repository_dependency, contains_error=True)
key = container_util.generate_repository_dependencies_key_for_repository(
) = parse_repository_dependency_tuple(invalid_repository_dependency, contains_error=True)
key = generate_repository_dependencies_key_for_repository(
toolshed,
name,
owner,
@@ -300,9 +301,7 @@ class ToolShedUtilityContainerManager(utility_container_manager.UtilityContainer
# Readme files container.
if metadata:
if "readme_files" not in exclude and "readme_files" in metadata:
readme_files_dict = readme_util.build_readme_files_dict(
self.app, repository, changeset_revision, metadata
)
readme_files_dict = build_readme_files_dict(self.app, repository, changeset_revision, metadata)
folder_id, readme_files_root_folder = self.build_readme_files_folder(
folder_id, readme_files_dict
)
@@ -363,4 +362,4 @@ class ToolShedUtilityContainerManager(utility_container_manager.UtilityContainer
return containers_dict
def generate_tool_dependencies_key(self, name, version, type):
return f"{str(name)}{container_util.STRSEP}{str(version)}{container_util.STRSEP}{str(type)}"
return f"{str(name)}{STRSEP}{str(version)}{STRSEP}{str(type)}"
@@ -24,6 +24,7 @@ from galaxy import (
util,
web,
)
from galaxy.tool_shed.util import dependency_display
from galaxy.tools.repositories import ValidationContext
from galaxy.web.form_builder import (
CheckboxField,
@@ -32,7 +33,6 @@ from galaxy.web.form_builder import (
from galaxy.web.legacy_framework import grids
from galaxy.webapps.base.controller import BaseUIController
from tool_shed.dependencies.repository import relation_builder
from tool_shed.galaxy_install import dependency_display
from tool_shed.metadata import repository_metadata_manager
from tool_shed.tools import (
tool_validator,
+1 -1
View File
@@ -10,10 +10,10 @@ from galaxy import (
util,
web,
)
from galaxy.tool_shed.util import dependency_display
from galaxy.util import checkers
from galaxy.webapps.base.controller import BaseUIController
from tool_shed.dependencies import attribute_handlers
from tool_shed.galaxy_install import dependency_display
from tool_shed.metadata import repository_metadata_manager
from tool_shed.repository_types import util as rt_util
from tool_shed.tools.data_table_manager import ShedToolDataTableManager
@@ -1,101 +0,0 @@
<%inherit file="/base.mako"/>
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/admin/tool_shed_repository/common.mako" import="render_dependencies_section" />
<%namespace file="/admin/tool_shed_repository/common.mako" import="render_readme_section" />
<%namespace file="/webapps/tool_shed/repository/common.mako" import="*" />
<%namespace file="/webapps/tool_shed/common/common.mako" import="*" />
<%def name="stylesheets()">
${parent.stylesheets()}
${h.css( "library" )}
</%def>
<%def name="javascripts()">
${parent.javascripts()}
${container_javascripts()}
</%def>
%if message:
${render_msg( message, status )}
%endif
<div class="card">
<div class="card-body">
<form name="reselect_tool_panel_section" id="reselect_tool_panel_section" action="${h.url_for( controller='admin_toolshed', action='reinstall_repository', id=trans.security.encode_id( repository.id ) )}" method="post" >
<div class="form-row">
<input type="hidden" name="repo_info_dict" value="${encoded_repo_info_dict}" />
</div>
<div style="clear: both"></div>
<% readme_files_dict = containers_dict.get( 'readme_files', None ) %>
%if readme_files_dict:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Repository README files - may contain important installation or license information</th>
</table>
</div>
${render_readme_section( containers_dict )}
<div style="clear: both"></div>
%endif
%if has_repository_dependencies or includes_tool_dependencies:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Confirm dependency installation</th>
</table>
</div>
${render_dependencies_section( install_resolver_dependencies_check_box, install_repository_dependencies_check_box, containers_dict, revision_label=None, export=False )}
%endif
%if shed_tool_conf_select_field:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Choose the configuration file whose tool_path setting will be used for installing repositories</th>
</table>
</div>
<%
if len( shed_tool_conf_select_field.options ) == 1:
select_help = "Your Galaxy instance is configured with 1 shed-related tool configuration file, so repositories will be "
select_help += "installed using its <b>tool_path</b> setting."
else:
select_help = "Your Galaxy instance is configured with %d shed-related tool configuration files, " % len( shed_tool_conf_select_field.options )
select_help += "so select the file whose <b>tool_path</b> setting you want used for installing repositories."
%>
<div class="form-row">
<label>Shed tool configuration file:</label>
${render_select(shed_tool_conf_select_field)}
<div class="toolParamHelp" style="clear: both;">
${select_help|h}
</div>
</div>
<div style="clear: both"></div>
%else:
<input type="hidden" name="shed_tool_conf" value="${shed_tool_conf|h}"/>
%endif
%if includes_tools_for_display_in_tool_panel:
<div style="clear: both"></div>
<div class="form-row">
${render_checkbox(no_changes_check_box)}
<label style="display: inline;">No changes</label>
<div class="toolParamHelp" style="clear: both;">
Uncheck and select a different tool panel section to load the tools into a different section in the tool panel.
</div>
</div>
<div class="form-row">
<label>Add new tool panel section:</label>
<input name="new_tool_panel_section_label" type="textfield" value="" size="40"/>
<div class="toolParamHelp" style="clear: both;">
Add a new tool panel section to contain the installed tools (optional).
</div>
</div>
<div class="form-row">
<label>Select existing tool panel section:</label>
${render_select(tool_panel_section_select_field)}
<div class="toolParamHelp" style="clear: both;">
Choose an existing section in your tool panel to contain the installed tools (optional).
</div>
</div>
%endif
<div class="form-row">
<input type="submit" name="select_tool_panel_section_button" value="Install"/>
</div>
</form>
</div>
</div>
@@ -1,188 +0,0 @@
<%inherit file="/base.mako"/>
<%namespace file="/message.mako" import="render_msg" />
<%namespace file="/admin/tool_shed_repository/common.mako" import="render_dependencies_section" />
<%namespace file="/admin/tool_shed_repository/common.mako" import="render_readme_section" />
<%namespace file="/webapps/tool_shed/repository/common.mako" import="*" />
<%namespace file="/webapps/tool_shed/common/common.mako" import="*" />
<%def name="stylesheets()">
${parent.stylesheets()}
${h.css( "library" )}
</%def>
<%def name="javascripts()">
${parent.javascripts()}
${container_javascripts()}
</%def>
<%
# Handle the case where an uninstalled repository encountered errors during the process of being reinstalled. In
# this case, the repository metadata is an empty dictionary, but one or both of has_repository_dependencies
# and includes_tool_dependencies may be True. If either of these are True but we have no metadata, we cannot install
# repository dependencies on this pass.
if has_repository_dependencies:
repository_dependencies = containers_dict[ 'repository_dependencies' ]
missing_repository_dependencies = containers_dict[ 'missing_repository_dependencies' ]
if repository_dependencies or missing_repository_dependencies:
can_display_repository_dependencies = True
else:
can_display_repository_dependencies = False
else:
can_display_repository_dependencies = False
if includes_tool_dependencies:
tool_dependencies = containers_dict[ 'tool_dependencies' ]
missing_tool_dependencies = containers_dict[ 'missing_tool_dependencies' ]
if tool_dependencies or missing_tool_dependencies:
can_display_tool_dependencies = True
else:
can_display_tool_dependencies = False
else:
can_display_tool_dependencies = False
can_display_resolver_installation = install_resolver_dependencies_check_box is not None
%>
%if message:
${render_msg( message, status )}
%endif
<div class="warningmessage">
<p>
The Galaxy development team does not maintain the contents of many Galaxy Tool Shed repositories. Some
repository tools may include code that produces malicious behavior, so be aware of what you are installing.
</p>
<p>
If you discover a repository that causes problems after installation, contact <a href="https://galaxyproject.org/support" target="_blank">Galaxy support</a>,
sending all necessary information, and appropriate action will be taken.
</p>
<p>
<a href="https://galaxyproject.org/toolshed/repository-features/#contact-repository-owner" target="_blank">Contact the repository owner</a> for
general questions or concerns.
</p>
</div>
<div class="card">
<div class="card-body">
<form name="select_tool_panel_section" id="select_tool_panel_section" action="${h.url_for( controller='admin_toolshed', action='prepare_for_install' )}" method="post" >
<div class="form-row">
<input type="hidden" name="includes_tools" value="${includes_tools}" />
<input type="hidden" name="includes_tool_dependencies" value="${includes_tool_dependencies}" />
<input type="hidden" name="requirements_status" value="${requirements_status}" />
<input type="hidden" name="includes_tools_for_display_in_tool_panel" value="${includes_tools_for_display_in_tool_panel}" />
<input type="hidden" name="tool_shed_url" value="${tool_shed_url}" />
<input type="hidden" name="encoded_repo_info_dicts" value="${encoded_repo_info_dicts}" />
<input type="hidden" name="updating" value="${updating}" />
<input type="hidden" name="updating_repository_id" value="${updating_repository_id}" />
<input type="hidden" name="updating_to_ctx_rev" value="${updating_to_ctx_rev}" />
<input type="hidden" name="updating_to_changeset_revision" value="${updating_to_changeset_revision}" />
<input type="hidden" name="encoded_updated_metadata" value="${encoded_updated_metadata}" />
</div>
<div style="clear: both"></div>
<% readme_files_dict = containers_dict.get( 'readme_files', None ) %>
%if readme_files_dict:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Repository README file - may contain important installation or license information</th>
</table>
</div>
${render_readme_section( containers_dict )}
<div style="clear: both"></div>
%endif
<%
if requirements_status and install_resolver_dependencies_check_box or includes_tool_dependencies:
display_dependency_confirmation = True
else:
display_dependency_confirmation = False
%>
%if requirements_status:
%if not install_resolver_dependencies_check_box and not includes_tool_dependencies:
<div class="form-row">
<table class="colored" width="100%">
<head>
<th>
<img src="${h.url_for('/static')}/images/icon_error_sml.gif" title='Cannot install dependencies'/>
This repository requires dependencies that cannot be installed through the Tool Shed
</th>
</head>
</table>
</div>
<div class="form-row">
<p>This repository defines tool requirements that cannot be installed through the Tool Shed.</p>
<p>Please activate Conda dependency resolution, activate Docker dependency resolution, setup Environment Modules
or manually satisfy the dependencies listed below.</p>
<p>For details see <a target="_blank" href="https://docs.galaxyproject.org/en/master/admin/dependency_resolvers.html">the dependency resolver documentation.</a></p>
</div>
%endif
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">The following tool dependencies are required by the current repository</th>
</table>
</div>
<div class="form-row">
${render_tool_dependency_resolver( requirements_status, prepare_for_install=True )}
</div>
<div style="clear: both"></div>
%endif
%if can_display_repository_dependencies or display_dependency_confirmation:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Confirm dependency installation</th>
</table>
</div>
${render_dependencies_section( install_resolver_dependencies_check_box, install_repository_dependencies_check_box, containers_dict, revision_label=None, export=False, requirements_status=requirements_status )}
<div style="clear: both"></div>
%endif
%if shed_tool_conf_select_field:
%if includes_tools_for_display_in_tool_panel:
<div class="form-row">
<table class="colored" width="100%">
<th bgcolor="#EBD9B2">Choose the tool panel section to contain the installed tools (optional)</th>
</table>
</div>
<div class="detail-section">
%endif
<%
if len( shed_tool_conf_select_field.options ) == 1:
select_help = "Your Galaxy instance is configured with 1 shed-related tool configuration file, so repositories will be "
select_help += "installed using its <b>tool_path</b> setting."
else:
select_help = "Your Galaxy instance is configured with %d shed-related tool configuration files, " % len( shed_tool_conf_select_field.options )
select_help += "so select the file whose <b>tool_path</b> setting you want used for installing repositories."
%>
<div class="form-row">
<label>Shed tool configuration file:</label>
${render_select(shed_tool_conf_select_field)}
<div class="toolParamHelp" style="clear: both;">
${select_help|h}
</div>
</div>
<div style="clear: both"></div>
</div>
%else:
<input type="hidden" name="shed_tool_conf" value="${shed_tool_conf|h}"/>
%endif
%if includes_tools_for_display_in_tool_panel:
<div class="form-row">
<label>Add new tool panel section:</label>
<input name="new_tool_panel_section_label" type="textfield" value="${new_tool_panel_section_label|h}" size="40"/>
<div class="toolParamHelp" style="clear: both;">
Add a new tool panel section to contain the installed tools (optional).
</div>
</div>
<div class="form-row">
<label>Select existing tool panel section:</label>
${render_select(tool_panel_section_select_field)}
<div class="toolParamHelp" style="clear: both;">
Choose an existing section in your tool panel to contain the installed tools (optional).
</div>
</div>
%endif
<div class="form-row">
<input type="submit" name="select_tool_panel_section_button" value="Install"/>
<div class="toolParamHelp" style="clear: both;">
%if includes_tools_for_display_in_tool_panel:
Clicking <b>Install</b> without selecting a tool panel section will load the installed tools into the tool panel outside of any sections.
%endif
</div>
</div>
</form>
</div>
</div>