Warning

This document is for an in-development version of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.

Source code for tool_shed.util.metadata_util

import logging
from operator import itemgetter
from typing import (
    TYPE_CHECKING,
)

from sqlalchemy import select

from galaxy.tool_shed.util.hg_util import (
    INITIAL_CHANGELOG_HASH,
    reversed_lower_upper_bounded_changelog,
)
from tool_shed.util.hg_util import changeset2rev
from tool_shed.webapp.model import (
    Repository,
    RepositoryMetadata,
)
from tool_shed.webapp.model.db import get_repository_by_name_and_owner

if TYPE_CHECKING:
    from tool_shed.structured_app import ToolShedApp
    from tool_shed.webapp.model.mapping import ToolShedModelMapping


log = logging.getLogger(__name__)


[docs] def build_invalid_tools(metadata: dict) -> list: """Transform raw invalid_tools metadata into a list of dicts with tool_config and error_message.""" raw_invalid_tools = metadata.get("invalid_tools", []) invalid_tool_errors = metadata.get("invalid_tool_errors", {}) invalid_tools = [] for item in raw_invalid_tools: if isinstance(item, str): invalid_tools.append( { "tool_config": item, "error_message": invalid_tool_errors.get(item, ""), } ) elif isinstance(item, dict): invalid_tools.append(item) return invalid_tools
[docs] def get_all_dependencies(app: "ToolShedApp", metadata_entry, processed_dependency_links=None): processed_dependency_links = processed_dependency_links or [] encoder = app.security.encode_id value_mapper = {"repository_id": encoder, "id": encoder, "user_id": encoder} metadata = metadata_entry.to_dict(value_mapper=value_mapper, view="element") returned_dependencies = [] required_metadata = get_dependencies_for_metadata_revision(app, metadata) if required_metadata is None: return metadata for dependency_metadata in required_metadata: dependency_dict = dependency_metadata.to_dict(value_mapper=value_mapper, view="element") dependency_link = (metadata["id"], dependency_dict["id"]) if dependency_link in processed_dependency_links: continue processed_dependency_links.append(dependency_link) repository = app.model.session.get(Repository, app.security.decode_id(dependency_dict["repository_id"])) assert repository dependency_dict["repository"] = repository.to_dict(value_mapper=value_mapper) if dependency_metadata.includes_tools: dependency_dict["tools"] = dependency_metadata.metadata["tools"] dependency_dict["invalid_tools"] = build_invalid_tools(dependency_metadata.metadata) dependency_dict["repository_dependencies"] = [] if dependency_dict["includes_tool_dependencies"]: dependency_dict["tool_dependencies"] = repository.get_tool_dependencies( app, dependency_dict["changeset_revision"] ) if dependency_dict["has_repository_dependencies"]: dependency_dict["repository_dependencies"] = get_all_dependencies( app, dependency_metadata, processed_dependency_links ) else: dependency_dict["repository_dependencies"] = [] returned_dependencies.append(dependency_dict) return returned_dependencies
[docs] def get_current_repository_metadata_for_changeset_revision( app: "ToolShedApp", repository: Repository, changeset_revision: str ) -> RepositoryMetadata | None: encoded_repository_id = app.security.encode_id(repository.id) repository_metadata = get_repository_metadata_by_changeset_revision(app, encoded_repository_id, changeset_revision) if repository_metadata: return repository_metadata # The installable changeset_revision may have been changed because it was "moved ahead" # in the repository changelog. updated_changeset_revision = get_next_downloadable_changeset_revision( app, repository, after_changeset_revision=changeset_revision ) if updated_changeset_revision and updated_changeset_revision != changeset_revision: repository_metadata = get_repository_metadata_by_changeset_revision( app, encoded_repository_id, updated_changeset_revision ) if repository_metadata: return repository_metadata return None
[docs] def get_dependencies_for_metadata_revision(app: "ToolShedApp", metadata): dependencies = [] for _shed, name, owner, changeset, _prior, _ in metadata["repository_dependencies"]: required_repository = get_repository_by_name_and_owner(app.model.context, name, owner) updated_changeset = get_next_downloadable_changeset_revision(app, required_repository, changeset) if updated_changeset is None: continue metadata_entry = get_repository_metadata_by_changeset_revision( app, app.security.encode_id(required_repository.id), updated_changeset ) dependencies.append(metadata_entry) return dependencies
[docs] def get_latest_changeset_revision(app, repository): repository_tip = repository.tip() repository_metadata = get_repository_metadata_by_changeset_revision( app, app.security.encode_id(repository.id), repository_tip ) if repository_metadata and repository_metadata.downloadable: return repository_tip changeset_revisions = [revision[1] for revision in get_metadata_revisions(app, repository)] if changeset_revisions: return changeset_revisions[-1] return INITIAL_CHANGELOG_HASH
[docs] def get_latest_downloadable_changeset_revision(app: "ToolShedApp", repository: Repository) -> str: repository_tip = repository.tip() repository_metadata = get_repository_metadata_by_changeset_revision( app, app.security.encode_id(repository.id), repository_tip ) if repository_metadata and repository_metadata.downloadable: return repository_tip changeset_revisions = [revision[1] for revision in get_metadata_revisions(app, repository)] if changeset_revisions: return changeset_revisions[-1] return INITIAL_CHANGELOG_HASH
[docs] def get_latest_repository_metadata(app: "ToolShedApp", decoded_repository_id, downloadable: bool = False): """Get last metadata defined for a specified repository from the database.""" sa_session = app.model.session repository = sa_session.get(Repository, decoded_repository_id) assert repository if downloadable: changeset_revision = get_latest_downloadable_changeset_revision(app, repository) else: changeset_revision = get_latest_changeset_revision(app, repository) return get_repository_metadata_by_changeset_revision(app, app.security.encode_id(repository.id), changeset_revision)
[docs] def get_metadata_revisions( app: "ToolShedApp", repository: Repository, sort_revisions: bool = True, reverse: bool = False, downloadable: bool = True, ) -> list[tuple[int, str]]: """ Return a list of changesets for the provided repository. """ sa_session = app.model.session if downloadable: metadata_revisions = repository.downloadable_revisions else: metadata_revisions = repository.metadata_revisions changeset_tups = [] for repository_metadata in metadata_revisions: assert repository_metadata.changeset_revision is not None if repository_metadata.numeric_revision == -1 or repository_metadata.numeric_revision is None: try: rev = changeset2rev(repository.hg_repo, repository_metadata.changeset_revision) repository_metadata.numeric_revision = rev sa_session.add(repository_metadata) session = sa_session() session.commit() except Exception: rev = -1 else: rev = repository_metadata.numeric_revision changeset_tups.append((rev, repository_metadata.changeset_revision)) if sort_revisions: changeset_tups.sort(key=itemgetter(0), reverse=reverse) return changeset_tups
[docs] def get_next_downloadable_changeset_revision( app: "ToolShedApp", repository: Repository, after_changeset_revision: str ) -> str | None: """ Return the installable changeset_revision in the repository changelog after the changeset to which after_changeset_revision refers. If there isn't one, return None. If there is only one installable changeset, and that matches the requested revision, return it. """ changeset_revisions = [revision[1] for revision in get_metadata_revisions(app, repository)] if len(changeset_revisions) == 1: changeset_revision = changeset_revisions[0] if changeset_revision == after_changeset_revision: return after_changeset_revision found_after_changeset_revision = False repo = repository.hg_repo for changeset in repo.changelog: changeset_revision = str(repo[changeset]) if found_after_changeset_revision: if changeset_revision in changeset_revisions: return changeset_revision elif changeset_revision == after_changeset_revision: # We've found the changeset in the changelog for which we need to get the next downloadable changeset. found_after_changeset_revision = True return None
[docs] def get_previous_metadata_changeset_revision( app: "ToolShedApp", repository: Repository, before_changeset_revision: str ) -> str | None: """ Return the changeset_revision in the repository changelog that has associated metadata prior to the changeset to which before_changeset_revision refers. If there isn't one, return the hash value of an empty repository changelog, INITIAL_CHANGELOG_HASH. """ changeset_revisions = [revision[1] for revision in get_metadata_revisions(app, repository)] if len(changeset_revisions) == 1: changeset_revision = changeset_revisions[0] if changeset_revision == before_changeset_revision: return INITIAL_CHANGELOG_HASH return changeset_revision previous_changeset_revision = None for changeset_revision in changeset_revisions: if changeset_revision == before_changeset_revision: if previous_changeset_revision: return previous_changeset_revision else: # Return the hash value of an empty repository changelog - note that this will not be a valid changeset revision. return INITIAL_CHANGELOG_HASH else: previous_changeset_revision = changeset_revision return None
[docs] def get_repository_metadata_by_changeset_revision( app: "ToolShedApp", id: str, changeset_revision: str ) -> RepositoryMetadata | None: """Get metadata for a specified repository change set from the database.""" decoded_id = app.security.decode_id(id) return repository_metadata_by_changeset_revision(app.model, decoded_id, changeset_revision)
[docs] def repository_metadata_by_changeset_revision( model_mapping: "ToolShedModelMapping", id: int, changeset_revision: str ) -> RepositoryMetadata | None: # Make sure there are no duplicate records, and return the single unique record for the changeset_revision. # Duplicate records were somehow created in the past. The cause of this issue has been resolved, but we'll # leave this method as is for a while longer to ensure all duplicate records are removed. sa_session = model_mapping.context all_metadata_records = get_metadata_by_changeset(sa_session, id, changeset_revision, RepositoryMetadata) if len(all_metadata_records) > 1: # Delete all records older than the last one updated. for repository_metadata in all_metadata_records[1:]: sa_session.delete(repository_metadata) session = sa_session() session.commit() return all_metadata_records[0] elif all_metadata_records: return all_metadata_records[0] return None
[docs] def get_repository_metadata_by_repository_id_changeset_revision(app, id, changeset_revision, metadata_only=False): """Get a specified metadata record for a specified repository in the tool shed.""" if metadata_only: repository_metadata = get_repository_metadata_by_changeset_revision(app, id, changeset_revision) if repository_metadata and repository_metadata.metadata: return repository_metadata.metadata return None return get_repository_metadata_by_changeset_revision(app, id, changeset_revision)
[docs] def get_updated_changeset_revisions(app: "ToolShedApp", name: str, owner: str, changeset_revision: str) -> str: """ Return a string of comma-separated changeset revision hashes for all available updates to the received changeset revision for the repository defined by the received name and owner. """ repository = get_repository_by_name_and_owner(app.model.context, name, owner) # Get the upper bound changeset revision. upper_bound_changeset_revision = get_next_downloadable_changeset_revision(app, repository, changeset_revision) # Build the list of changeset revision hashes defining each available update up to, but excluding # upper_bound_changeset_revision. repo = repository.hg_repo changeset_hashes = [] for changeset in reversed_lower_upper_bounded_changelog(repo, changeset_revision, upper_bound_changeset_revision): # Make sure to exclude upper_bound_changeset_revision. if changeset != upper_bound_changeset_revision: changeset_hashes.append(str(repo[changeset])) if changeset_hashes: changeset_hashes_str = ",".join(changeset_hashes) return changeset_hashes_str return ""
[docs] def is_downloadable(metadata_dict): # NOTE: although repository README files are considered Galaxy utilities, they have no # effect on determining if a revision is installable. See the comments in the # compare_readme_files() method. if "datatypes" in metadata_dict: # We have proprietary datatypes. return True if "repository_dependencies" in metadata_dict: # We have repository_dependencies. return True if "tools" in metadata_dict: # We have tools. return True if "tool_dependencies" in metadata_dict: # We have tool dependencies, and perhaps only tool dependencies! return True if "workflows" in metadata_dict: # We have exported workflows. return True return False
[docs] def get_metadata_by_changeset(session, repository_id, changeset_revision, repository_metadata_model): stmt = ( select(repository_metadata_model) .where(repository_metadata_model.repository_id == repository_id) .where(repository_metadata_model.changeset_revision == changeset_revision) ) return session.scalars(stmt).all()