Files
galaxy/lib/tool_shed/managers/tools.py
T
Nicola Soranzo 7f89e0eab9 Code cleanups from pyupgrade
Found by running:

```
make pyupgrade
ruff check --fix .
ruff check --preview --unsafe-fixes --select=F401 --fix .
make format
make update-client-api-schema
```

The bulk of the changes are replacements of typing deprecated aliases
such as `Dict` and `List` with the standard library classes. These
aliases became redundant in Python 3.9 when the corresponding
pre-existing classes were enhanced to support `[]`, see
https://docs.python.org/3/library/typing.html#deprecated-aliases

Also:
- Update the `pyupgrade` Makefile target to use `--py38-plus` for the
  packages needed by Pulsar, including the new galaxy-tool-util-models
  package.
2025-07-31 11:17:22 +01:00

192 lines
7.9 KiB
Python

import os
import tempfile
from collections import namedtuple
from typing import (
Optional,
)
from galaxy import exceptions
from galaxy.exceptions import (
InconsistentApplicationState,
InternalServerError,
ObjectNotFound,
RequestParameterInvalidException,
)
from galaxy.tool_shed.metadata.metadata_generator import RepositoryMetadataToolDict
from galaxy.tool_shed.util.basic_util import remove_dir
from galaxy.tool_shed.util.hg_util import (
clone_repository,
get_changectx_for_changeset,
)
from galaxy.tool_util.model_factory import parse_tool_custom
from galaxy.tool_util.parser import (
get_tool_source,
ToolSource,
)
from galaxy.tools.stock import stock_tool_sources
from galaxy.util import relpath
from tool_shed.context import (
ProvidesRepositoriesContext,
SessionRequestContext,
)
from tool_shed.util.common_util import generate_clone_url_for
from tool_shed.webapp.model import RepositoryMetadata
from tool_shed.webapp.search.tool_search import ToolSearch
from tool_shed_client.schema import ShedParsedTool
from .repositories import get_repository_revision_metadata_model
from .trs import trs_tool_id_to_repository_metadata
STOCK_TOOL_SOURCES: Optional[dict[str, dict[str, ToolSource]]] = None
def search(trans: SessionRequestContext, q: str, page: int = 1, page_size: int = 10) -> dict:
"""
Perform the search over TS tools index.
Note that search works over the Whoosh index which you have
to pre-create with scripts/tool_shed/build_ts_whoosh_index.sh manually.
Also TS config option toolshed_search_on has to be True and
whoosh_index_dir has to be specified.
"""
app = trans.app
conf = app.config
if not conf.toolshed_search_on:
raise exceptions.ConfigDoesNotAllowException(
"Searching the TS through the API is turned off for this instance."
)
if not conf.whoosh_index_dir:
raise exceptions.ConfigDoesNotAllowException(
"There is no directory for the search index specified. Please contact the administrator."
)
search_term = q.strip()
if len(search_term) < 1:
raise exceptions.RequestParameterInvalidException("The search term has to be at least one character long.")
tool_search = ToolSearch()
Boosts = namedtuple(
"Boosts", ["tool_name_boost", "tool_description_boost", "tool_help_boost", "tool_repo_owner_username_boost"]
)
boosts = Boosts(
float(conf.get("tool_name_boost", 1.2)),
float(conf.get("tool_description_boost", 0.6)),
float(conf.get("tool_help_boost", 0.4)),
float(conf.get("tool_repo_owner_username_boost", 0.3)),
)
results = tool_search.search(trans.app, search_term, page, page_size, boosts)
results["hostname"] = trans.repositories_hostname
return results
def get_repository_metadata_tool_dict(
trans: ProvidesRepositoriesContext, trs_tool_id: str, tool_version: str
) -> tuple[RepositoryMetadata, RepositoryMetadataToolDict]:
if trs_tool_id.count("~") < 2:
RequestParameterInvalidException(f"Invalid TRS tool id ({trs_tool_id})")
name, owner, tool_id = trs_tool_id.split("~", 3)
repository, metadata_by_version = trs_tool_id_to_repository_metadata(trans, trs_tool_id)
if tool_version not in metadata_by_version:
raise ObjectNotFound()
tool_version_repository_metadata: RepositoryMetadata = metadata_by_version[tool_version]
raw_metadata = tool_version_repository_metadata.metadata
tool_dicts: list[RepositoryMetadataToolDict] = raw_metadata.get("tools", [])
for tool_dict in tool_dicts:
if tool_dict["id"] != tool_id or tool_dict["version"] != tool_version:
continue
return tool_version_repository_metadata, tool_dict
raise ObjectNotFound()
def parsed_tool_model_cached_for(
trans: ProvidesRepositoriesContext, trs_tool_id: str, tool_version: str, repository_clone_url: Optional[str] = None
) -> ShedParsedTool:
model_cache = trans.app.model_cache
parsed_tool = model_cache.get_cache_entry_for(ShedParsedTool, trs_tool_id, tool_version)
if parsed_tool is not None:
return parsed_tool
parsed_tool = parsed_tool_model_for(trans, trs_tool_id, tool_version, repository_clone_url=repository_clone_url)
model_cache.insert_cache_entry_for(parsed_tool, trs_tool_id, tool_version)
return parsed_tool
def parsed_tool_model_for(
trans: ProvidesRepositoriesContext, trs_tool_id: str, tool_version: str, repository_clone_url: Optional[str] = None
) -> ShedParsedTool:
tool_source, repository_metadata = tool_source_for(
trans, trs_tool_id, tool_version, repository_clone_url=repository_clone_url
)
parsed_tool = parse_tool_custom(tool_source, ShedParsedTool)
if repository_metadata:
revision_model = get_repository_revision_metadata_model(
trans.app, repository_metadata.repository, repository_metadata, recursive=False
)
parsed_tool.repository_revision = revision_model
return parsed_tool
def tool_source_for(
trans: ProvidesRepositoriesContext, trs_tool_id: str, tool_version: str, repository_clone_url: Optional[str] = None
) -> tuple[ToolSource, Optional[RepositoryMetadata]]:
if "~" in trs_tool_id:
return _shed_tool_source_for(trans, trs_tool_id, tool_version, repository_clone_url)
else:
tool_source = _stock_tool_source_for(trs_tool_id, tool_version)
if tool_source is None:
raise ObjectNotFound()
return tool_source, None
def _shed_tool_source_for(
trans: ProvidesRepositoriesContext, trs_tool_id: str, tool_version: str, repository_clone_url: Optional[str] = None
) -> tuple[ToolSource, RepositoryMetadata]:
rval = get_repository_metadata_tool_dict(trans, trs_tool_id, tool_version)
repository_metadata, tool_version_metadata = rval
tool_config = tool_version_metadata["tool_config"]
repo = repository_metadata.repository.hg_repo
ctx = get_changectx_for_changeset(repo, repository_metadata.changeset_revision)
work_dir = tempfile.mkdtemp(prefix="tmp-toolshed-tool_source")
if repository_clone_url is None:
repository_clone_url = generate_clone_url_for(trans, repository_metadata.repository)
try:
cloned_ok, error_message = clone_repository(repository_clone_url, work_dir, str(ctx.rev()))
if error_message:
raise InternalServerError("Failed to materialize target repository revision")
repo_files_dir = repository_metadata.repository.hg_repository_path(trans.app.config.file_path)
if not repo_files_dir:
raise InconsistentApplicationState(
f"Failed to resolve repository path from hgweb_config_manager for [{trs_tool_id}], inconsistent repository state or application configuration"
)
repo_rel_tool_path = relpath(tool_config, repo_files_dir)
path_to_tool = os.path.join(work_dir, repo_rel_tool_path)
if not os.path.exists(path_to_tool):
raise InconsistentApplicationState(
f"Target tool expected at [{path_to_tool}] and not found, inconsistent repository state or application configuration"
)
tool_source = get_tool_source(path_to_tool)
return tool_source, repository_metadata
finally:
remove_dir(work_dir)
def _stock_tool_source_for(tool_id: str, tool_version: str) -> Optional[ToolSource]:
_init_stock_tool_sources()
assert STOCK_TOOL_SOURCES
tool_version_sources = STOCK_TOOL_SOURCES.get(tool_id)
if tool_version_sources is None:
return None
return tool_version_sources.get(tool_version)
def _init_stock_tool_sources() -> None:
global STOCK_TOOL_SOURCES
if STOCK_TOOL_SOURCES is None:
STOCK_TOOL_SOURCES = {}
for tool_source in stock_tool_sources():
tool_id = tool_source.parse_id()
tool_version = tool_source.parse_version()
if tool_id not in STOCK_TOOL_SOURCES:
STOCK_TOOL_SOURCES[tool_id] = {}
STOCK_TOOL_SOURCES[tool_id][tool_version] = tool_source