Merge pull request #23186 from mvdbeek/annotate-trans

Annotate trans across the codebase
This commit is contained in:
John Chilton
2026-07-28 10:48:53 -04:00
committed by GitHub
174 changed files with 2159 additions and 1200 deletions
+44 -14
View File
@@ -5,6 +5,10 @@ Contains library functions
import json
import logging
import os.path
from typing import (
Any,
Protocol,
)
from markupsafe import escape
@@ -12,10 +16,15 @@ from galaxy import (
exceptions,
util,
)
from galaxy.managers.collections import DatasetCollectionManager
from galaxy.managers.collections_util import (
api_payload_to_create_params,
dictify_dataset_collection_instance,
)
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
HistoryDatasetAssociation,
Library,
@@ -32,7 +41,7 @@ from galaxy.util.path import (
log = logging.getLogger(__name__)
def validate_server_directory_upload(trans, server_dir):
def validate_server_directory_upload(trans: ProvidesUserContext, server_dir):
if server_dir in [None, "None", ""]:
raise exceptions.RequestParameterInvalidException("Invalid or unspecified server_dir parameter")
@@ -81,7 +90,7 @@ def validate_server_directory_upload(trans, server_dir):
return full_dir, import_dir_desc
def validate_path_upload(trans):
def validate_path_upload(trans: ProvidesUserContext):
if not trans.app.config.allow_library_path_paste:
raise exceptions.ConfigDoesNotAllowException(
'"allow_path_paste" is not set to True in the Galaxy configuration file'
@@ -93,12 +102,24 @@ def validate_path_upload(trans):
)
class CreatesLibraryCollections(Protocol):
"""What LibraryActions._create_collection needs from its host class.
Hosts inject collection_manager; check_user_can_add_to_library_item comes
from UsesLibraryMixinItems, which they all mix in as well.
"""
collection_manager: DatasetCollectionManager
def check_user_can_add_to_library_item(self, trans: ProvidesUserContext, item, check_accessible: bool = True): ...
class LibraryActions:
"""
Mixin for controllers that provide library functionality.
"""
def _upload_dataset(self, trans, folder_id: int, payload):
def _upload_dataset(self, trans: ProvidesHistoryContext, folder_id: int, payload):
# Set up the traditional tool state/params
cntrller = "api"
tool_id = "upload1"
@@ -106,6 +127,8 @@ class LibraryActions:
datatypes_registry=trans.app.datatypes_registry, ext=payload.file_type
)
tool = trans.app.toolbox.get_tool(tool_id)
if tool is None:
raise exceptions.ToolMissingException(f"Tool '{tool_id}' is missing from the toolbox")
state = tool.new_state(trans)
populate_state(trans, tool.inputs, payload.model_dump(), state.inputs)
tool_params = state.inputs
@@ -160,7 +183,9 @@ class LibraryActions:
raise exceptions.RequestParameterInvalidException("Upload failed")
return output
def _get_server_dir_uploaded_datasets(self, trans, payload, full_dir, import_dir_desc, library_bunch):
def _get_server_dir_uploaded_datasets(
self, trans: ProvidesHistoryContext, payload, full_dir, import_dir_desc, library_bunch
):
files = self._get_server_dir_files(payload, full_dir, import_dir_desc)
uploaded_datasets = []
for file in files:
@@ -206,9 +231,11 @@ class LibraryActions:
raise exceptions.ObjectAttributeMissingException(f"The directory '{full_dir}' contains no valid files")
return files
def _get_path_paste_uploaded_datasets(self, trans, params, library_bunch, response_code, message):
def _get_path_paste_uploaded_datasets(
self, trans: ProvidesHistoryContext, params, library_bunch, response_code, message
):
preserve_dirs = util.string_as_bool(params.get("preserve_dirs", False))
uploaded_datasets = []
uploaded_datasets: list = []
files_and_folders, _response_code, _message = self._get_path_files_and_folders(params, preserve_dirs)
if _response_code:
return (uploaded_datasets, _response_code, _message)
@@ -264,12 +291,14 @@ class LibraryActions:
return None, response_code, message
return None
def _make_library_uploaded_dataset(self, trans, params, name, path, type, library_bunch, in_folder=None):
def _make_library_uploaded_dataset(
self, trans: ProvidesHistoryContext, params, name, path, type, library_bunch, in_folder=None
):
link_data_only = params.get("link_data_only", "copy_files")
uuid_str = params.get("uuid", None)
file_type = params.get("file_type", None)
library_bunch.replace_dataset = None # not valid for these types of upload
uploaded_dataset = util.bunch.Bunch()
uploaded_dataset: Any = util.bunch.Bunch()
new_name = name
# Remove compressed file extensions, if any, but only if
# we're copying files into Galaxy's file space.
@@ -300,7 +329,7 @@ class LibraryActions:
trans.sa_session.commit()
return uploaded_dataset
def _upload_library_dataset(self, trans, payload):
def _upload_library_dataset(self, trans: ProvidesHistoryContext, payload):
is_admin = trans.user_is_admin
current_user_roles = trans.get_current_user_roles()
folder = trans.sa_session.get(LibraryFolder, payload.folder_id)
@@ -320,7 +349,7 @@ class LibraryActions:
created_outputs_dict = self._upload_dataset(trans, folder.id, payload)
return created_outputs_dict
def _create_folder(self, trans, payload):
def _create_folder(self, trans: ProvidesUserContext, payload):
is_admin = trans.user_is_admin
current_user_roles = trans.get_current_user_roles()
parent_folder = trans.sa_session.get(LibraryFolder, payload.folder_id)
@@ -343,7 +372,7 @@ class LibraryActions:
new_folder_dict = dict(created=new_folder)
return new_folder_dict
def _create_collection(self, trans, payload, parent):
def _create_collection(self: CreatesLibraryCollections, trans: ProvidesUserContext, payload, parent):
# Not delegating to library_common, so need to check access to parent folder here.
self.check_user_can_add_to_library_item(trans, parent, check_accessible=True)
create_params = api_payload_to_create_params(payload.model_dump())
@@ -356,13 +385,14 @@ class LibraryActions:
)
return [dataset_collection]
def _check_access(self, trans, is_admin, item, current_user_roles):
def _check_access(self, trans: ProvidesUserContext, is_admin, item, current_user_roles):
if isinstance(item, HistoryDatasetAssociation):
# Make sure the user has the DATASET_ACCESS permission on the history_dataset_association.
if not item:
message = f"Invalid history dataset ({escape(str(item))}) specified."
raise exceptions.ObjectNotFound(message)
elif (
assert item.dataset is not None
if (
not trans.app.security_agent.can_access_dataset(current_user_roles, item.dataset)
and item.user == trans.user
):
@@ -385,7 +415,7 @@ class LibraryActions:
message = f"You do not have permission to access the {escape(item_type)} with id ({str(item.id)})."
raise exceptions.ItemAccessibilityException(message)
def _check_add(self, trans, is_admin, item, current_user_roles):
def _check_add(self, trans: ProvidesUserContext, is_admin, item, current_user_roles):
# Deny access if the user is not an admin and does not have the LIBRARY_ADD permission.
if not (is_admin or trans.app.security_agent.can_add_library_item(current_user_roles, item)):
message = f"You are not authorized to add an item to ({escape(item.name)})."
+2 -2
View File
@@ -29,13 +29,13 @@ from typing import (
import yaml
from galaxy.exceptions import ConfigurationError
from galaxy.managers.context import ProvidesUserContext
from galaxy.model import User
from galaxy.schema.agents import (
ActionSuggestion,
ActionType,
ConfidenceLevel,
)
from galaxy.work.context import SessionRequestContext
if TYPE_CHECKING:
from galaxy.config import GalaxyAppConfiguration
@@ -353,7 +353,7 @@ class AgentRunState:
class GalaxyAgentDependencies:
"""Dependencies passed to Galaxy agents via dependency injection."""
trans: ProvidesUserContext
trans: SessionRequestContext
user: User
config: "GalaxyAppConfiguration"
# Callable to get agent instances, avoids circular import in base.py
+2 -2
View File
@@ -13,7 +13,6 @@ from typing import (
from sqlalchemy import select
from galaxy.agents import iwc
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.hdas import HDAManager
from galaxy.managers.tools import DynamicToolManager
from galaxy.model import UserDynamicToolAssociation
@@ -40,6 +39,7 @@ from galaxy.schema.schema import (
from galaxy.schema.workflows import InvokeWorkflowPayload
from galaxy.structured_app import MinimalManagerApp
from galaxy.tool_util_models.dynamic_tool_models import DynamicUnprivilegedToolCreatePayload
from galaxy.work.context import SessionRequestContext
log = logging.getLogger(__name__)
@@ -61,7 +61,7 @@ ID_FIELDS = {
class AgentOperationsManager:
"""Shared operations for AI agents, delegating to Galaxy's service layer."""
def __init__(self, app: MinimalManagerApp, trans: ProvidesUserContext):
def __init__(self, app: MinimalManagerApp, trans: SessionRequestContext):
self.app = app
self.trans = trans
self._tools_service: Any | None = None
+1 -1
View File
@@ -246,7 +246,7 @@ class ToolRecommendationAgent(BaseGalaxyAgent):
try:
panel_view = self.deps.config.default_panel_view or "default"
toolbox_search = self.deps.trans.app.toolbox_search # type: ignore[attr-defined]
toolbox_search = self.deps.trans.app.toolbox_search
tool_ids = toolbox_search.search(query, panel_view, self.deps.config)
tools = []
+3 -1
View File
@@ -977,7 +977,9 @@ class UniverseApplication(StructuredApp, GalaxyManagerApplication, InstallationT
self.proxy_manager = ProxyManager(self.config)
# Must be initialized after job_config.
self.workflow_scheduling_manager = scheduling_manager.WorkflowSchedulingManager(self)
self.workflow_scheduling_manager = self._register_singleton(
scheduling_manager.WorkflowSchedulingManager, scheduling_manager.WorkflowSchedulingManager(self)
)
# Initialize workflow completion monitoring (manager is always available,
# but monitor only runs on workflow scheduler processes)
+2 -1
View File
@@ -28,6 +28,7 @@ from galaxy.config_watchers import ConfigWatchers
from galaxy.job_metrics import JobMetrics
from galaxy.jobs.manager import NoopManager
from galaxy.managers.collections import DatasetCollectionManager
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.dbkeys import GenomeBuilds
from galaxy.managers.hdas import HDAManager
from galaxy.managers.histories import HistoryManager
@@ -430,7 +431,7 @@ class MockTrans:
class MockVisualizationsRegistry:
def get_visualizations(self, trans, target):
def get_visualizations(self, trans: ProvidesAppContext, target):
return []
+5 -1
View File
@@ -3,6 +3,7 @@ Contains implementations of the authentication logic.
"""
import logging
from typing import TYPE_CHECKING
from galaxy.auth.util import (
get_authenticators,
@@ -11,6 +12,9 @@ from galaxy.auth.util import (
from galaxy.exceptions import Conflict
from galaxy.util import string_as_bool
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -41,7 +45,7 @@ class AuthManager:
break
return message, status
def check_auto_registration(self, trans, login, password, no_password_check=False):
def check_auto_registration(self, trans: "GalaxyWebTransaction", login, password, no_password_check=False):
"""
Checks the username/email & password using auth providers in order.
If a match is found, returns the 'auto-register' option for that provider.
+10 -2
View File
@@ -1,6 +1,10 @@
import errno
import logging
from collections import namedtuple
from typing import (
Any,
TYPE_CHECKING,
)
import galaxy.auth.providers
from galaxy.exceptions import Conflict
@@ -12,6 +16,10 @@ from galaxy.util import (
string_as_bool,
)
if TYPE_CHECKING:
# runtime cycle: galaxy.managers.context -> galaxy.structured_app -> galaxy.auth -> here
from galaxy.managers.context import ProvidesAppContext
log = logging.getLogger(__name__)
AUTH_CONF_XML = """<?xml version="1.0"?>
@@ -73,8 +81,8 @@ def get_authenticators(auth_config_file, auth_config_file_set):
return authenticators
def parse_auth_results(trans, auth_results, options):
auth_return = {}
def parse_auth_results(trans: "ProvidesAppContext", auth_results, options):
auth_return: dict[str, Any] = {}
auth_result, auto_email, auto_username = auth_results[:3]
auto_username = str(auto_username).lower()
# make username unique
+12 -5
View File
@@ -9,6 +9,11 @@ Additionally, this package implements functionalist's to request temporary acces
credentials for cloud-based resource providers (e.g., Amazon AWS, Microsoft Azure).
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
class IdentityProvider:
"""
@@ -40,10 +45,10 @@ class IdentityProvider:
"""
raise NotImplementedError()
def refresh(self, trans, token):
def refresh(self, trans: "GalaxyWebTransaction", token):
raise NotImplementedError()
def authenticate(self, trans, idphint=None):
def authenticate(self, trans: "GalaxyWebTransaction", idphint=None):
"""Runs for authentication process. Checks the database if a
valid identity exists in the database; if yes, then the user
is authenticated, if not, it generates a provider-specific
@@ -56,7 +61,7 @@ class IdentityProvider:
"""
raise NotImplementedError()
def callback(self, state_token: str, authz_code: str, trans, login_redirect_url):
def callback(self, state_token: str, authz_code: str, trans: "GalaxyWebTransaction", login_redirect_url):
"""Handles authentication call-backs from identity providers.
This process maps `state-token` to a user.
@@ -72,10 +77,12 @@ class IdentityProvider:
"""
raise NotImplementedError()
def disconnect(self, provider, trans, disconnect_redirect_url=None, email=None, association_id=None):
def disconnect(
self, provider, trans: "GalaxyWebTransaction", disconnect_redirect_url=None, email=None, association_id=None
):
raise NotImplementedError()
def logout(self, trans, post_user_logout_href=None):
def logout(self, trans: "GalaxyWebTransaction", post_user_logout_href=None):
"""
Return a URL that will log the user out of the IDP. In OIDC this is
called the 'end_session_endpoint'.
+16 -5
View File
@@ -40,7 +40,10 @@ from .psa_authnz import (
)
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
from galaxy.webapps.base.webapp import GalaxyWebTransaction
OIDC_BACKEND_SCHEMA = resource_path(__name__, "xsd/oidc_backends_config.xsd")
@@ -300,7 +303,7 @@ class AuthnzManager:
return None
@staticmethod
def can_user_assume_authn(trans, authn_id):
def can_user_assume_authn(trans: ProvidesUserContext, authn_id):
qres = trans.sa_session.query(model.UserAuthnzToken).get(authn_id)
if qres is None:
msg = f"Authentication record with the given `authn_id` (`{trans.security.encode_id(authn_id)}`) not found."
@@ -405,9 +408,13 @@ class AuthnzManager:
log.exception(msg)
return False, msg, None
def callback(self, provider, state_token, authz_code, trans, login_redirect_url, idphint=None):
def callback(
self, provider, state_token, authz_code, trans: GalaxyWebTransaction, login_redirect_url, idphint=None
):
try:
success, message, backend = self._get_authnz_backend(provider, idphint=idphint)
if backend is None:
return False, f"Provider `{provider}` not found", (None, None)
if success is False:
return False, message, (None, None)
return success, message, backend.callback(state_token, authz_code, trans, login_redirect_url)
@@ -505,7 +512,7 @@ class AuthnzManager:
return user
return None
def logout(self, provider, trans, post_user_logout_href=None):
def logout(self, provider, trans: GalaxyWebTransaction, post_user_logout_href=None):
"""
Log the user out of the identity provider.
@@ -525,6 +532,8 @@ class AuthnzManager:
return False, f"IDP logout is not enabled for {provider}", None
success, message, backend = self._get_authnz_backend(provider)
if backend is None:
return False, f"Provider `{provider}` not found", None
if success is False:
return False, message, None
return True, message, backend.logout(trans, post_user_logout_href)
@@ -533,9 +542,11 @@ class AuthnzManager:
log.exception(msg)
return False, msg, None
def disconnect(self, provider, trans, email=None, disconnect_redirect_url=None, idphint=None):
def disconnect(self, provider, trans: GalaxyWebTransaction, email=None, disconnect_redirect_url=None, idphint=None):
try:
success, message, backend = self._get_authnz_backend(provider, idphint=idphint)
if backend is None:
return False, f"Provider `{provider}` not found", None
if success is False:
return False, message, None
return backend.disconnect(provider, trans, disconnect_redirect_url, email=email)
+9 -6
View File
@@ -54,6 +54,7 @@ if TYPE_CHECKING:
from social_core.strategy import HttpResponseProtocol
from galaxy.managers.context import ProvidesAppContext
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -293,7 +294,7 @@ class PSAAuthnz(IdentityProvider):
extra_data["expires"] = int(expires - time.time())
user_authnz_token.set_extra_data(extra_data)
def refresh(self, trans, user_authnz_token):
def refresh(self, trans: "GalaxyWebTransaction", user_authnz_token):
if (
not user_authnz_token
or not user_authnz_token.extra_data
@@ -318,13 +319,13 @@ class PSAAuthnz(IdentityProvider):
def _try_to_locate_token_expiration(self, extra_data):
return locate_token_expiration(extra_data)
def authenticate(self, trans, idphint=None) -> "HttpResponseProtocol":
def authenticate(self, trans: "GalaxyWebTransaction", idphint=None) -> "HttpResponseProtocol":
on_the_fly_config(trans.sa_session)
strategy = Strategy(trans.request, trans.session, Storage, self.config)
backend = self._load_backend(strategy, self.config["redirect_uri"])
return do_auth(backend)
def callback(self, state_token, authz_code, trans, login_redirect_url):
def callback(self, state_token, authz_code, trans: "GalaxyWebTransaction", login_redirect_url):
on_the_fly_config(trans.sa_session)
# Always set LOGIN_REDIRECT_URL to the base URL for pipeline steps
# We'll adjust the final redirect based on fixed_delegated_auth after do_complete
@@ -384,7 +385,9 @@ class PSAAuthnz(IdentityProvider):
return redirect_url, user
def disconnect(self, provider, trans, disconnect_redirect_url=None, email=None, association_id=None):
def disconnect(
self, provider, trans: "GalaxyWebTransaction", disconnect_redirect_url=None, email=None, association_id=None
):
on_the_fly_config(trans.sa_session)
self.config[setting_name("DISCONNECT_REDIRECT_URL")] = (
disconnect_redirect_url if disconnect_redirect_url is not None else ()
@@ -397,7 +400,7 @@ class PSAAuthnz(IdentityProvider):
return True, "", response_url
return response.get("success", False), response.get("message", ""), ""
def logout(self, trans, post_user_logout_href=None):
def logout(self, trans: "GalaxyWebTransaction", post_user_logout_href=None):
"""
Logout from the identity provider.
@@ -846,7 +849,7 @@ def sync_user_profile(strategy=None, details=None, user=None, **kwargs):
_send_oidc_profile_update_notification(trans, user, updates)
def _send_oidc_profile_update_notification(trans, user, updates: list[str]) -> None:
def _send_oidc_profile_update_notification(trans: "ProvidesAppContext", user, updates: list[str]) -> None:
if not trans.app.notification_manager.notifications_enabled:
return
try:
+7 -3
View File
@@ -112,7 +112,9 @@ except ModuleNotFoundError:
pass
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesUserContext
from galaxy.util.compression_utils import FileObjType
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
# pysam 0.16.0.1 emits logs containing the word 'Error', this can confuse the stdout/stderr checkers.
@@ -777,7 +779,9 @@ class BamNative(CompressedArchive, _BamOrSam):
# Remove temp file and empty temporary directory
os.rmdir(tmp_dir)
def get_chunk(self, trans, dataset: HasFileName, offset: int = 0, ck_size: int | None = None) -> str:
def get_chunk(
self, trans: "ProvidesUserContext | None", dataset: HasFileName, offset: int = 0, ck_size: int | None = None
) -> str:
if not offset == -1:
try:
with pysam.AlignmentFile(
@@ -830,7 +834,7 @@ class BamNative(CompressedArchive, _BamOrSam):
def display_data(
self,
trans,
trans: "GalaxyWebTransaction",
dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: str | None = None,
@@ -2537,7 +2541,7 @@ class H5MLM(H5):
def display_data(
self,
trans,
trans: "GalaxyWebTransaction",
dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: str | None = None,
+27 -12
View File
@@ -34,6 +34,7 @@ from galaxy.datatypes.protocols import (
HasExt,
HasExtraFilesAndMetadata,
HasFileName,
HasHid,
HasInfo,
HasMetadata,
HasName,
@@ -69,6 +70,11 @@ from . import (
if TYPE_CHECKING:
from galaxy.datatypes.display_applications.application import DisplayApplication
from galaxy.datatypes.registry import Registry
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
from galaxy.webapps.base.webapp import GalaxyWebTransaction
XSS_VULNERABLE_MIME_TYPES = [
"image/svg+xml", # Unfiltered by Galaxy and may contain JS that would be executed by some browsers.
@@ -397,7 +403,7 @@ class Data(metaclass=DataMeta):
return error, msg, messagetype
def _archive_composite_dataset(
self, trans, data: DatasetHasHidProtocol, headers: Headers, do_action: str = "zip"
self, trans: "GalaxyWebTransaction", data: DatasetHasHidProtocol, headers: Headers, do_action: str = "zip"
) -> tuple[ZipstreamWrapper | str, Headers]:
# save a composite object into a compressed archive for downloading
assert data.name
@@ -518,7 +524,7 @@ class Data(metaclass=DataMeta):
)
return to_content_disposition(filename)
def _serve_file_download(self, headers, data, trans, to_ext, file_size, **kwd):
def _serve_file_download(self, headers, data, trans: "GalaxyWebTransaction", to_ext, file_size, **kwd):
if self.is_archive_download(trans.app.datatypes_registry, data.extension):
return self._archive_composite_dataset(trans, data, headers, do_action=kwd.get("do_action", "zip"))
else:
@@ -529,7 +535,9 @@ class Data(metaclass=DataMeta):
headers["Content-Disposition"] = self.download_content_disposition(data, to_ext, **kwd)
return open(data.get_file_name(auth=ObjectStoreAuth(user=trans.user)), "rb"), headers
def _serve_binary_file_contents_as_text(self, trans, data, headers, file_size, max_peek_size):
def _serve_binary_file_contents_as_text(
self, trans: "ProvidesUserContext", data, headers, file_size, max_peek_size
):
# Use text/plain so the browser preserves whitespace and line endings
# and does not attempt to interpret stray markup as HTML.
headers["content-type"] = "text/plain; charset=utf-8"
@@ -538,7 +546,7 @@ class Data(metaclass=DataMeta):
with open(data.get_file_name(auth=ObjectStoreAuth(user=trans.user)), "rb") as fh:
return unicodify(fh.read(max_peek_size)), headers
def _serve_file_contents(self, trans, data, headers, preview, file_size, max_peek_size):
def _serve_file_contents(self, trans: "ProvidesUserContext", data, headers, preview, file_size, max_peek_size):
from galaxy.datatypes import images
preview = util.string_as_bool(preview)
@@ -559,7 +567,7 @@ class Data(metaclass=DataMeta):
def display_data(
self,
trans,
trans: "GalaxyWebTransaction",
dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: str | None = None,
@@ -671,7 +679,9 @@ class Data(metaclass=DataMeta):
result += indicate_data_truncated()
return result
def _yield_user_file_content(self, trans, from_dataset: HasCreatingJob, filename: str, headers: Headers) -> IO:
def _yield_user_file_content(
self, trans: "ProvidesAppContext", from_dataset: HasCreatingJob, filename: str, headers: Headers
) -> IO:
"""This method sets the content type header to text/plain if we don't trust html content."""
if trans.app.config.sanitize_all_html and headers.get("content-type", None) == "text/html":
# Check to see if this dataset's parent job is allowlisted
@@ -790,7 +800,9 @@ class Data(metaclass=DataMeta):
) -> Union["DisplayApplication", None]:
return self.display_applications.get(key, default)
def get_display_applications_by_dataset(self, dataset: DatasetProtocol, trans) -> dict[str, "DisplayApplication"]:
def get_display_applications_by_dataset(
self, dataset: DatasetProtocol, trans: "GalaxyWebTransaction"
) -> dict[str, "DisplayApplication"]:
rval = {}
for key, value in self.display_applications.items():
value = value.filter_by_dataset(dataset, trans)
@@ -851,7 +863,7 @@ class Data(metaclass=DataMeta):
return datatypes_registry.get_converters_by_datatype(original_dataset.ext)
def find_conversion_destination(
self, dataset: DatasetProtocol, accepted_formats: list[str], datatypes_registry, **kwd
self, dataset: DatasetProtocol, accepted_formats: Iterable[Union[str, "Data"]], datatypes_registry, **kwd
) -> tuple[bool, str | None, Any]:
"""Returns ( direct_match, converted_ext, existing converted dataset )"""
return datatypes_registry.find_conversion_destination_for_dataset_by_extensions(
@@ -860,8 +872,8 @@ class Data(metaclass=DataMeta):
def convert_dataset(
self,
trans,
original_dataset: DatasetHasHidProtocol,
trans: "ProvidesUserContext",
original_dataset: DatasetProtocol,
target_type: str,
return_output: bool = False,
visible: bool = True,
@@ -909,6 +921,9 @@ class Data(metaclass=DataMeta):
value.visible = False
if return_output:
return converted_datasets
# Only this message names the dataset by hid; library datasets do not have one
# and reach conversion through the return_output callers instead.
assert isinstance(original_dataset, HasHid)
return f"The file conversion of {converter.name} on data {original_dataset.hid} has been added to the Queue."
# We need to clear associated files before we set metadata
@@ -1048,7 +1063,7 @@ class Data(metaclass=DataMeta):
dataset_source = p_dataproviders.dataset.DatasetDataProvider(dataset)
return p_dataproviders.chunk.Base64ChunkDataProvider(dataset_source, **settings)
def _clean_and_set_mime_type(self, trans, mime: str, headers: Headers) -> None:
def _clean_and_set_mime_type(self, trans: "ProvidesAppContext", mime: str, headers: Headers) -> None:
if mime.lower() in XSS_VULNERABLE_MIME_TYPES:
if not getattr(trans.app.config, "serve_xss_vulnerable_mimetypes", True):
mime = DEFAULT_MIME_TYPE
@@ -1330,7 +1345,7 @@ class ZarrDirectory(Directory):
def display_data(
self,
trans,
trans: "GalaxyWebTransaction",
dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: str | None = None,
@@ -1,11 +1,16 @@
from typing import TYPE_CHECKING
from galaxy.model import (
HistoryDatasetAssociation,
User,
)
from galaxy.security.idencoding import IdEncodingHelper
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesAppContext
def encode_dataset_user(trans, dataset, user):
def encode_dataset_user(trans: "ProvidesAppContext", dataset, user):
# encode dataset id as usual
# encode user id using the dataset create time as the key
dataset_hash = trans.security.encode_id(dataset.id)
@@ -17,7 +22,7 @@ def encode_dataset_user(trans, dataset, user):
return dataset_hash, user_hash
def decode_dataset_user(trans, dataset_hash, user_hash):
def decode_dataset_user(trans: "ProvidesAppContext", dataset_hash, user_hash):
# decode dataset id as usual
# decode user id using the dataset create time as the key
dataset_id = trans.security.decode_id(dataset_hash)
+5 -1
View File
@@ -4,7 +4,10 @@ Location of protocols used in datatypes
from typing import Any
from typing_extensions import Protocol
from typing_extensions import (
Protocol,
runtime_checkable,
)
from galaxy.objectstore import ObjectStoreAuth
@@ -36,6 +39,7 @@ class HasFileName(Protocol):
def get_file_name(self, sync_cache=True, auth: ObjectStoreAuth | None = None) -> str: ...
@runtime_checkable
class HasHid(Protocol):
hid: str
+13 -4
View File
@@ -14,6 +14,7 @@ import tempfile
from json import dumps
from typing import (
cast,
TYPE_CHECKING,
)
import pysam
@@ -74,6 +75,10 @@ from galaxy.util.markdown import (
)
from . import dataproviders
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesAppContext
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
MAX_DATA_LINES = 100000
@@ -142,7 +147,9 @@ class TabularData(Text):
except Exception:
return False
def get_chunk(self, trans, dataset: HasFileName, offset: int = 0, ck_size: int | None = None) -> str:
def get_chunk(
self, trans: "ProvidesAppContext", dataset: HasFileName, offset: int = 0, ck_size: int | None = None
) -> str:
ck_data, last_read = self._read_chunk(trans, dataset, offset, ck_size)
return dumps(
{
@@ -152,7 +159,7 @@ class TabularData(Text):
}
)
def _read_chunk(self, trans, dataset: HasFileName, offset: int, ck_size: int | None = None):
def _read_chunk(self, trans: "ProvidesAppContext", dataset: HasFileName, offset: int, ck_size: int | None = None):
with compression_utils.get_fileobj(dataset.get_file_name()) as f:
f.seek(offset)
try:
@@ -173,7 +180,7 @@ class TabularData(Text):
def display_data(
self,
trans,
trans: "GalaxyWebTransaction",
dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: str | None = None,
@@ -1631,7 +1638,9 @@ class ConnectivityTable(Tabular):
i += 1
return False
def get_chunk(self, trans, dataset: HasFileName, offset: int = 0, ck_size: int | None = None) -> str:
def get_chunk(
self, trans: "ProvidesAppContext", dataset: HasFileName, offset: int = 0, ck_size: int | None = None
) -> str:
ck_data, last_read = self._read_chunk(trans, dataset, offset, ck_size)
try:
# The ConnectivityTable format has several derivatives of which one is delimited by (multiple) spaces.
+13 -3
View File
@@ -10,6 +10,7 @@ import subprocess
import tempfile
from typing import (
IO,
TYPE_CHECKING,
)
import ijson
@@ -42,6 +43,13 @@ from galaxy.util import (
unicodify,
)
if TYPE_CHECKING:
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -207,7 +215,7 @@ class Ipynb(Json):
def display_data(
self,
trans,
trans: "GalaxyWebTransaction",
dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: str | None = None,
@@ -223,7 +231,7 @@ class Ipynb(Json):
def _display_data_trusted(
self,
trans,
trans: "ProvidesUserContext",
dataset: DatasetHasHidProtocol,
preview: bool = False,
filename: str | None = None,
@@ -1280,7 +1288,9 @@ class Yaml(Text):
"""Returns the mime type of the datatype"""
return "application/yaml"
def _yield_user_file_content(self, trans, from_dataset: HasCreatingJob, filename: str, headers: Headers) -> IO:
def _yield_user_file_content(
self, trans: "ProvidesAppContext", from_dataset: HasCreatingJob, filename: str, headers: Headers
) -> IO:
# Override non-standard application/yaml mediatype with
# text/plain, so preview is shown in preview iframe,
# instead of downloading the file.
+13
View File
@@ -51,3 +51,16 @@ for entry in loads(error_codes_json):
globals()[name] = error_code_obj
error_codes_by_name[name] = error_code_obj
error_codes_by_int_code[error_code_obj.code] = error_code_obj
def __getattr__(name: str) -> ErrorCode:
"""Expose the JSON-driven, dynamically-created error codes to static analysis.
The codes above are assigned via ``globals()[name] = ...``, which mypy cannot
see, so attribute access like ``error_codes.ADMIN_REQUIRED`` is otherwise
reported as an unknown attribute.
"""
try:
return error_codes_by_name[name]
except KeyError:
raise AttributeError(name)
+24 -1
View File
@@ -26,6 +26,29 @@ from .plugins import (
FileSourcePluginsConfig,
)
class ProvidesFileSourcesTransaction(Protocol):
"""The slice of a Galaxy transaction ProvidesFileSourcesUserContext reads."""
@property
def anonymous(self) -> bool: ...
@property
def user(self) -> Any: ...
@property
def user_ftp_dir(self) -> str | None: ...
@property
def user_is_admin(self) -> bool: ...
@property
def user_vault(self) -> Any: ...
@property
def app(self) -> Any: ...
log = logging.getLogger(__name__)
@@ -371,7 +394,7 @@ OptionalUserContext = FileSourcesUserContext | None
class ProvidesFileSourcesUserContext(FileSourcesUserContext, FileSourceDictifiable):
"""Implement a FileSourcesUserContext from a Galaxy ProvidesUserContext (e.g. trans)."""
def __init__(self, trans, **kwargs):
def __init__(self, trans: ProvidesFileSourcesTransaction, **kwargs):
self.trans = trans
@property
+4 -4
View File
@@ -9,10 +9,10 @@ from galaxy.agents import GalaxyAgentDependencies
from galaxy.agents.registry import AgentRegistry
from galaxy.agents.router import QueryRouterAgent
from galaxy.config import GalaxyAppConfiguration
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.jobs import JobManager
from galaxy.model import User
from galaxy.schema.agents import AgentResponse
from galaxy.work.context import SessionRequestContext
log = logging.getLogger(__name__)
@@ -30,7 +30,7 @@ class AgentService:
self.job_manager = job_manager
self.registry = registry
def create_dependencies(self, trans: ProvidesUserContext, user: User) -> GalaxyAgentDependencies:
def create_dependencies(self, trans: SessionRequestContext, user: User) -> GalaxyAgentDependencies:
"""Create agent dependencies for dependency injection."""
toolbox = trans.app.toolbox if hasattr(trans, "app") and hasattr(trans.app, "toolbox") else None
return GalaxyAgentDependencies(
@@ -47,7 +47,7 @@ class AgentService:
self,
agent_type: str,
query: str,
trans: ProvidesUserContext,
trans: SessionRequestContext,
user: User,
context: dict[str, Any] | None = None,
) -> AgentResponse:
@@ -96,7 +96,7 @@ class AgentService:
async def route_and_execute(
self,
query: str,
trans: ProvidesUserContext,
trans: SessionRequestContext,
user: User,
context: dict[str, Any] | None = None,
agent_type: str = "auto",
+3 -3
View File
@@ -65,7 +65,7 @@ from galaxy.structured_app import (
)
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.context import ProvidesUserContext
log = logging.getLogger(__name__)
@@ -84,7 +84,7 @@ FunctionFilterParsersType = dict[str, Any]
# ==== accessors from base/controller.py
def security_check(trans, item, check_ownership=False, check_accessible=False):
def security_check(trans: "ProvidesUserContext", item, check_ownership=False, check_accessible=False):
"""
Security checks for an item: checks if (a) user owns item or (b) item
is accessible to user. This is a generic method for dealing with objects
@@ -162,7 +162,7 @@ def encode_with_security(security: IdEncodingHelper, id: Any, kind: str | None =
def get_object(
trans: "ProvidesAppContext",
trans: "ProvidesUserContext",
id,
class_name,
check_ownership: bool = False,
+68 -5
View File
@@ -177,6 +177,54 @@ class DatasetCollectionManager:
return dataset_collection
@overload
def create(
self,
trans: ProvidesHistoryContext,
parent: model.History,
name,
collection_type,
element_identifiers=None,
elements=None,
implicit_collection_info=None,
trusted_identifiers=None,
hide_source_items: bool = False,
tags=None,
copy_elements: bool = False,
history=None,
set_hid: bool = True,
flush=True,
completed_job=None,
output_name=None,
fields: str | list["FieldDict"] | None = None,
column_definitions=None,
rows=None,
) -> model.HistoryDatasetCollectionAssociation: ...
@overload
def create(
self,
trans: ProvidesHistoryContext,
parent: model.LibraryFolder,
name,
collection_type,
element_identifiers=None,
elements=None,
implicit_collection_info=None,
trusted_identifiers=None,
hide_source_items: bool = False,
tags=None,
copy_elements: bool = False,
history=None,
set_hid: bool = True,
flush=True,
completed_job=None,
output_name=None,
fields: str | list["FieldDict"] | None = None,
column_definitions=None,
rows=None,
) -> model.LibraryDatasetCollectionAssociation: ...
def create(
self,
trans: ProvidesHistoryContext,
@@ -603,7 +651,12 @@ class DatasetCollectionManager:
)
def __recursively_create_collections_for_identifiers(
self, trans, element_identifiers, hide_source_items: bool, copy_elements: bool, history=None
self,
trans: ProvidesHistoryContext,
element_identifiers,
hide_source_items: bool,
copy_elements: bool,
history=None,
):
for element_identifier in element_identifiers:
try:
@@ -629,7 +682,7 @@ class DatasetCollectionManager:
return element_identifiers
def __recursively_create_collections_for_elements(
self, trans, elements, hide_source_items: bool, copy_elements: bool, history=None
self, trans: ProvidesHistoryContext, elements, hide_source_items: bool, copy_elements: bool, history=None
) -> None:
if elements is self.ELEMENTS_UNINITIALIZED:
return
@@ -655,7 +708,12 @@ class DatasetCollectionManager:
elements.update(new_elements)
def __load_elements(
self, trans, element_identifiers, hide_source_items: bool = False, copy_elements: bool = False, history=None
self,
trans: ProvidesHistoryContext,
element_identifiers,
hide_source_items: bool = False,
copy_elements: bool = False,
history=None,
) -> dict[str, HDCAElementObjectType]:
elements: dict[str, HDCAElementObjectType] = {}
for element_identifier in element_identifiers:
@@ -669,7 +727,12 @@ class DatasetCollectionManager:
return elements
def __load_element(
self, trans, element_identifier, hide_source_items: bool, copy_elements: bool, history=None
self,
trans: ProvidesHistoryContext,
element_identifier,
hide_source_items: bool,
copy_elements: bool,
history=None,
) -> HDCAElementObjectType:
# if not isinstance( element_identifier, dict ):
# # Is allowing this to just be the id of an hda too clever? Somewhat
@@ -752,7 +815,7 @@ class DatasetCollectionManager:
return self.__get_library_collection_instance(trans, id, **kwds)
raise NotImplementedError()
def get_dataset_collection(self, trans, encoded_id):
def get_dataset_collection(self, trans: ProvidesAppContext, encoded_id):
collection_id = int(trans.app.security.decode_id(encoded_id))
collection = trans.sa_session.get(DatasetCollection, collection_id)
return collection
+17
View File
@@ -46,6 +46,8 @@ from typing import (
Any,
cast,
Literal,
Optional,
TYPE_CHECKING,
)
from sqlalchemy import select
@@ -69,6 +71,9 @@ from galaxy.security.vault import UserVaultWrapper
from galaxy.structured_app import MinimalManagerApp
from galaxy.util import bunch
if TYPE_CHECKING:
from galaxy.tools.parameters.dataset_matcher import DatasetMatcherFactory
class ProvidesAppContext:
"""For transaction-like objects to provide Galaxy convenience layer for
@@ -314,6 +319,18 @@ class ProvidesHistoryContext(ProvidesUserContext):
properties.
"""
# set per-request by galaxy.tools.parameters.dataset_matcher while a tool
# form is being evaluated
dataset_matcher_factory: Optional["DatasetMatcherFactory"] = None
@abc.abstractmethod
def get_history(self, create: bool = False) -> History | None:
"""Return the current history, optionally creating one when there is none.
Transactions do not always have an active history, so None is a valid
response even when create is set.
"""
@property
@abc.abstractmethod
def history(self) -> History | None:
@@ -981,7 +981,6 @@ class StorageOperationRunExecutor:
self.run = run
self.user = user
self.current_task_id = current_task_id
self.trans = SimpleNamespace(user=user)
self.storage_operation_manager = storage_operation_manager
self.quota_source_map = app.object_store.get_quota_source_map()
self.target_quota_usage_at_start = 0
@@ -1315,7 +1314,9 @@ class StorageOperationRunExecutor:
# Queue cleanup for after DB commit to ensure crash safety.
self._pending_cleanups.append((source_proxy, extra_files_path_name))
else:
self.dataset_manager.update_object_store_id(self.trans, dataset, self.run.target_object_store_id)
self.dataset_manager.update_object_store_id_for_user(
self.user, dataset, self.run.target_object_store_id
)
self.additional_target_usage += quota_delta
self.succeeded_count += 1
+18 -8
View File
@@ -25,12 +25,17 @@ from galaxy.managers import (
secured,
users,
)
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
Dataset,
DatasetHash,
DatasetInstance,
DatasetPermissions,
HistoryDatasetAssociation,
User,
)
from galaxy.model.db.role import (
get_private_role_user_emails_dict,
@@ -133,7 +138,10 @@ class DatasetManager(
roles = user.all_roles_exploiting_cache() if user else []
return self.app.security_agent.can_access_dataset(roles, dataset)
def update_object_store_id(self, trans, dataset, object_store_id: str):
def update_object_store_id(self, trans: ProvidesUserContext, dataset, object_store_id: str):
return self.update_object_store_id_for_user(trans.user, dataset, object_store_id)
def update_object_store_id_for_user(self, user: User | None, dataset, object_store_id: str):
device_source_map = self.app.object_store.get_device_source_map()
old_object_store_id = dataset.object_store_id
new_object_store_id = object_store_id
@@ -146,7 +154,7 @@ class DatasetManager(
"Cannot swap object store IDs for object stores that don't share a device ID."
)
if not self.app.security_agent.can_change_object_store_id(trans.user, dataset):
if not self.app.security_agent.can_change_object_store_id(user, dataset):
# TODO: probably want separate exceptions for doesn't own the dataset and dataset
# has been shared.
raise exceptions.InsufficientPermissionsException("Cannot change dataset permissions...")
@@ -228,7 +236,7 @@ class DatasetRBACPermissions:
self.manage = rbac_secured.ManageDatasetRBACPermission(app)
# TODO: temporary facade over security_agent
def available_roles(self, trans, dataset, controller="root"):
def available_roles(self, trans: ProvidesUserContext, dataset, controller="root"):
return self.app.security_agent.get_legitimate_roles(trans, dataset, controller)
def get(self, dataset, flush=True):
@@ -494,7 +502,7 @@ class DatasetAssociationManager(
rval["modify_item_roles"] = role_name_id_pairs(modify_roles, private_role_emails, encode_id)
return rval
def ensure_dataset_on_disk(self, trans, dataset: U):
def ensure_dataset_on_disk(self, trans: ProvidesUserContext, dataset: U):
# Not a guarantee data is really present, but excludes a lot of expected cases
if not dataset.dataset:
raise exceptions.InternalServerError("Item has no associated dataset.")
@@ -546,7 +554,7 @@ class DatasetAssociationManager(
)
return True
def detect_datatype(self, trans, dataset_assoc: U):
def detect_datatype(self, trans: ProvidesHistoryContext, dataset_assoc: U):
"""Sniff and assign the datatype to a given dataset association (ldda or hda)"""
session = self.session()
self.ensure_can_change_datatype(dataset_assoc)
@@ -558,7 +566,9 @@ class DatasetAssociationManager(
session.commit()
self.set_metadata(trans, dataset_assoc)
def set_metadata(self, trans, dataset_assoc: U, overwrite: bool = False, validate: bool = True) -> None:
def set_metadata(
self, trans: ProvidesHistoryContext, dataset_assoc: U, overwrite: bool = False, validate: bool = True
) -> None:
"""Trigger a job that detects and sets metadata on a given dataset association (ldda or hda)"""
self.ensure_can_set_metadata(dataset_assoc)
if overwrite:
@@ -579,7 +589,7 @@ class DatasetAssociationManager(
if spec.get("default"):
setattr(data.metadata, name, spec.unwrap(spec.get("default")))
def update_permissions(self, trans, dataset_assoc: U, **kwd):
def update_permissions(self, trans: ProvidesUserContext, dataset_assoc: U, **kwd):
action = kwd.get("action", "set_permissions")
if action not in ["remove_restrictions", "make_private", "set_permissions"]:
raise exceptions.RequestParameterInvalidException(
@@ -632,7 +642,7 @@ class DatasetAssociationManager(
self._set_permissions(trans, dataset_assoc, role_ids_dict)
def _set_permissions(self, trans, dataset_assoc: U, roles_dict):
def _set_permissions(self, trans: ProvidesUserContext, dataset_assoc: U, roles_dict):
raise exceptions.NotImplemented()
+3 -1
View File
@@ -204,7 +204,9 @@ class DisplayApplicationsManager:
)
raise MessageException("You do not have permission to view this dataset at an external display application.")
def _can_access_dataset(self, trans, dataset_association, allow_admin=True, additional_roles=None):
def _can_access_dataset(
self, trans: ProvidesUserContext, dataset_association, allow_admin=True, additional_roles=None
):
roles = trans.get_current_user_roles()
if additional_roles:
roles = roles + additional_roles
+3 -2
View File
@@ -7,15 +7,16 @@ from typing import (
import yaml
from galaxy import exceptions
from galaxy.managers.context import ProvidesUserContext
from galaxy.util import in_directory
def artifact_class(trans, as_dict: dict[str, Any], allow_in_directory: str | None = None):
def artifact_class(trans: ProvidesUserContext | None, as_dict: dict[str, Any], allow_in_directory: str | None = None):
object_id = as_dict.get("object_id", None)
if as_dict.get("src", None) == "from_path":
workflow_path = as_dict.get("path")
allow = not trans or trans.user_is_admin
allow = allow or (allow_in_directory and in_directory(workflow_path, allow_in_directory))
allow = allow or bool(allow_in_directory and in_directory(workflow_path, allow_in_directory))
if not allow:
raise exceptions.AdminRequiredException()
+1 -1
View File
@@ -563,7 +563,7 @@ class FileSourceInstancesManager:
def _save(self, user_file_source: UserFileSource) -> None:
save_template_instance(self._sa_session, user_file_source)
def _to_model(self, trans, persisted_file_source: UserFileSource) -> UserFileSourceModel:
def _to_model(self, trans: ProvidesUserContext, persisted_file_source: UserFileSource) -> UserFileSourceModel:
file_source_type = persisted_file_source.template.configuration.type
secrets = persisted_file_source.template_secrets or []
uuid = str(persisted_file_source.uuid)
+23 -14
View File
@@ -54,7 +54,10 @@ from galaxy.schema.schema import LibraryFolderContentsIndexQueryPayload
from galaxy.security import RBACAgent
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
log = logging.getLogger(__name__)
@@ -63,7 +66,7 @@ log = logging.getLogger(__name__)
class SecurityParams:
"""Contains security data bundled for reusability."""
user_role_ids: list[model.Role]
user_role_ids: list[int]
security_agent: RBACAgent
is_admin: bool
@@ -148,7 +151,7 @@ class FolderManager:
folder = self.check_accessible(trans, folder)
return folder
def check_modifyable(self, trans, folder):
def check_modifyable(self, trans: "ProvidesUserContext", folder):
"""
Check whether the user can modify the folder (name and description).
@@ -165,7 +168,7 @@ class FolderManager:
else:
return folder
def check_manageable(self, trans, folder):
def check_manageable(self, trans: "ProvidesUserContext", folder):
"""
Check whether the user can manage the folder.
@@ -182,14 +185,14 @@ class FolderManager:
else:
return folder
def check_accessible(self, trans, folder):
def check_accessible(self, trans: "ProvidesUserContext", folder):
"""
Check whether the folder is accessible to current user.
By default every folder is accessible (contents have their own permissions).
"""
return folder
def get_folder_dict(self, trans, folder):
def get_folder_dict(self, trans: "ProvidesUserContext", folder):
"""
Return folder data in the form of a dictionary.
@@ -204,7 +207,13 @@ class FolderManager:
folder_dict["update_time"] = folder.update_time
return folder_dict
def create(self, trans, parent_folder_id: int, new_folder_name: str, new_folder_description: str | None = None):
def create(
self,
trans: "ProvidesUserContext",
parent_folder_id: int,
new_folder_name: str,
new_folder_description: str | None = None,
):
"""
Create a new folder under the given folder.
@@ -240,7 +249,7 @@ class FolderManager:
trans.app.security_agent.copy_library_permissions(trans, parent_folder, new_folder)
return new_folder
def update(self, trans, folder, name=None, description=None):
def update(self, trans: "ProvidesUserContext", folder, name=None, description=None):
"""
Update the given folder's name or description.
@@ -272,7 +281,7 @@ class FolderManager:
trans.sa_session.commit()
return folder
def delete(self, trans, folder, undelete=False):
def delete(self, trans: "ProvidesUserContext", folder, undelete=False):
"""
Mark given folder deleted/undeleted based on the flag.
@@ -296,7 +305,7 @@ class FolderManager:
trans.sa_session.commit()
return folder
def get_current_roles(self, trans, folder):
def get_current_roles(self, trans: "ProvidesUserContext", folder):
"""
Find all roles currently connected to relevant permissions
on the folder.
@@ -332,7 +341,7 @@ class FolderManager:
add_library_item_role_list=role_name_id_pairs(add_roles, private_role_emails, encode_id),
)
def can_add_item(self, trans, folder):
def can_add_item(self, trans: "ProvidesUserContext", folder):
"""
Return true if the user has permissions to add item to the given folder.
"""
@@ -367,7 +376,7 @@ class FolderManager:
raise MalformedId(f"Malformed folder id ( {str(encoded_folder_id)} ) specified, unable to decode.")
return cut_id
def decode_folder_id(self, trans, encoded_folder_id):
def decode_folder_id(self, trans: "ProvidesAppContext", encoded_folder_id):
"""
Decode the folder id given that it has already lost the prefixed 'F'.
@@ -381,7 +390,7 @@ class FolderManager:
"""
return trans.security.decode_id(encoded_folder_id, object_name="folder")
def cut_and_decode(self, trans, encoded_folder_id):
def cut_and_decode(self, trans: "ProvidesAppContext", encoded_folder_id):
"""
Cuts the folder prefix (the prepended 'F') and returns the decoded id.
@@ -395,7 +404,7 @@ class FolderManager:
def get_contents(
self,
trans,
trans: "ProvidesUserContext",
folder: LibraryFolder,
payload: LibraryFolderContentsIndexQueryPayload,
) -> tuple[list[LibraryFolder | LibraryDataset], int]:
+6 -1
View File
@@ -1,3 +1,5 @@
from typing import TYPE_CHECKING
from sqlalchemy import select
from sqlalchemy.exc import (
MultipleResultsFound,
@@ -17,6 +19,9 @@ from galaxy.model import (
)
from galaxy.util import unicodify
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
def get_form_definitions(session):
stmt = select(FormDefinition)
@@ -33,7 +38,7 @@ def get_filtered_form_definitions_current(session, filter):
return session.scalars(stmt)
def get_form(trans, form_id):
def get_form(trans: "GalaxyWebTransaction", form_id):
"""Get a FormDefinition from the database by id."""
form = trans.sa_session.query(FormDefinitionCurrent).get(trans.security.decode_id(form_id))
if not form:
+5 -2
View File
@@ -12,7 +12,10 @@ from galaxy.exceptions import (
ReferenceDataError,
RequestParameterInvalidException,
)
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import User
from galaxy.model.database_utils import is_postgres
from galaxy.structured_app import (
@@ -41,7 +44,7 @@ class GenomesManager:
return False
def get_genome(
self, trans: ProvidesUserContext, id: str, num: int, chrom: str, low: int, high: int, reference: bool
self, trans: ProvidesHistoryContext, id: str, num: int, chrom: str, low: int, high: int, reference: bool
) -> Any:
if reference:
region = self.genomes.reference(trans, dbkey=id, chrom=chrom, low=low, high=high)
+6 -5
View File
@@ -19,6 +19,7 @@ from galaxy.schema.groups import (
GroupUpdatePayload,
)
from galaxy.structured_app import MinimalManagerApp
from galaxy.work.context import SessionRequestContext
class GroupsManager:
@@ -27,7 +28,7 @@ class GroupsManager:
def __init__(self, app: MinimalManagerApp) -> None:
self._app = app
def index(self, trans: ProvidesAppContext):
def index(self, trans: SessionRequestContext):
"""
Displays a collection (list) of groups.
"""
@@ -39,7 +40,7 @@ class GroupsManager:
rval.append(item)
return rval
def create(self, trans: ProvidesAppContext, payload: GroupCreatePayload):
def create(self, trans: SessionRequestContext, payload: GroupCreatePayload):
"""
Creates a new group.
"""
@@ -73,7 +74,7 @@ class GroupsManager:
item["url"] = self._url_for(trans, "group", id=encoded_id)
return [item]
def show(self, trans: ProvidesAppContext, group_id: int):
def show(self, trans: SessionRequestContext, group_id: int):
"""
Displays information about a group.
"""
@@ -85,7 +86,7 @@ class GroupsManager:
item["roles_url"] = self._url_for(trans, "group_roles", group_id=encoded_id)
return item
def update(self, trans: ProvidesAppContext, group_id: int, payload: GroupUpdatePayload):
def update(self, trans: SessionRequestContext, group_id: int, payload: GroupUpdatePayload):
"""
Modifies a group.
"""
@@ -138,7 +139,7 @@ class GroupsManager:
trans.sa_session.add(group)
trans.sa_session.commit()
def _url_for(self, trans, name, **kwargs):
def _url_for(self, trans: SessionRequestContext, name, **kwargs):
return trans.url_builder(name, **kwargs)
def _check_duplicated_group_name(self, sa_session: galaxy_scoped_session, group_name: str) -> None:
+15 -5
View File
@@ -44,7 +44,11 @@ from galaxy.managers import (
taggable,
users,
)
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
HistoryDatasetAssociation,
HistoryDatasetCollectionAssociation,
@@ -79,7 +83,10 @@ from galaxy.tool_util_models.parameters import (
FileRequestUri,
)
from galaxy.util.compression_utils import get_fileobj
from galaxy.work.context import WorkRequestContext
from galaxy.work.context import (
SessionRequestContext,
WorkRequestContext,
)
if TYPE_CHECKING:
from galaxy.model import LibraryDatasetDatasetAssociation
@@ -339,7 +346,7 @@ class HDAManager(
# override to scope to history owner
return self._user_annotation(hda, hda.user)
def _set_permissions(self, trans, hda, role_ids_dict):
def _set_permissions(self, trans: ProvidesUserContext, hda, role_ids_dict):
# The user associated the DATASET_ACCESS permission on the dataset with 1 or more roles. We
# need to ensure that they did not associate roles that would cause accessibility problems.
security_agent = trans.app.security_agent
@@ -657,7 +664,7 @@ class HDASerializer( # datasets._UnflattenedMetadataDatasetAssociationSerialize
keys = self._view_to_keys("inaccessible")
return super().serialize(item, keys, user=user, **context)
def serialize_display_apps(self, item, key, trans=None, **context):
def serialize_display_apps(self, item, key, trans: ProvidesAppContext | None = None, **context):
"""
Return dictionary containing new-style display app urls.
"""
@@ -681,7 +688,9 @@ class HDASerializer( # datasets._UnflattenedMetadataDatasetAssociationSerialize
return display_apps
def serialize_old_display_applications(self, item, key, trans=None, **context):
# trans arrives through the serializer dispatch's **context, so it has to be optional here;
# a request context is required in practice, for trans.request.base.
def serialize_old_display_applications(self, item, key, trans: "SessionRequestContext | None" = None, **context):
"""
Return dictionary containing old-style display app urls.
"""
@@ -692,6 +701,7 @@ class HDASerializer( # datasets._UnflattenedMetadataDatasetAssociationSerialize
and hda.state == HistoryDatasetAssociation.states.OK
and not hda.deleted
):
assert trans is not None
display_link_fn = hda.datatype.get_display_links
for display_app in hda.datatype.get_display_types():
target_frame, display_links = display_link_fn(
+48 -18
View File
@@ -12,6 +12,7 @@ from typing import (
cast,
Literal,
TYPE_CHECKING,
Union,
)
from uuid import UUID
@@ -46,7 +47,10 @@ from galaxy.managers.base import (
SortableManager,
StorageCleanerManager,
)
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.managers.export_tracker import StoreExportTracker
from galaxy.model import (
History,
@@ -87,10 +91,17 @@ from galaxy.util.search import (
parse_filters_structured,
RawTextTerm,
)
from galaxy.work.context import SessionRequestContext
if TYPE_CHECKING:
from sqlalchemy.engine import ScalarResult
from galaxy.webapps.base.webapp import GalaxyWebTransaction
# "Current history" is a request/session-scoped concept: only trans objects backed by
# a session (FastAPI SessionRequestContext) or the legacy web transaction implement it.
CurrentHistoryContext = Union[SessionRequestContext, "GalaxyWebTransaction"]
log = logging.getLogger(__name__)
INDEX_SEARCH_FILTERS = {
@@ -321,14 +332,14 @@ class HistoryManager(sharable.SharableModelManager[model.History], deletable.Pur
# def is_current_users_current_history( self, history, trans ):
# pass
def get_current(self, trans):
def get_current(self, trans: CurrentHistoryContext):
"""
Return the current history.
"""
# TODO: trans
return trans.get_history()
def set_current(self, trans, history):
def set_current(self, trans: CurrentHistoryContext, history):
"""
Set the current history.
"""
@@ -336,7 +347,7 @@ class HistoryManager(sharable.SharableModelManager[model.History], deletable.Pur
trans.set_history(history)
return history
def set_current_by_id(self, trans, history_id):
def set_current_by_id(self, trans: CurrentHistoryContext, history_id):
"""
Set the current history by an id.
"""
@@ -382,16 +393,17 @@ class HistoryManager(sharable.SharableModelManager[model.History], deletable.Pur
stmt = select(Job).where(Job.history == history).where(Job.state.in_(Job.non_ready_states))
return self.session().scalars(stmt)
def queue_history_import(self, trans, archive_type, archive_source, target_history=None):
def queue_history_import(self, trans: ProvidesHistoryContext, archive_type, archive_source, target_history=None):
# Run job to do import.
history_imp_tool = trans.app.toolbox.get_tool("__IMPORT_HISTORY__")
assert history_imp_tool is not None
incoming = {"__ARCHIVE_SOURCE__": archive_source, "__ARCHIVE_TYPE__": archive_type}
job, *_ = history_imp_tool.execute(trans, incoming=incoming, history=target_history)
trans.app.job_manager.enqueue(job, tool=history_imp_tool)
return job
# TODO: remove this function when the legacy endpoint using it is removed
def legacy_serve_ready_history_export(self, trans, jeha):
def legacy_serve_ready_history_export(self, trans: "GalaxyWebTransaction", jeha):
assert jeha.ready
if jeha.compressed:
trans.response.set_content_type("application/x-gzip")
@@ -402,7 +414,7 @@ class HistoryManager(sharable.SharableModelManager[model.History], deletable.Pur
archive = trans.app.object_store.get_filename(jeha.dataset, auth=ObjectStoreAuth(user=trans.user))
return open(archive, mode="rb")
def get_ready_history_export_file_path(self, trans, jeha) -> str:
def get_ready_history_export_file_path(self, trans: ProvidesUserContext, jeha) -> str:
"""
Serves the history export archive for use as a streaming response so the file
doesn't need to be loaded into memory.
@@ -411,7 +423,14 @@ class HistoryManager(sharable.SharableModelManager[model.History], deletable.Pur
return trans.app.object_store.get_filename(jeha.dataset, auth=ObjectStoreAuth(user=trans.user))
def queue_history_export(
self, trans, history, gzip=True, include_hidden=False, include_deleted=False, directory_uri=None, file_name=None
self,
trans: ProvidesHistoryContext,
history,
gzip=True,
include_hidden=False,
include_deleted=False,
directory_uri=None,
file_name=None,
):
# Convert options to booleans.
if isinstance(gzip, str):
@@ -437,12 +456,18 @@ class HistoryManager(sharable.SharableModelManager[model.History], deletable.Pur
# Run job to do export.
history_exp_tool = trans.app.toolbox.get_tool(export_tool_id)
assert history_exp_tool is not None
job, *_ = history_exp_tool.execute(trans, incoming=params, history=history)
trans.app.job_manager.enqueue(job, tool=history_exp_tool)
return job
def get_sharing_extra_information(
self, trans, item, users: set[model.User], errors: set[str], option: sharable.SharingOptions | None = None
self,
trans: ProvidesUserContext,
item,
users: set[model.User],
errors: set[str],
option: sharable.SharingOptions | None = None,
) -> ShareHistoryExtra:
"""Returns optional extra information about the datasets of the history that can be accessed by the users."""
extra = ShareHistoryExtra()
@@ -505,7 +530,7 @@ class HistoryManager(sharable.SharableModelManager[model.History], deletable.Pur
)
return bool(self.session().scalar(stmt))
def make_members_public(self, trans, item):
def make_members_public(self, trans: ProvidesUserContext, item):
"""Make the non-purged datasets in history public.
Performs permissions check.
"""
@@ -686,7 +711,9 @@ class HistoryExportManager:
self.app = app
self.export_tracker = export_tracker
def get_task_exports(self, trans, history_id: int, limit: int | None = None, offset: int | None = None):
def get_task_exports(
self, trans: ProvidesHistoryContext, history_id: int, limit: int | None = None, offset: int | None = None
):
"""Returns task-based exports associated with this history"""
history = self._history(trans, history_id)
export_associations = self.export_tracker.get_object_exports(
@@ -759,17 +786,18 @@ class HistoryExportManager:
"export_metadata": export_metadata,
}
def get_exports(self, trans, history_id: int):
def get_exports(self, trans: ProvidesHistoryContext, history_id: int):
"""Returns job-based exports associated with this history"""
history = self._history(trans, history_id)
matching_exports = history.exports
return [self.serialize(trans, history_id, e) for e in matching_exports]
def serialize(self, trans, history_id: int, jeha: model.JobExportHistoryArchive) -> dict:
def serialize(self, trans: ProvidesHistoryContext, history_id: int, jeha: model.JobExportHistoryArchive) -> dict:
rval = jeha.to_dict()
rval["type"] = "job"
encoded_jeha_id = Security.security.encode_id(jeha.id)
encoded_history_id = Security.security.encode_id(history_id)
assert trans.url_builder
api_url = trans.url_builder("history_archive_download", history_id=encoded_history_id, jeha_id=encoded_jeha_id)
external_url = trans.url_builder(
"history_archive_download", history_id=encoded_history_id, jeha_id="latest", qualified=True
@@ -783,7 +811,9 @@ class HistoryExportManager:
rval = trans.security.encode_all_ids(rval)
return rval
def get_ready_jeha(self, trans, history_id: int, jeha_id: int | Literal["latest"] = "latest"):
def get_ready_jeha(
self, trans: ProvidesHistoryContext, history_id: int, jeha_id: int | Literal["latest"] = "latest"
):
history = self._history(trans, history_id)
matching_exports = history.exports
if jeha_id != "latest":
@@ -797,7 +827,7 @@ class HistoryExportManager:
return jeha
def _history(self, trans, history_id: int) -> model.History:
def _history(self, trans: ProvidesHistoryContext, history_id: int) -> model.History:
history = self.app.history_manager.get_accessible(history_id, trans.user, current_history=trans.history)
return history
@@ -995,7 +1025,7 @@ class HistorySerializer(sharable.SharableModelSerializer, deletable.PurgableSeri
return state
def serialize_contents(self, item, key, trans=None, user=None, **context):
def serialize_contents(self, item, key, trans: ProvidesHistoryContext | None = None, user=None, **context):
history = item
returned = []
for content in self.manager.contents_manager._union_of_contents_query(history).all():
@@ -1005,7 +1035,7 @@ class HistorySerializer(sharable.SharableModelSerializer, deletable.PurgableSeri
returned.append(serialized)
return returned
def serialize_contents_states(self, item, key, trans=None, **context):
def serialize_contents_states(self, item, key, trans: ProvidesHistoryContext | None = None, **context):
"""
Return a dictionary containing the counts of all contents in each state
keyed by the distinct states.
@@ -1053,7 +1083,7 @@ class HistoryDeserializer(sharable.SharableModelDeserializer, deletable.Purgable
def deserialize_preferred_object_store_id(self, item, key, val, **context):
preferred_object_store_id = val
validation_error = validate_preferred_object_store_id(
context["trans"], self.app.object_store, preferred_object_store_id
context["trans"].user, self.app.object_store, preferred_object_store_id
)
if validation_error:
raise ModelDeserializingError(validation_error)
+4 -1
View File
@@ -45,6 +45,7 @@ from galaxy.managers import (
taggable,
tools,
)
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.managers.job_connections import JobConnectionsManager
from galaxy.model import batch_fetch_job_state_summaries
from galaxy.schema import ValueFilterQueryParams
@@ -242,7 +243,9 @@ class HistoryContentsManager(base.SortableManager):
.filter_by(history_id=history_id)
)
def copy_contents(self, trans, history_id, payload: CopyDatasetsPayload) -> CopyDatasetsResponse:
def copy_contents(
self, trans: ProvidesHistoryContext, history_id, payload: CopyDatasetsPayload
) -> CopyDatasetsResponse:
user = trans.get_user()
if not user:
raise glx_exceptions.MessageException("Please login to copy datasets between histories.")
+3 -2
View File
@@ -38,6 +38,7 @@ if TYPE_CHECKING:
from galaxy.managers.context import ProvidesUserContext
from galaxy.structured_app import MinimalManagerApp
from galaxy.tools import Tool
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -301,7 +302,7 @@ class InteractiveToolManager:
self.sa_session.commit()
self.propagator.remove_entry_point(entry_point)
def target_if_active(self, trans, entry_point: InteractiveToolEntryPoint) -> str | None:
def target_if_active(self, trans: "GalaxyWebTransaction", entry_point: InteractiveToolEntryPoint) -> str | None:
if entry_point.active and not entry_point.deleted:
use_it_proxy_host_cfg = (
not self.app.config.interactivetools_upstream_proxy and self.app.config.interactivetools_proxy_host
@@ -354,7 +355,7 @@ class InteractiveToolManager:
url_path += entry_point.entry_url.lstrip("/")
return url_path
def access_entry_point_target(self, trans: "ProvidesUserContext", entry_point_id: int) -> str | None:
def access_entry_point_target(self, trans: "GalaxyWebTransaction", entry_point_id: int) -> str | None:
entry_point = self.sa_session.get(InteractiveToolEntryPoint, entry_point_id)
assert entry_point
if self.can_access_entry_point(trans, entry_point):
+14 -11
View File
@@ -3,7 +3,10 @@ from galaxy.exceptions import (
ObjectNotFound,
)
from galaxy.managers import base
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
from galaxy.schema.fields import DecodedDatabaseIdField
from galaxy.schema.item_tags import ItemTagsCreatePayload
from galaxy.structured_app import MinimalManagerApp
@@ -16,13 +19,13 @@ class ItemTagsManager:
self._app = app
self._tag_handler = app.tag_handler
def index(self, trans: ProvidesAppContext, tagged_item_class: str, tagged_item_id: DecodedDatabaseIdField):
def index(self, trans: ProvidesUserContext, tagged_item_class: str, tagged_item_id: DecodedDatabaseIdField):
"""Displays a collection (list) of tags associated with an item."""
tags = self._get_user_tags(trans, tagged_item_class, tagged_item_id)
return [self._api_value(tag, trans, view="collection") for tag in tags]
def show(
self, trans: ProvidesAppContext, tagged_item_class: str, tagged_item_id: DecodedDatabaseIdField, tag_name: str
self, trans: ProvidesUserContext, tagged_item_class: str, tagged_item_id: DecodedDatabaseIdField, tag_name: str
):
"""Displays information about a tag associated with an item."""
tag = self._get_item_tag_assoc(trans, tagged_item_class, tagged_item_id, tag_name)
@@ -32,7 +35,7 @@ class ItemTagsManager:
def create(
self,
trans: ProvidesAppContext,
trans: ProvidesUserContext,
tagged_item_class: str,
tagged_item_id: DecodedDatabaseIdField,
tag_name: str,
@@ -44,7 +47,7 @@ class ItemTagsManager:
return self._api_value(tag, trans)
def delete(
self, trans: ProvidesAppContext, tagged_item_class: str, tagged_item_id: DecodedDatabaseIdField, tag_name: str
self, trans: ProvidesUserContext, tagged_item_class: str, tagged_item_id: DecodedDatabaseIdField, tag_name: str
):
"""Remove a tag from an item."""
deleted = self._remove_items_tag(trans, tagged_item_class, tagged_item_id, tag_name)
@@ -52,35 +55,35 @@ class ItemTagsManager:
raise NoContentException("Failed to delete specified tag.")
return deleted
def _get_tagged_item(self, trans, item_class_name, id, check_ownership=True):
def _get_tagged_item(self, trans: ProvidesUserContext, item_class_name, id, check_ownership=True):
tagged_item = base.get_object(
trans, id, item_class_name, check_ownership=check_ownership, check_accessible=True
)
return tagged_item
def _get_user_tags(self, trans, item_class_name, id):
def _get_user_tags(self, trans: ProvidesUserContext, item_class_name, id):
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
return [tag for tag in tagged_item.tags if tag.user == user]
def _remove_items_tag(self, trans, item_class_name, id, tag_name):
def _remove_items_tag(self, trans: ProvidesUserContext, item_class_name, id, tag_name):
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
deleted = tagged_item and self._tag_handler.remove_item_tag(user, tagged_item, tag_name)
trans.sa_session.commit()
return deleted
def _apply_item_tag(self, trans, item_class_name, id, tag_name, tag_value=None):
def _apply_item_tag(self, trans: ProvidesUserContext, item_class_name, id, tag_name, tag_value=None):
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
tag_assoc = self._tag_handler.apply_item_tag(user, tagged_item, tag_name, tag_value)
trans.sa_session.commit()
return tag_assoc
def _get_item_tag_assoc(self, trans, item_class_name, id, tag_name):
def _get_item_tag_assoc(self, trans: ProvidesUserContext, item_class_name, id, tag_name):
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
return self._tag_handler._get_item_tag_assoc(user, tagged_item, tag_name)
def _api_value(self, tag, trans, view="element"):
def _api_value(self, tag, trans: ProvidesAppContext, view="element"):
return tag.to_dict(view=view, value_mapper={"id": trans.security.encode_id})
+10 -6
View File
@@ -50,7 +50,11 @@ from galaxy.job_metrics import (
Safety,
)
from galaxy.managers.collections import DatasetCollectionManager
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.managers.datasets import DatasetManager
from galaxy.managers.hdas import (
dereference_input_to_hda,
@@ -384,7 +388,7 @@ class JobManager:
return job.history is not None and self.history_manager.is_accessible(job.history, user)
def get_job_console_output(
self, trans, job, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0
self, trans: ProvidesAppContext, job, stdout_position=-1, stdout_length=0, stderr_position=-1, stderr_length=0
):
if job is None:
raise ObjectNotFound()
@@ -1517,7 +1521,7 @@ class JobSearch:
return stmt
def view_show_job(trans, job: Job, full: bool) -> dict:
def view_show_job(trans: ProvidesUserContext, job: Job, full: bool) -> dict:
is_admin = trans.user_is_admin
job_dict = job.to_dict("element", system_details=is_admin)
if trans.app.config.expose_dataset_path and "command_line" not in job_dict:
@@ -1900,7 +1904,7 @@ def summarize_jobs_to_dict(sa_session, jobs_source) -> JobsSummary | None:
return rval
def summarize_job_metrics(trans, job):
def summarize_job_metrics(trans: ProvidesUserContext, job):
"""Produce a dict-ified version of job metrics ready for tabular rendering.
Precondition: the caller has verified the job is accessible to the user
@@ -1927,7 +1931,7 @@ def summarize_metrics(trans: ProvidesUserContext, job_metrics):
return [d.dict() for d in dictifiable_metrics]
def summarize_destination_params(trans, job):
def summarize_destination_params(trans: ProvidesUserContext, job):
"""Produce a dict-ified version of job destination parameters ready for tabular rendering.
Precondition: the caller has verified the job is accessible to the user
@@ -1944,7 +1948,7 @@ def summarize_destination_params(trans, job):
return destination_params
def summarize_job_parameters(trans: ProvidesUserContext, job: Job) -> dict[str, Any]:
def summarize_job_parameters(trans: ProvidesHistoryContext, job: Job) -> dict[str, Any]:
"""Produce a dict-ified version of job parameters ready for tabular rendering.
Precondition: the caller has verified the job is accessible to the user
+7 -4
View File
@@ -55,7 +55,10 @@ from galaxy.tool_util_models.parameters import (
ToolParameterBundleModel,
)
from galaxy.util import safe_str_cmp
from .context import ProvidesUserContext
from .context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from .headers_encryption import (
decrypt_headers_in_data,
encrypt_headers_in_data,
@@ -237,7 +240,7 @@ class LandingRequestManager:
return self._tool_response(request)
def claim_workflow_landing_request(
self, trans: ProvidesUserContext, uuid: UUID4, claim: ClaimLandingPayload | None
self, trans: ProvidesHistoryContext, uuid: UUID4, claim: ClaimLandingPayload | None
) -> WorkflowLandingRequest:
request = self._get_workflow_landing_request(uuid)
self._check_can_claim(trans, request, claim)
@@ -246,7 +249,7 @@ class LandingRequestManager:
self._save(request)
return self._workflow_response(request)
def _ensure_workflow(self, trans: ProvidesUserContext, request: WorkflowLandingRequestModel):
def _ensure_workflow(self, trans: ProvidesHistoryContext, request: WorkflowLandingRequestModel):
if request.workflow_source_type == "trs_url" and isinstance(trans.app, StructuredApp):
# trans is always structured app except for unit test
assert request.workflow_source
@@ -266,7 +269,7 @@ class LandingRequestManager:
request = self._get_claimed_tool_landing_request(trans, uuid)
return self._tool_response(request)
def get_workflow_landing_request(self, trans: ProvidesUserContext, uuid: UUID4) -> WorkflowLandingRequest:
def get_workflow_landing_request(self, trans: ProvidesHistoryContext, uuid: UUID4) -> WorkflowLandingRequest:
request = self._get_claimed_workflow_landing_request(trans, uuid)
self._ensure_workflow(trans, request)
return self._workflow_response(request)
+3 -2
View File
@@ -4,6 +4,7 @@ from typing import (
)
from galaxy.managers import base as manager_base
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.datasets import DatasetAssociationManager
from galaxy.model import (
LibraryDatasetDatasetAssociation,
@@ -27,7 +28,7 @@ class LDDAManager(DatasetAssociationManager[LibraryDatasetDatasetAssociation]):
"""
super().__init__(app)
def get(self, trans, id: int, check_accessible=True) -> LibraryDatasetDatasetAssociation:
def get(self, trans: ProvidesUserContext, id: int, check_accessible=True) -> LibraryDatasetDatasetAssociation:
return manager_base.get_object(
trans, id, "LibraryDatasetDatasetAssociation", check_ownership=False, check_accessible=check_accessible
)
@@ -41,7 +42,7 @@ class LDDAManager(DatasetAssociationManager[LibraryDatasetDatasetAssociation]):
return True
return item.user == user
def _set_permissions(self, trans, library_dataset, role_ids_dict):
def _set_permissions(self, trans: ProvidesUserContext, library_dataset, role_ids_dict):
# Check Git history for an older broken implementation, but it was broken
# and security related and had not test coverage so it was deleted.
raise NotImplementedError()
+24 -16
View File
@@ -11,6 +11,10 @@ from sqlalchemy.exc import (
from sqlalchemy.orm import Query
from galaxy import exceptions
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
from galaxy.managers.folders import FolderManager
from galaxy.model import (
Library,
@@ -42,7 +46,7 @@ class LibraryManager:
Interface/service object for interacting with libraries.
"""
def get(self, trans, decoded_library_id: int, check_accessible: bool = True) -> Library:
def get(self, trans: ProvidesUserContext, decoded_library_id: int, check_accessible: bool = True) -> Library:
"""
Get the library from the DB.
@@ -65,7 +69,9 @@ class LibraryManager:
library = self.secure(trans, library, check_accessible)
return library
def create(self, trans, name: str, description: str | None = "", synopsis: str | None = "") -> Library:
def create(
self, trans: ProvidesUserContext, name: str, description: str | None = "", synopsis: str | None = ""
) -> Library:
"""
Create a new library.
"""
@@ -81,7 +87,7 @@ class LibraryManager:
def update(
self,
trans,
trans: ProvidesUserContext,
library: Library,
name: str | None = None,
description: str | None = None,
@@ -116,7 +122,7 @@ class LibraryManager:
trans.sa_session.commit()
return library
def delete(self, trans, library: Library, undelete: bool | None = False) -> Library:
def delete(self, trans: ProvidesUserContext, library: Library, undelete: bool | None = False) -> Library:
"""
Mark given library deleted/undeleted based on the flag.
"""
@@ -130,7 +136,7 @@ class LibraryManager:
trans.sa_session.commit()
return library
def list(self, trans, deleted: bool | None = False) -> tuple[Query, dict[str, set]]:
def list(self, trans: ProvidesUserContext, deleted: bool | None = False) -> tuple[Query, dict[str, set]]:
"""
Return a list of libraries from the DB.
@@ -182,7 +188,7 @@ class LibraryManager:
return libraries, prefetched_ids
def secure(self, trans, library: Library, check_accessible: bool = True) -> Library:
def secure(self, trans: ProvidesUserContext, library: Library, check_accessible: bool = True) -> Library:
"""
Check if library is accessible to user.
@@ -201,7 +207,7 @@ class LibraryManager:
library = self.check_accessible(trans, library)
return library
def check_accessible(self, trans, library: Library) -> Library:
def check_accessible(self, trans: ProvidesUserContext, library: Library) -> Library:
"""
Check whether the library is accessible to current user.
"""
@@ -212,7 +218,9 @@ class LibraryManager:
else:
return library
def get_library_dict(self, trans, library: Library, prefetched_ids: dict[str, set] | None = None) -> dict:
def get_library_dict(
self, trans: ProvidesUserContext, library: Library, prefetched_ids: dict[str, set] | None = None
) -> dict:
"""
Return library data in the form of a dictionary.
@@ -262,7 +270,7 @@ class LibraryManager:
library_dict["can_user_manage"] = True
return library_dict
def get_current_roles(self, trans, library: Library) -> dict:
def get_current_roles(self, trans: ProvidesAppContext, library: Library) -> dict:
"""
Load all permissions currently related to the given library.
@@ -286,13 +294,13 @@ class LibraryManager:
add_library_item_role_list=role_name_id_pairs(add_roles, private_role_emails, encode_id),
)
def get_access_roles(self, trans, library: Library) -> set[Role]:
def get_access_roles(self, trans: ProvidesAppContext, library: Library) -> set[Role]:
"""
Load access roles for all library permissions
"""
return set(library.get_access_roles(trans.app.security_agent))
def get_modify_roles(self, trans, library: Library) -> set[Role]:
def get_modify_roles(self, trans: ProvidesAppContext, library: Library) -> set[Role]:
"""
Load modify roles for all library permissions
"""
@@ -302,7 +310,7 @@ class LibraryManager:
)
)
def get_manage_roles(self, trans, library: Library) -> set[Role]:
def get_manage_roles(self, trans: ProvidesAppContext, library: Library) -> set[Role]:
"""
Load manage roles for all library permissions
"""
@@ -312,7 +320,7 @@ class LibraryManager:
)
)
def get_add_roles(self, trans, library: Library) -> set[Role]:
def get_add_roles(self, trans: ProvidesAppContext, library: Library) -> set[Role]:
"""
Load add roles for all library permissions
"""
@@ -322,21 +330,21 @@ class LibraryManager:
)
)
def make_public(self, trans, library: Library) -> bool:
def make_public(self, trans: ProvidesAppContext, library: Library) -> bool:
"""
Makes the given library public (removes all access roles)
"""
trans.app.security_agent.make_library_public(library)
return self.is_public(trans, library)
def is_public(self, trans, library: Library) -> bool:
def is_public(self, trans: ProvidesAppContext, library: Library) -> bool:
"""
Return true if lib is public.
"""
return trans.app.security_agent.library_is_public(library)
def get_containing_library_from_library_dataset(trans, library_dataset) -> Library | None:
def get_containing_library_from_library_dataset(trans: ProvidesAppContext, library_dataset) -> Library | None:
"""Given a library_dataset, get the containing library"""
folder = library_dataset.folder
while folder.parent:
+12 -8
View File
@@ -15,7 +15,11 @@ from galaxy.exceptions import (
RequestParameterInvalidException,
)
from galaxy.managers.base import ModelManager
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.managers.lddas import LDDAManager
from galaxy.model import (
LibraryDataset,
@@ -37,7 +41,7 @@ class LibraryDatasetsManager(ModelManager[LibraryDataset]):
super().__init__(app)
self.ldda_manager = LDDAManager(app)
def get(self, trans, decoded_library_dataset_id, check_accessible=True) -> LibraryDataset:
def get(self, trans: ProvidesUserContext, decoded_library_dataset_id, check_accessible=True) -> LibraryDataset:
"""
Get the library dataset from the DB.
@@ -90,7 +94,7 @@ class LibraryDatasetsManager(ModelManager[LibraryDataset]):
def _set_from_dict(
self,
trans: ProvidesUserContext,
trans: ProvidesHistoryContext,
ldda: LibraryDatasetDatasetAssociation,
new_data: dict[str, Any],
flush: bool = True,
@@ -166,7 +170,7 @@ class LibraryDatasetsManager(ModelManager[LibraryDataset]):
validated_payload[key] = val
return validated_payload
def secure(self, trans, ld, check_accessible=True, check_ownership=False):
def secure(self, trans: ProvidesUserContext, ld, check_accessible=True, check_ownership=False):
"""
Check if library dataset is accessible to current user or the user is an admin.
@@ -185,7 +189,7 @@ class LibraryDatasetsManager(ModelManager[LibraryDataset]):
ld = self.check_accessible(trans, ld)
return ld
def check_accessible(self, trans, ld):
def check_accessible(self, trans: ProvidesUserContext, ld):
"""
Check whether the current user has permissions to access library dataset.
@@ -204,7 +208,7 @@ class LibraryDatasetsManager(ModelManager[LibraryDataset]):
else:
return ld
def check_modifiable(self, trans, ld):
def check_modifiable(self, trans: ProvidesUserContext, ld):
"""
Check whether the current user has permissions to modify library dataset.
@@ -225,7 +229,7 @@ class LibraryDatasetsManager(ModelManager[LibraryDataset]):
else:
return ld
def serialize(self, trans, ld: LibraryDataset) -> dict[str, Any]:
def serialize(self, trans: ProvidesUserContext, ld: LibraryDataset) -> dict[str, Any]:
"""Serialize the library dataset into a dictionary."""
current_user_roles = trans.get_current_user_roles()
@@ -271,7 +275,7 @@ class LibraryDatasetsManager(ModelManager[LibraryDataset]):
)
return rval
def _build_path(self, trans, folder):
def _build_path(self, trans: ProvidesAppContext, folder):
"""
Search the path upwards recursively and load the whole route of
names and ids for breadcrumb building purposes.
+26 -16
View File
@@ -20,9 +20,7 @@ import shutil
import tempfile
from datetime import datetime
from re import Match
from typing import (
Any,
)
from typing import Any
import markdown
@@ -37,6 +35,11 @@ from galaxy.exceptions import (
ObjectNotFound,
ServerNotConfiguredForRequest,
)
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.managers.jobs import (
JobManager,
summarize_job_metrics,
@@ -107,7 +110,7 @@ def process_invocation_ids(f, workflow_markdown: str) -> str:
return re.sub(VISUALIZATION_FENCED_BLOCK, process_block, workflow_markdown)
def ready_galaxy_markdown_for_import(trans, external_galaxy_markdown):
def ready_galaxy_markdown_for_import(trans: ProvidesAppContext, external_galaxy_markdown):
"""Convert from encoded IDs to decoded numeric IDs for storing in the DB."""
_validate(external_galaxy_markdown, internal=False)
@@ -138,11 +141,12 @@ def ready_galaxy_markdown_for_import(trans, external_galaxy_markdown):
class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
def walk(self, trans, internal_galaxy_markdown):
def walk(self, trans: ProvidesHistoryContext, internal_galaxy_markdown):
hda_manager = trans.app.hda_manager
history_manager = trans.app.history_manager
workflow_manager = trans.app.workflow_manager
job_manager = JobManager(trans.app, history_manager)
# not trans.app.job_manager, which is the job queue manager of the same name
job_manager = trans.app[JobManager]
collection_manager = trans.app.dataset_collection_manager
def _job_for_job_directive(object_type, object_id):
@@ -325,10 +329,12 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
if container == "history_dataset_type":
_check_object(object_id, match.group(0))
assert object_id is not None
hda = hda_manager.get_accessible(object_id, trans.user)
return hda.extension or "data"
elif container == "history_dataset_name":
_check_object(object_id, match.group(0))
assert object_id is not None
hda = hda_manager.get_accessible(object_id, trans.user)
return hda.name or ""
elif container == "workflow_license":
@@ -337,6 +343,7 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
return _workflow_license_as_simple_markdown(stored_workflow)
elif container == "invocation_time":
_check_object(object_id, match.group(0))
assert object_id is not None
invocation = workflow_manager.get_invocation(trans, object_id)
return _database_time_to_str(invocation.create_time)
elif container == "generate_time":
@@ -368,6 +375,7 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
return _link_to_markdown(url, title)
elif container == "history_dataset_as_image":
_check_object(object_id, match.group(0))
assert object_id is not None
hda = hda_manager.get_accessible(object_id, trans.user)
return f"![{hda.name}](gxdatasetasimage://{encoded_id})"
else:
@@ -394,7 +402,7 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
)
return export_markdown, export_markdown_embed_expanded
def _encode_line(self, trans, line):
def _encode_line(self, trans: ProvidesAppContext, line):
object_type = None
object_id = None
encoded_id = None
@@ -531,7 +539,7 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
class ReadyForExportMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHandler):
def __init__(self, trans, extra_rendering_data=None):
def __init__(self, trans: ProvidesHistoryContext, extra_rendering_data=None):
extra_rendering_data = extra_rendering_data if extra_rendering_data is not None else {}
self.trans = trans
self.extra_rendering_data = extra_rendering_data
@@ -646,7 +654,7 @@ class ReadyForExportMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHand
return (line, False)
def ready_galaxy_markdown_for_export(trans, internal_galaxy_markdown):
def ready_galaxy_markdown_for_export(trans: ProvidesHistoryContext, internal_galaxy_markdown):
"""Fill in details needed to render Galaxy flavored markdown.
Take it from a minimal internal version to an externally render-able version
@@ -668,7 +676,7 @@ def ready_galaxy_markdown_for_export(trans, internal_galaxy_markdown):
class ToBasicMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHandler):
def __init__(self, trans):
def __init__(self, trans: ProvidesHistoryContext):
self.trans = trans
def _format_printable_time(self, time):
@@ -918,7 +926,7 @@ class ToBasicMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHandler):
return (line, False)
def to_basic_markdown(trans, internal_galaxy_markdown: str) -> str:
def to_basic_markdown(trans: ProvidesHistoryContext, internal_galaxy_markdown: str) -> str:
"""Replace Galaxy Markdown extensions with plain Markdown for PDF/HTML export."""
directive_handler = ToBasicMarkdownDirectiveHandler(trans)
resolved_invocations_markdown = resolve_invocation_markdown(trans, internal_galaxy_markdown)
@@ -964,7 +972,9 @@ def _check_can_convert_to_pdf_or_raise():
raise ServerNotConfiguredForRequest("PDF conversion service not available.")
def internal_galaxy_markdown_to_pdf(trans, internal_galaxy_markdown: str, document_type: PdfDocumentType) -> bytes:
def internal_galaxy_markdown_to_pdf(
trans: ProvidesHistoryContext, internal_galaxy_markdown: str, document_type: PdfDocumentType
) -> bytes:
_check_can_convert_to_pdf_or_raise()
basic_markdown = to_basic_markdown(trans, internal_galaxy_markdown)
config = trans.app.config
@@ -1002,7 +1012,7 @@ def to_branded_pdf(basic_markdown: str, document_type: PdfDocumentType, config:
return to_pdf_raw(branded_markdown, css_paths=css_paths)
def populate_invocation_markdown(trans, invocation, workflow_markdown):
def populate_invocation_markdown(trans: ProvidesHistoryContext, invocation, workflow_markdown):
"""
Resolve invocation objects to convert markdown to 'internal' representation.
@@ -1074,7 +1084,7 @@ def populate_invocation_markdown(trans, invocation, workflow_markdown):
return galaxy_markdown
def resolve_invocation_markdown(trans, workflow_markdown):
def resolve_invocation_markdown(trans: ProvidesUserContext, workflow_markdown):
"""Resolve invocation objects to convert markdown to 'internal' representation.
Replace references to abstract workflow parts with actual galaxy object IDs corresponding
@@ -1089,7 +1099,7 @@ def resolve_invocation_markdown(trans, workflow_markdown):
Hopefully this list will be expanded to include invocation_qc and step_output.
"""
def get_invocation(trans, line):
def get_invocation(trans: ProvidesUserContext, line):
workflow_manager = trans.app.workflow_manager
if invocation_id_match := re.search(INVOCATION_ID_PATTERN, line):
invocation_id = int(invocation_id_match.group(1))
@@ -1249,7 +1259,7 @@ def resolve_invocation_markdown(trans, workflow_markdown):
return workflow_markdown
def resolve_job_markdown(trans, job, job_markdown):
def resolve_job_markdown(trans: ProvidesHistoryContext, job, job_markdown):
"""Resolve job objects to convert tool markdown to 'internal' representation.
Replace references to abstract workflow parts with actual galaxy object IDs corresponding
@@ -332,7 +332,9 @@ class ObjectStoreInstancesManager:
template = catalog.find_template_by(persisted_object_store.template_id, target_template_version)
return template
def _to_model(self, trans, persisted_object_store: UserObjectStore) -> UserConcreteObjectStoreModel:
def _to_model(
self, trans: ProvidesUserContext, persisted_object_store: UserObjectStore
) -> UserConcreteObjectStoreModel:
quota = QuotaModel(source=None, enabled=False)
object_store_type = persisted_object_store.template.configuration.type
admin_badges = persisted_object_store.template.configuration.badges or []
+10 -9
View File
@@ -34,6 +34,7 @@ from galaxy.managers import (
sharable,
)
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
@@ -269,7 +270,7 @@ class PageManager(sharable.SharableModelManager[model.Page], UsesAnnotations):
stmt = stmt.offset(payload.offset)
return trans.sa_session.scalars(stmt), total_matches
def create_page(self, trans, payload: CreatePagePayload):
def create_page(self, trans: ProvidesUserContext, payload: CreatePagePayload):
user = trans.get_user()
if not user:
raise exceptions.AuthenticationRequired("You must be logged in to create pages.")
@@ -341,7 +342,7 @@ class PageManager(sharable.SharableModelManager[model.Page], UsesAnnotations):
session.commit()
return page
def update_page(self, trans, id: int, payload: UpdatePagePayload):
def update_page(self, trans: ProvidesUserContext, id: int, payload: UpdatePagePayload):
user = trans.get_user()
if not user:
raise exceptions.AuthenticationRequired("You must be logged in to update pages.")
@@ -386,7 +387,7 @@ class PageManager(sharable.SharableModelManager[model.Page], UsesAnnotations):
session.commit()
return page
def save_new_revision(self, trans, page, payload):
def save_new_revision(self, trans: ProvidesAppContext, page, payload):
# Assumes security has already been checked by caller.
content = payload.get("content", None)
content_format = payload.get("content_format", None)
@@ -420,18 +421,18 @@ class PageManager(sharable.SharableModelManager[model.Page], UsesAnnotations):
session.commit()
return page_revision
def list_revisions(self, trans, page, sort_desc: bool = False):
def list_revisions(self, trans: ProvidesUserContext, page, sort_desc: bool = False):
page = base.security_check(trans, page, check_ownership=False, check_accessible=True)
return sorted(page.revisions, key=lambda r: r.create_time, reverse=sort_desc)
def get_revision(self, trans, page, revision_id):
def get_revision(self, trans: ProvidesUserContext, page, revision_id):
page = base.security_check(trans, page, check_ownership=False, check_accessible=True)
revision = trans.sa_session.get(model.PageRevision, revision_id)
if not revision or revision.page_id != page.id:
raise exceptions.ObjectNotFound("Page revision not found")
return revision
def restore_revision(self, trans, page, revision_id):
def restore_revision(self, trans: ProvidesUserContext, page, revision_id):
page = base.security_check(trans, page, check_ownership=True, check_accessible=True)
old_revision = self.get_revision(trans, page, revision_id)
# Build revision directly — content is already in internal format
@@ -446,7 +447,7 @@ class PageManager(sharable.SharableModelManager[model.Page], UsesAnnotations):
trans.sa_session.commit()
return page_revision
def rewrite_content_for_import(self, trans, content, content_format: str):
def rewrite_content_for_import(self, trans: ProvidesAppContext, content, content_format: str):
if content_format == PageContentFormat.html.value:
try:
content = sanitize_html(content)
@@ -466,7 +467,7 @@ class PageManager(sharable.SharableModelManager[model.Page], UsesAnnotations):
)
return content
def rewrite_content_for_export(self, trans, as_dict):
def rewrite_content_for_export(self, trans: ProvidesHistoryContext, as_dict):
content = as_dict["content"]
content_format = as_dict.get("content_format", PageContentFormat.html.value)
if content_format == PageContentFormat.html.value:
@@ -554,7 +555,7 @@ class PageContentProcessor(HTMLParser):
"wbr",
}
def __init__(self, trans, render_embed_html_fn: Callable):
def __init__(self, trans: ProvidesAppContext, render_embed_html_fn: Callable):
HTMLParser.__init__(self)
self.trans = trans
self.ignore_content = False
+2 -1
View File
@@ -20,6 +20,7 @@ from galaxy import (
)
from galaxy.exceptions import ActionInputError
from galaxy.managers import base
from galaxy.managers.context import ProvidesUserContext
from galaxy.model import (
Group,
Quota,
@@ -275,5 +276,5 @@ class QuotaManager:
message += ", ".join(names)
return message
def get_quota(self, trans, id: int, deleted: bool | None = None) -> model.Quota:
def get_quota(self, trans: ProvidesUserContext, id: int, deleted: bool | None = None) -> model.Quota:
return base.get_object(trans, id, "Quota", check_ownership=False, check_accessible=False, deleted=deleted)
+3 -2
View File
@@ -37,6 +37,7 @@ from galaxy.managers import (
users,
)
from galaxy.managers.base import combine_lists
from galaxy.managers.context import ProvidesUserContext
from galaxy.model import (
User,
UserShareAssociation,
@@ -244,7 +245,7 @@ class SharableModelManager(
return list(self._apply_fn_limit_offset_gen(items, limit, offset))
def get_sharing_extra_information(
self, trans, item, users: set[User], errors: set[str], option: SharingOptions | None = None
self, trans: ProvidesUserContext, item, users: set[User], errors: set[str], option: SharingOptions | None = None
) -> ShareWithExtra | None:
"""Returns optional extra information about the shareability of the given item.
@@ -252,7 +253,7 @@ class SharableModelManager(
to provide the extra information, otherwise, it will be None by default."""
return None
def make_members_public(self, trans, item):
def make_members_public(self, trans: ProvidesUserContext, item):
"""Make potential elements of this item public.
This method must be overridden in managers that need to change permissions of internal elements
+1 -1
View File
@@ -72,7 +72,7 @@ class ToolDataManager:
data_table.reload_from_files()
return self._reload_data_table(table_name)
def get_field_file_path(self, trans, table_name: str, field_name: str, file_name: str) -> Path:
def get_field_file_path(self, trans: ProvidesUserContext, table_name: str, field_name: str, file_name: str) -> Path:
"""Get the absolute path to a given file name in the table field"""
field_value = self._data_table_field(table_name, field_name)
if table_name not in PUBLIC_TABLES and not trans.user_is_admin:
+3 -3
View File
@@ -9,7 +9,7 @@ from galaxy.exceptions import (
ObjectNotFound,
RequestParameterInvalidException,
)
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.schema.schema import GenerateTourResponse
from galaxy.schema.tours import (
TourDetails,
@@ -27,7 +27,7 @@ class ToursManager:
self._app = app
def generate_tour(
self, tool_id: str, tool_version: str, trans: ProvidesAppContext, performs_upload=True
self, tool_id: str, tool_version: str, trans: ProvidesHistoryContext, performs_upload=True
) -> GenerateTourResponse:
"""
Generate a tour designed for the given tool.
@@ -51,7 +51,7 @@ class ToursManager:
class TourGenerator:
def __init__(self, trans: ProvidesAppContext, tool_id: str, tool_version: str, performs_upload=True) -> None:
def __init__(self, trans: ProvidesHistoryContext, tool_id: str, tool_version: str, performs_upload=True) -> None:
self._trans = trans
self._tool: Tool = self._get_and_ensure_tool(tool_id, tool_version)
self._use_datasets = True
+43 -21
View File
@@ -9,6 +9,7 @@ import string
import time
from typing import (
Any,
TYPE_CHECKING,
)
from markupsafe import escape
@@ -32,6 +33,11 @@ from galaxy.managers import (
deletable,
)
from galaxy.managers.base import combine_lists
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
Job,
User,
@@ -57,6 +63,9 @@ from galaxy.structured_app import (
from galaxy.util import now
from galaxy.util.hash_util import new_secure_hash_v2
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
PASSWORD_RESET_TEMPLATE = """
@@ -88,7 +97,9 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
self.app_type = app_type
super().__init__(app)
def register(self, trans, email=None, username=None, password=None, confirm=None, subscribe=False):
def register(
self, trans: "GalaxyWebTransaction", email=None, username=None, password=None, confirm=None, subscribe=False
):
"""
Register a new user.
"""
@@ -164,7 +175,13 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
return user
def update_email(
self, trans, user: User, new_email: str, *, commit: bool = True, send_activation_email: bool = True
self,
trans: ProvidesAppContext,
user: User,
new_email: str,
*,
commit: bool = True,
send_activation_email: bool = True,
) -> None:
"""
Update a user's email address, keeping the private role in sync and honoring activation settings.
@@ -192,7 +209,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
if commit:
session.commit()
def update_username(self, trans, user: User, new_username: str, *, commit: bool = True) -> None:
def update_username(self, trans: ProvidesAppContext, user: User, new_username: str, *, commit: bool = True) -> None:
"""
Update a user's public name after validating it. Raises RequestParameterInvalidException on validation errors.
"""
@@ -378,7 +395,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
return util.safe_str_cmp(bootstrap_hash, provided_hash)
# ---- admin
def is_admin(self, user: model.User | None, trans=None) -> bool:
def is_admin(self, user: model.User | None, trans: ProvidesUserContext | None = None) -> bool:
"""Return True if this user is an admin (or session is authenticated as admin).
Do not pass trans to simply check if an existing user object is an admin user,
@@ -387,7 +404,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
if user is None:
# Anonymous session or master_api_key used, if master_api_key is detected
# return True.
return trans and trans.user_is_admin
return bool(trans and trans.user_is_admin)
return self.app.config.is_admin_user(user)
def admins(self, filters=None, **kwargs):
@@ -440,7 +457,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
return user
# ---- current
def current_user(self, trans):
def current_user(self, trans: ProvidesUserContext):
# define here for single point of change and make more readable
# TODO: trans
return trans.user
@@ -508,7 +525,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
else:
return user, "User not found."
def __set_password(self, trans, user, password, confirm):
def __set_password(self, trans: ProvidesUserContext, user, password, confirm):
if not password:
return "Please provide a new password."
if user:
@@ -537,14 +554,14 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
else:
return "Failed to determine user, access denied."
def impersonate(self, trans, user):
def impersonate(self, trans: "GalaxyWebTransaction", user):
if not trans.app.config.allow_user_impersonation:
raise exceptions.Message("User impersonation is not enabled in this instance of Galaxy.")
raise exceptions.MessageException("User impersonation is not enabled in this instance of Galaxy.")
if user:
trans.handle_user_logout()
trans.handle_user_login(user)
else:
raise exceptions.Message("Please provide a valid user.")
raise exceptions.MessageException("Please provide a valid user.")
def send_activation_email(self, trans, email, username):
"""
@@ -582,7 +599,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
log.exception("Unable to send the activation email.")
return False
def __get_activation_token(self, trans, email):
def __get_activation_token(self, trans: ProvidesAppContext, email):
"""
Check for the activation token. Create new activation token and store it in the database if no token found.
Flushes but does not commit—the caller is responsible for committing the transaction.
@@ -591,6 +608,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
# Flush pending changes so the user is visible to the DB query below.
session.flush()
user = get_user_by_email(session, email, self.app.model.User)
assert user is not None, f"User with email '{email}' not found while generating activation token."
activation_token = user.activation_token
if activation_token is None:
activation_token = util.hash_util.new_secure_hash_v2(str(random.getrandbits(256)))
@@ -599,7 +617,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
session.flush()
return activation_token
def send_reset_email(self, trans, payload, **kwd):
def send_reset_email(self, trans: "GalaxyWebTransaction", payload, **kwd):
"""Reset the user's password. Send an email with token that allows a password change."""
if self.app.config.smtp_server is None:
return "Mail is not configured for this Galaxy instance and password reset information cannot be sent. Please contact your local Galaxy administrator."
@@ -632,7 +650,7 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
log.warning(f"Failed to produce password reset token. User with email '{email}' not found.")
return None
def get_reset_token(self, trans, email):
def get_reset_token(self, trans: ProvidesAppContext, email):
reset_user = self.by_email(email)
if not reset_user:
reset_user = self.by_email(email, case_sensitive=False)
@@ -799,17 +817,21 @@ class UserDeserializer(base.ModelDeserializer):
}
self.deserializers.update(user_deserializers)
def deserialize_preferred_object_store_id(self, item: Any, key: Any, val: Any, trans=None, **context):
def deserialize_preferred_object_store_id(
self, item: Any, key: Any, val: Any, trans: ProvidesUserContext | None = None, **context
):
preferred_object_store_id = val
validation_error = validate_preferred_object_store_id(trans, self.app.object_store, preferred_object_store_id)
validation_error = validate_preferred_object_store_id(
trans.user if trans else None, self.app.object_store, preferred_object_store_id
)
if validation_error:
raise base.ModelDeserializingError(validation_error)
return self.default_deserializer(item, key, preferred_object_store_id, **context)
def deserialize_username(self, item, key, username, trans=None, **context):
def deserialize_username(self, item, key, username, trans: ProvidesAppContext | None = None, **context):
# TODO: validate_publicname requires trans and should(?) raise exceptions
# move validation to UserValidator and use self.app, exceptions instead
validation_error = validate_publicname(trans, username, user=item)
validation_error = validate_publicname(trans, username, user=item) # type: ignore[arg-type]
if validation_error:
raise base.ModelDeserializingError(validation_error)
return self.default_deserializer(item, key, username, trans=trans, **context)
@@ -827,14 +849,14 @@ class CurrentUserSerializer(UserSerializer):
return self.serialize_current_anonymous_user(user, keys, **kwargs)
return super(UserSerializer, self).serialize(user, keys, **kwargs)
def serialize_current_anonymous_user(self, user, keys, trans=None, **kwargs):
def serialize_current_anonymous_user(self, user, keys, trans: ProvidesHistoryContext | None = None, **kwargs):
# use the current history if any to get usage stats for trans' anonymous user
# TODO: might be better as sep. Serializer class
usage = 0
usage: float = 0
percent = None
if hasattr(trans, "history") and trans.history:
usage = self.app.quota_agent.get_usage(trans, history=trans.history)
if trans is not None and trans.history:
usage = self.app.quota_agent.get_usage(trans, history=trans.history) or 0
percent = self.app.quota_agent.get_percent(trans=trans, usage=usage)
# a very small subset of keys available
+57 -39
View File
@@ -57,7 +57,11 @@ from galaxy.managers.base import (
decode_id,
security_check,
)
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.managers.executables import artifact_class
from galaxy.model import (
History,
@@ -296,7 +300,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
result = trans.sa_session.scalars(stmt).unique()
return result, total_matches
def get_stored_workflow(self, trans, workflow_id, by_stored_id=True) -> StoredWorkflow:
def get_stored_workflow(self, trans: ProvidesUserContext, workflow_id, by_stored_id=True) -> StoredWorkflow:
"""Use a supplied ID (UUID or encoded stored workflow ID) to find
a workflow.
"""
@@ -318,7 +322,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
raise exceptions.ObjectNotFound("No such workflow found.")
return stored_workflow
def get_stored_accessible_workflow(self, trans, workflow_id, by_stored_id=True):
def get_stored_accessible_workflow(self, trans: ProvidesUserContext, workflow_id, by_stored_id=True):
"""Get a stored workflow from a encoded stored workflow id and
make sure it accessible to the user.
"""
@@ -335,7 +339,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
return stored_workflow
def attach_stored_workflow(self, trans, workflow):
def attach_stored_workflow(self, trans: ProvidesUserContext, workflow):
"""Attach and return stored workflow if possible."""
# Imported Subworkflows are not created with a StoredWorkflow association
# To properly serialize them we do need a StoredWorkflow, so we create and attach one here.
@@ -346,7 +350,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
trans.sa_session.commit()
return stored_workflow
def get_owned_workflow(self, trans, encoded_workflow_id):
def get_owned_workflow(self, trans: ProvidesUserContext, encoded_workflow_id):
"""Get a workflow (non-stored) from a encoded workflow id and
make sure it accessible to the user.
"""
@@ -355,7 +359,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
self.check_security(trans, workflow, check_ownership=True)
return workflow
def check_security(self, trans, has_workflow, check_ownership=True, check_accessible=True):
def check_security(self, trans: ProvidesUserContext, has_workflow, check_ownership=True, check_accessible=True):
"""check accessibility or ownership of workflows, storedworkflows, and
workflowinvocations. Throw an exception or returns True if user has
needed level of access.
@@ -391,12 +395,12 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
return True
def get_workflow_svg_from_id(self, trans, id, version=None, for_embed=False) -> bytes:
def get_workflow_svg_from_id(self, trans: ProvidesHistoryContext, id, version=None, for_embed=False) -> bytes:
stored = self.get_stored_accessible_workflow(trans, id)
workflow = stored.get_internal_version(version)
return self.get_workflow_svg(trans, workflow, for_embed=for_embed)
def get_workflow_svg(self, trans, workflow, for_embed=False) -> bytes:
def get_workflow_svg(self, trans: ProvidesHistoryContext, workflow, for_embed=False) -> bytes:
try:
svg = self._workflow_to_svg_canvas(trans, workflow, for_embed=for_embed)
s = STANDALONE_SVG_TEMPLATE % svg.tostring()
@@ -407,7 +411,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
)
raise exceptions.MessageException(message)
def _workflow_to_svg_canvas(self, trans, workflow, for_embed=False):
def _workflow_to_svg_canvas(self, trans: ProvidesHistoryContext, workflow, for_embed=False):
workflow_canvas = WorkflowCanvas()
for step in workflow.steps:
# Load from database representation
@@ -425,7 +429,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
return workflow_canvas.finish(for_embed=for_embed)
def get_invocation(
self, trans, decoded_invocation_id: int, check_ownership=True, check_accessible=True
self, trans: ProvidesUserContext, decoded_invocation_id: int, check_ownership=True, check_accessible=True
) -> WorkflowInvocation:
workflow_invocation = _get_invocation(trans.sa_session, decoded_invocation_id)
if not workflow_invocation:
@@ -437,7 +441,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
)
return workflow_invocation
def get_invocation_report(self, trans, invocation_id, **kwd):
def get_invocation_report(self, trans: ProvidesUserContext, invocation_id, **kwd):
decoded_workflow_invocation_id = (
trans.security.decode_id(invocation_id) if isinstance(invocation_id, str) else invocation_id
)
@@ -458,7 +462,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
target_format=target_format,
)
def request_invocation_cancellation(self, trans, decoded_invocation_id: int):
def request_invocation_cancellation(self, trans: ProvidesUserContext, decoded_invocation_id: int):
workflow_invocation = self.get_invocation(trans, decoded_invocation_id, check_ownership=True)
cancelled = workflow_invocation.cancel()
@@ -471,7 +475,11 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
return workflow_invocation
def get_invocation_step(
self, trans, decoded_workflow_invocation_step_id, check_ownership: bool = True, check_accessible: bool = True
self,
trans: ProvidesUserContext,
decoded_workflow_invocation_step_id,
check_ownership: bool = True,
check_accessible: bool = True,
) -> WorkflowInvocationStep:
try:
workflow_invocation_step = trans.sa_session.get(WorkflowInvocationStep, decoded_workflow_invocation_step_id)
@@ -485,7 +493,7 @@ class WorkflowsManager(sharable.SharableModelManager[model.StoredWorkflow], dele
)
return workflow_invocation_step
def update_invocation_step(self, trans, decoded_workflow_invocation_step_id, action):
def update_invocation_step(self, trans: ProvidesHistoryContext, decoded_workflow_invocation_step_id, action):
if action is None:
raise exceptions.RequestParameterMissingException(
"Updating workflow invocation step requires an action parameter. "
@@ -664,7 +672,7 @@ class WorkflowContentsManager(UsesAnnotations):
created_workflow = self.build_workflow_from_raw_description(trans, raw_description, WorkflowCreateOptions())
return created_workflow.workflow
def normalize_workflow_format(self, trans, as_dict):
def normalize_workflow_format(self, trans: ProvidesUserContext, as_dict):
"""Process incoming workflow descriptions for consumption by other methods.
Currently this mostly means converting format 2 workflows into standard Galaxy
@@ -699,7 +707,7 @@ class WorkflowContentsManager(UsesAnnotations):
def build_workflow_from_raw_description(
self,
trans,
trans: ProvidesHistoryContext,
raw_workflow_description,
workflow_create_options,
source=None,
@@ -763,7 +771,7 @@ class WorkflowContentsManager(UsesAnnotations):
def update_workflow_from_raw_description(
self,
trans,
trans: ProvidesHistoryContext,
stored_workflow: StoredWorkflow,
raw_workflow_description: RawWorkflowDescription,
workflow_update_options: WorkflowUpdateOptions,
@@ -836,7 +844,7 @@ class WorkflowContentsManager(UsesAnnotations):
def _workflow_from_raw_description(
self,
trans,
trans: ProvidesHistoryContext,
raw_workflow_description,
workflow_state_resolution_options,
name,
@@ -976,7 +984,7 @@ class WorkflowContentsManager(UsesAnnotations):
def workflow_to_dict(
self,
trans,
trans: ProvidesHistoryContext,
stored: StoredWorkflow,
style: str = "export",
version: int | None = None,
@@ -1053,7 +1061,7 @@ class WorkflowContentsManager(UsesAnnotations):
wf_dict["version"] = len(stored.workflows) - 1
return wf_dict
def _sync_stored_workflow(self, trans, stored_workflow: StoredWorkflow) -> None:
def _sync_stored_workflow(self, trans: ProvidesHistoryContext, stored_workflow: StoredWorkflow) -> None:
if trans.user_is_admin:
workflow_path = stored_workflow.from_path
assert workflow_path is not None
@@ -1083,7 +1091,7 @@ class WorkflowContentsManager(UsesAnnotations):
workflow_path: str,
stored_workflow: StoredWorkflow,
workflow: Workflow,
trans=None,
trans: ProvidesHistoryContext | None = None,
history: History | None = None,
user: User | None = None,
) -> None:
@@ -1102,7 +1110,7 @@ class WorkflowContentsManager(UsesAnnotations):
f.write(wf_dict["yaml_content"])
def _workflow_to_dict_run(
self, trans: ProvidesUserContext, stored: StoredWorkflow, workflow: Workflow, history: History | None = None
self, trans: ProvidesHistoryContext, stored: StoredWorkflow, workflow: Workflow, history: History | None = None
) -> dict[str, Any]:
"""
Builds workflow dictionary used by run workflow form
@@ -1217,7 +1225,7 @@ class WorkflowContentsManager(UsesAnnotations):
"workflow_resource_parameters": self._workflow_resource_parameters(trans, stored, workflow),
}
def _workflow_to_dict_preview(self, trans, workflow):
def _workflow_to_dict_preview(self, trans: ProvidesHistoryContext, workflow):
"""
Builds workflow dictionary containing input labels and values.
Used to create embedded workflow previews.
@@ -1325,6 +1333,9 @@ class WorkflowContentsManager(UsesAnnotations):
continue
if step.type == "tool":
tool = trans.app.toolbox.get_tool(step.tool_id, step.tool_version)
assert (
tool is not None
), f"Tool '{step.tool_id}' unexpectedly missing after successful runtime state computation"
step_dict["tool_id"] = step.tool_id
step_dict["tool_version"] = step.tool_version
step_dict["label"] = step.label or tool.name
@@ -1346,13 +1357,13 @@ class WorkflowContentsManager(UsesAnnotations):
"steps": step_dicts,
}
def _workflow_resource_parameters(self, trans, stored, workflow):
def _workflow_resource_parameters(self, trans: ProvidesUserContext, stored, workflow):
"""Get workflow scheduling resource parameters for this user and workflow or None if not configured."""
return self._resource_mapper_function(trans=trans, stored_workflow=stored, workflow=workflow)
def _workflow_to_dict_editor(
self,
trans,
trans: ProvidesHistoryContext,
stored: StoredWorkflow | None,
workflow: Workflow,
tooltip: bool = True,
@@ -1585,7 +1596,7 @@ class WorkflowContentsManager(UsesAnnotations):
def _workflow_to_dict_export(
self,
trans,
trans: ProvidesHistoryContext,
workflow: Workflow,
stored: StoredWorkflow | None = None,
internal: bool = False,
@@ -1833,12 +1844,13 @@ class WorkflowContentsManager(UsesAnnotations):
return data
def _workflow_to_dict_instance(
self, trans, stored: StoredWorkflow, workflow: Workflow, legacy: bool = True
self, trans: ProvidesAppContext, stored: StoredWorkflow, workflow: Workflow, legacy: bool = True
) -> dict[str, Any]:
encode = self.app.security.encode_id
sa_session = self.app.model.context
item = stored.to_dict(view="element")
item["name"] = workflow.name
assert trans.url_builder
item["url"] = trans.url_builder("workflow", id=encode(stored.id))
item["owner"] = stored.user.username
item["email_hash"] = md5_hash_str(stored.user.email)
@@ -1976,7 +1988,7 @@ class WorkflowContentsManager(UsesAnnotations):
def __load_subworkflows(
self,
trans,
trans: ProvidesHistoryContext,
step_dict,
subworkflow_id_map,
workflow_state_resolution_options,
@@ -1997,7 +2009,7 @@ class WorkflowContentsManager(UsesAnnotations):
def __module_from_dict(
self,
trans,
trans: ProvidesHistoryContext,
steps: list[model.WorkflowStep],
steps_by_external_id: dict[str, model.WorkflowStep],
step_dict,
@@ -2083,7 +2095,7 @@ class WorkflowContentsManager(UsesAnnotations):
def __load_subworkflow_from_step_dict(
self,
trans,
trans: ProvidesHistoryContext,
step_dict,
subworkflow_id_map,
workflow_state_resolution_options,
@@ -2147,7 +2159,7 @@ class WorkflowContentsManager(UsesAnnotations):
def __build_subworkflow_from_url(
self,
trans,
trans: ProvidesHistoryContext,
url: str,
resolving_urls: frozenset[str],
) -> model.Workflow:
@@ -2173,7 +2185,7 @@ class WorkflowContentsManager(UsesAnnotations):
def __build_subworkflow_from_trs_url(
self,
trans,
trans: ProvidesHistoryContext,
trs_url: str,
resolving_urls: frozenset[str],
) -> model.Workflow:
@@ -2206,7 +2218,7 @@ class WorkflowContentsManager(UsesAnnotations):
def __build_subworkflow_from_trs_id(
self,
trans,
trans: ProvidesHistoryContext,
step_dict: dict,
resolving_urls: frozenset[str],
) -> model.Workflow:
@@ -2227,7 +2239,11 @@ class WorkflowContentsManager(UsesAnnotations):
return self.__build_subworkflow_from_trs_url(trans, trs_url, resolving_urls)
def __build_embedded_subworkflow(
self, trans, data, workflow_state_resolution_options, resolving_urls: frozenset[str] = frozenset()
self,
trans: ProvidesHistoryContext,
data,
workflow_state_resolution_options,
resolving_urls: frozenset[str] = frozenset(),
):
raw_workflow_description = self.ensure_raw_description(data)
subworkflow = self.build_workflow_from_raw_description(
@@ -2290,7 +2306,7 @@ class WorkflowContentsManager(UsesAnnotations):
step.label = module.label = default_label
def do_refactor(
self, trans: ProvidesUserContext, stored_workflow: StoredWorkflow, refactor_request: RefactorRequest
self, trans: ProvidesHistoryContext, stored_workflow: StoredWorkflow, refactor_request: RefactorRequest
):
"""Apply supplied actions to either the latest version of the workflow or a specific version to build a new version."""
# Get the workflow version to refactor (latest or specific version)
@@ -2323,7 +2339,9 @@ class WorkflowContentsManager(UsesAnnotations):
# we send back anyway
return refactored_workflow, action_executions
def refactor(self, trans: ProvidesUserContext, stored_workflow: StoredWorkflow, refactor_request: RefactorRequest):
def refactor(
self, trans: ProvidesHistoryContext, stored_workflow: StoredWorkflow, refactor_request: RefactorRequest
):
refactored_workflow, action_executions = self.do_refactor(trans, stored_workflow, refactor_request)
return RefactorResponse(
action_executions=action_executions,
@@ -2354,7 +2372,7 @@ class WorkflowContentsManager(UsesAnnotations):
def get_or_create_workflow_from_trs(
self,
trans: ProvidesUserContext,
trans: ProvidesHistoryContext,
trs_url: str | None,
trs_id: str | None = None,
trs_version: str | None = None,
@@ -2376,7 +2394,7 @@ class WorkflowContentsManager(UsesAnnotations):
return workflow
def create_workflow_from_trs_url(
self, trans: ProvidesUserContext, trs_url: str, trs_server: str | None = None
self, trans: ProvidesHistoryContext, trs_url: str, trs_server: str | None = None
) -> StoredWorkflow:
_, trs_tool_id, trs_version_id = self.trs_proxy.get_trs_id_and_version_from_trs_url(trs_url=trs_url)
data = self.trs_proxy.get_version_from_trs_url(trs_url)
@@ -2395,7 +2413,7 @@ class WorkflowContentsManager(UsesAnnotations):
)
return created_workflow.stored_workflow
def get_or_create_workflow_from_url(self, trans: ProvidesUserContext, url: str) -> StoredWorkflow:
def get_or_create_workflow_from_url(self, trans: ProvidesHistoryContext, url: str) -> StoredWorkflow:
"""Fetch and import a workflow from an arbitrary URL.
Supports various URL schemes including http://, https://, and base64://.
+26 -21
View File
@@ -241,12 +241,17 @@ from galaxy.util.sanitize_html import sanitize_html
if TYPE_CHECKING:
from sqlalchemy.sql.expression import BindParameter
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
from galaxy.objectstore import (
BaseObjectStore,
ObjectStorePopulator,
QuotaSourceMap,
)
from galaxy.schema.invocation import InvocationMessageUnion
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -5790,7 +5795,7 @@ class DatasetInstance(RepresentById, UsesCreateAndUpdateTime, _HasTable):
return item
return None
def get_converted_dataset_deps(self, trans, target_ext, use_cached_job=False):
def get_converted_dataset_deps(self, trans: "ProvidesUserContext", target_ext, use_cached_job=False):
"""
Returns dict of { "dependency" => HDA }
"""
@@ -5802,7 +5807,13 @@ class DatasetInstance(RepresentById, UsesCreateAndUpdateTime, _HasTable):
return {dep: self.get_converted_dataset(trans, dep, use_cached_job=use_cached_job) for dep in depends_list}
def get_converted_dataset(
self, trans, target_ext, target_context=None, history=None, include_errored=False, use_cached_job=False
self,
trans: "ProvidesUserContext",
target_ext,
target_context=None,
history=None,
include_errored=False,
use_cached_job=False,
):
"""
Return converted dataset(s) if they exist, along with a dict of dependencies.
@@ -5897,7 +5908,7 @@ class DatasetInstance(RepresentById, UsesCreateAndUpdateTime, _HasTable):
return format in self.get_converter_types()
def find_conversion_destination(
self, accepted_formats: list[str], **kwd
self, accepted_formats: Iterable[Union[str, "Data"]], **kwd
) -> tuple[bool, str | None, Optional["DatasetInstance"]]:
"""Returns ( target_ext, existing converted dataset )"""
return self.datatype.find_conversion_destination(self, accepted_formats, _get_datatypes_registry(), **kwd)
@@ -5992,10 +6003,10 @@ class DatasetInstance(RepresentById, UsesCreateAndUpdateTime, _HasTable):
return creating_job_associations[0].job
return None
def get_display_applications(self, trans):
def get_display_applications(self, trans: "GalaxyWebTransaction"):
return self.datatype.get_display_applications_by_dataset(self, trans)
def get_datasources(self, trans):
def get_datasources(self, trans: "ProvidesUserContext"):
"""
Returns datasources for dataset; if datasources are not available
due to indexing, indexing is started. Return value is a dictionary
@@ -6011,25 +6022,19 @@ class DatasetInstance(RepresentById, UsesCreateAndUpdateTime, _HasTable):
msg = None
data_source = source_list
else:
# Convert.
if isinstance(source_list, str):
source_list = [source_list]
# Loop through sources until viable one is found.
for source in source_list:
msg = self.convert_dataset(trans, source)
# No message or PENDING means that source is viable. No
# message indicates conversion was done and is successful.
if not msg or msg == self.conversion_messages.PENDING:
data_source = source
break
# Convert. Each data_sources entry names a single source.
msg = self.convert_dataset(trans, source_list)
# No message or PENDING means that source is viable. No
# message indicates conversion was done and is successful.
if not msg or msg == self.conversion_messages.PENDING:
data_source = source_list
# Store msg.
data_sources_dict[source_type] = {"name": data_source, "message": msg}
return data_sources_dict
def convert_dataset(self, trans, target_type):
def convert_dataset(self, trans: "ProvidesUserContext", target_type):
"""
Converts a dataset to the target_type and returns a message indicating
status of the conversion. None is returned to indicate that dataset
@@ -6046,7 +6051,7 @@ class DatasetInstance(RepresentById, UsesCreateAndUpdateTime, _HasTable):
return {"kind": self.conversion_messages.ERROR, "message": dep_error.value}
# Check dataset state and return any messages.
msg = None
msg: dict[str, Any] | Dataset.conversion_messages | None = None
if converted_dataset and converted_dataset.state == Dataset.states.ERROR:
stmt = select(JobToOutputDatasetAssociation.job_id).filter_by(dataset_id=converted_dataset.id).limit(1)
job_id = trans.sa_session.scalars(stmt).first()
@@ -6258,7 +6263,7 @@ class HistoryDatasetAssociation(DatasetInstance, HasTags, UsesAnnotations, HasNa
def to_library_dataset_dataset_association(
self,
trans,
trans: "ProvidesUserContext",
target_folder,
replace_dataset=None,
parent_id=None,
@@ -11421,7 +11426,7 @@ class UserAddress(Base, RepresentById):
# TODO: db migration to rename column, then use `desc`
user: Mapped[Optional["User"]] = relationship(back_populates="addresses", order_by=sqlalchemy.desc("update_time"))
def to_dict(self, trans):
def to_dict(self, trans: "ProvidesAppContext"):
return {
"id": trans.security.encode_id(self.id),
"name": sanitize_html(self.name),
+48 -34
View File
@@ -2,6 +2,7 @@ import logging
import socket
import sqlite3
from datetime import timedelta
from typing import TYPE_CHECKING
from sqlalchemy import (
and_,
@@ -57,6 +58,12 @@ from galaxy.util import (
)
from galaxy.util.bunch import Bunch
if TYPE_CHECKING:
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
log = logging.getLogger(__name__)
@@ -89,7 +96,7 @@ class GalaxyRBACAgent(RBACAgent):
intermed.sort()
return [_[-1] for _ in intermed]
def get_all_roles(self, trans, cntrller):
def get_all_roles(self, trans: "ProvidesUserContext", cntrller):
admin_controller = cntrller in ["library_admin"]
roles = set()
if not trans.user:
@@ -123,7 +130,9 @@ class GalaxyRBACAgent(RBACAgent):
roles.append(item_permission.role)
return roles
def get_valid_roles(self, trans, item, query=None, page=None, page_limit=None, is_library_access=False):
def get_valid_roles(
self, trans: "ProvidesUserContext", item, query=None, page=None, page_limit=None, is_library_access=False
):
"""
This method retrieves the list of possible roles that user can select
in the item permissions form. Admins can select any role so the
@@ -196,7 +205,7 @@ class GalaxyRBACAgent(RBACAgent):
total_count = len(return_roles)
return self.sort_by_attr(list(return_roles), "name"), total_count
def get_legitimate_roles(self, trans, item, cntrller):
def get_legitimate_roles(self, trans: "ProvidesUserContext", item, cntrller):
"""
Return a sorted list of legitimate roles that can be associated with a permission on
item where item is a Library or a Dataset. The cntrller param is the controller from
@@ -290,7 +299,7 @@ class GalaxyRBACAgent(RBACAgent):
break
return ret_val
def get_actions_for_items(self, trans, action, permission_items):
def get_actions_for_items(self, trans: "ProvidesAppContext", action, permission_items):
# TODO: Rename this; it's a replacement for get_item_actions, but it
# doesn't represent what it's really doing, which is confusing.
# TODO: Make this work for other classes besides lib_datasets.
@@ -309,18 +318,18 @@ class GalaxyRBACAgent(RBACAgent):
#
# If the dataset id has no corresponding action in its permissions,
# then the returned permissions will not carry an entry for the dataset.
ret_permissions = {}
ret_permissions: dict[int, list] = {}
if len(permission_items) > 0:
# SM: NB: LibraryDatasets became Datasets for some odd reason.
if isinstance(permission_items[0], LibraryDataset):
ids = [item.library_dataset_id for item in permission_items]
stmt = select(LibraryDatasetPermissions).where(
library_dataset_stmt = select(LibraryDatasetPermissions).where(
and_(
LibraryDatasetPermissions.library_dataset_id.in_(ids),
LibraryDatasetPermissions.action == action.action,
)
)
permissions = trans.sa_session.scalars(stmt)
permissions = trans.sa_session.scalars(library_dataset_stmt)
# Massage the return data. We will return a list of permissions
# for each library dataset. So we initialize the return list to
# have an empty list for each dataset. Then each permission is
@@ -334,10 +343,10 @@ class GalaxyRBACAgent(RBACAgent):
elif isinstance(permission_items[0], Dataset):
ids = [item.id for item in permission_items]
stmt = select(DatasetPermissions).where(
dataset_stmt = select(DatasetPermissions).where(
and_(DatasetPermissions.dataset_id.in_(ids), DatasetPermissions.action == action.action)
)
permissions = trans.sa_session.scalars(stmt)
permissions = trans.sa_session.scalars(dataset_stmt)
# Massage the return data. We will return a list of permissions
# for each library dataset. So we initialize the return list to
# have an empty list for each dataset. Then each permission is
@@ -384,7 +393,7 @@ class GalaxyRBACAgent(RBACAgent):
return ret_permissions
def allow_action_on_libitems(self, trans, user_roles, action, items):
def allow_action_on_libitems(self, trans: "ProvidesAppContext", user_roles, action, items):
"""
This should be the equivalent of allow_action defined on multiple items.
It is meant to specifically replace allow_action for multiple
@@ -437,7 +446,7 @@ class GalaxyRBACAgent(RBACAgent):
return ret_allow_action
# DELETEME: SM: DO NOT TOUCH! This actually works.
def dataset_access_mapping(self, trans, user_roles, datasets):
def dataset_access_mapping(self, trans: "ProvidesAppContext", user_roles, datasets):
"""
For the given list of datasets, return a mapping of the datasets' ids
to whether they can be accessed by the user or not. The datasets input
@@ -452,7 +461,7 @@ class GalaxyRBACAgent(RBACAgent):
can_access[dataset.id] = datasets_public_map[dataset.id] or datasets_allow_action_map[dataset.id]
return can_access
def dataset_permission_map_for_access(self, trans, user_roles, libitems):
def dataset_permission_map_for_access(self, trans: "ProvidesAppContext", user_roles, libitems):
"""
For a given list of library items (e.g., Datasets), return a map of the
datasets' ids to whether they can have permission to use that action
@@ -477,13 +486,13 @@ class GalaxyRBACAgent(RBACAgent):
can_access[libitem.id] = libitems_public_map[libitem.id] or libitems_allow_action_map[libitem.id]
return can_access
def item_permission_map_for_modify(self, trans, user_roles, libitems):
def item_permission_map_for_modify(self, trans: "ProvidesAppContext", user_roles, libitems):
return self.allow_action_on_libitems(trans, user_roles, self.permitted_actions.LIBRARY_MODIFY, libitems)
def item_permission_map_for_manage(self, trans, user_roles, libitems):
def item_permission_map_for_manage(self, trans: "ProvidesAppContext", user_roles, libitems):
return self.allow_action_on_libitems(trans, user_roles, self.permitted_actions.LIBRARY_MANAGE, libitems)
def item_permission_map_for_add(self, trans, user_roles, libitems):
def item_permission_map_for_add(self, trans: "ProvidesAppContext", user_roles, libitems):
return self.allow_action_on_libitems(trans, user_roles, self.permitted_actions.LIBRARY_ADD, libitems)
def can_access_dataset(self, user_roles, dataset: Dataset):
@@ -519,26 +528,28 @@ class GalaxyRBACAgent(RBACAgent):
roles, self.permitted_actions.LIBRARY_ACCESS, library
)
def get_accessible_libraries(self, trans, user):
def get_accessible_libraries(self, trans: "ProvidesAppContext", user):
"""Return all data libraries that the received user can access"""
accessible_libraries = []
current_user_role_ids = [role.id for role in user.all_roles()]
library_access_action = self.permitted_actions.LIBRARY_ACCESS.action
stmt = select(LibraryPermissions).where(LibraryPermissions.action == library_access_action).distinct()
restricted_library_ids = [lp.library_id for lp in trans.sa_session.scalars(stmt)]
restricted_stmt = (
select(LibraryPermissions).where(LibraryPermissions.action == library_access_action).distinct()
)
restricted_library_ids = [lp.library_id for lp in trans.sa_session.scalars(restricted_stmt)]
stmt = select(LibraryPermissions).where(
accessible_stmt = select(LibraryPermissions).where(
and_(
LibraryPermissions.action == library_access_action,
LibraryPermissions.role_id.in_(current_user_role_ids),
)
)
accessible_restricted_library_ids = [lp.library_id for lp in trans.sa_session.scalars(stmt)]
accessible_restricted_library_ids = [lp.library_id for lp in trans.sa_session.scalars(accessible_stmt)]
# Filter to get libraries accessible by the current user. Get both
# public libraries and restricted libraries accessible by the current user.
stmt = (
library_stmt = (
select(Library)
.where(
and_(
@@ -551,11 +562,11 @@ class GalaxyRBACAgent(RBACAgent):
)
.order_by(Library.name)
)
for library in trans.sa_session.scalars(stmt):
for library in trans.sa_session.scalars(library_stmt):
accessible_libraries.append(library)
return accessible_libraries
def has_accessible_folders(self, trans, folder, user, roles, search_downward=True):
def has_accessible_folders(self, trans: "ProvidesAppContext", folder, user, roles, search_downward=True):
if (
self.has_accessible_library_datasets(trans, folder, user, roles, search_downward=search_downward)
or self.can_add_library_item(roles, folder)
@@ -568,7 +579,7 @@ class GalaxyRBACAgent(RBACAgent):
return self.has_accessible_folders(trans, active_folder, user, roles, search_downward=search_downward)
return False
def has_accessible_library_datasets(self, trans, folder, user, roles, search_downward=True):
def has_accessible_library_datasets(self, trans: "ProvidesAppContext", folder, user, roles, search_downward=True):
stmt = select(LibraryDataset).where(
and_(LibraryDataset.deleted == false(), LibraryDataset.folder_id == folder.id)
)
@@ -579,7 +590,7 @@ class GalaxyRBACAgent(RBACAgent):
return self.__active_folders_have_accessible_library_datasets(trans, folder, user, roles)
return False
def __active_folders_have_accessible_library_datasets(self, trans, folder, user, roles):
def __active_folders_have_accessible_library_datasets(self, trans: "ProvidesAppContext", folder, user, roles):
for active_folder in folder.active_folders:
if self.has_accessible_library_datasets(trans, active_folder, user, roles):
return True
@@ -996,7 +1007,7 @@ WHERE history.user_id != :user_id and history_dataset_association.dataset_id = :
self.associate_user_role(user, sharing_role)
return sharing_role
def set_all_library_permissions(self, trans, library_item, permissions=None):
def set_all_library_permissions(self, trans: "ProvidesAppContext", library_item, permissions=None):
# Set new permissions on library_item, eliminating all current permissions
flush_needed = False
permissions = permissions or {}
@@ -1016,6 +1027,7 @@ WHERE history.user_id != :user_id and history_dataset_association.dataset_id = :
# Permission setting related to DATASET_MANAGE_PERMISSIONS was broken for a period of time,
# so it is possible that some Datasets have no roles associated with the DATASET_MANAGE_PERMISSIONS
# permission. In this case, we'll reset this permission to the library_item user's private role.
assert library_item.dataset is not None
if not library_item.dataset.has_manage_permissions_roles(self):
# Well this looks like a bug, this should be looked at.
# Default permissions above is single hash that keeps getting reeditted here
@@ -1120,14 +1132,14 @@ WHERE history.user_id != :user_id and history_dataset_association.dataset_id = :
"""
return self.permitted_actions.DATASET_ACCESS.action not in [a.action for a in dataset.actions]
def dataset_is_unrestricted(self, trans, dataset):
def dataset_is_unrestricted(self, trans: "ProvidesAppContext", dataset):
"""
Different implementation of the method above with signature:
def dataset_is_public( self, dataset )
"""
return len(dataset.library_dataset_dataset_association.get_access_roles(self)) == 0
def dataset_is_private_to_user(self, trans, dataset):
def dataset_is_private_to_user(self, trans: "ProvidesUserContext", dataset):
"""
If the Dataset object has exactly one access role and that is
the current user's private role then we consider the dataset private.
@@ -1156,7 +1168,7 @@ WHERE history.user_id != :user_id and history_dataset_association.dataset_id = :
access_role = access_roles[0]
return access_role.type == Role.types.PRIVATE
def datasets_are_public(self, trans, datasets):
def datasets_are_public(self, trans: "ProvidesAppContext", datasets):
"""
Given a transaction object and a list of Datasets, return
a mapping from Dataset ids to whether the Dataset is public
@@ -1197,12 +1209,12 @@ WHERE history.user_id != :user_id and history_dataset_association.dataset_id = :
if flush_needed:
self.sa_session.commit()
def derive_roles_from_access(self, trans, item_id, cntrller, library=False, **kwd):
def derive_roles_from_access(self, trans: "ProvidesUserContext", item_id, cntrller, library=False, **kwd):
# Check the access permission on a dataset. If library is true, item_id refers to a library. If library
# is False, item_id refers to a dataset ( item_id must currently be decoded before being sent ). The
# cntrller param is the calling controller, which needs to be passed to get_legitimate_roles().
msg = ""
permissions = {}
permissions: dict = {}
# accessible will be True only if at least 1 user has every role in DATASET_ACCESS_in
accessible = False
# legitimate will be True only if all roles in DATASET_ACCESS_in are in the set of roles returned from
@@ -1296,9 +1308,11 @@ WHERE history.user_id != :user_id and history_dataset_association.dataset_id = :
permissions[self.get_action(v.action)] = in_roles
return permissions, in_roles, error, msg
def copy_library_permissions(self, trans, source_library_item, target_library_item, user=None):
def copy_library_permissions(
self, trans: "ProvidesAppContext", source_library_item, target_library_item, user=None
):
# Copy all relevant permissions from source.
permissions = {}
permissions: dict = {}
for role_assoc in source_library_item.actions:
if role_assoc.action != self.permitted_actions.LIBRARY_ACCESS.action:
# LIBRARY_ACCESS is a special permission that is set only at the library level.
@@ -1324,7 +1338,7 @@ WHERE history.user_id != :user_id and history_dataset_association.dataset_id = :
self.sa_session.add(lp)
self.sa_session.commit()
def get_permitted_libraries(self, trans, user, actions):
def get_permitted_libraries(self, trans: "ProvidesAppContext", user, actions):
"""
This method is historical (it is not currently used), but may be useful again at some
point. It returns a dictionary whose keys are library objects and whose values are a
+13 -6
View File
@@ -5,10 +5,17 @@ Galaxy Security
from typing import (
Literal,
TYPE_CHECKING,
)
from galaxy.util.bunch import Bunch
if TYPE_CHECKING:
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesUserContext,
)
ActionModel = Literal["grant", "restrict"]
@@ -111,7 +118,7 @@ class RBACAgent:
def set_dataset_permission(self, dataset, permission):
raise Exception("Unimplemented Method")
def set_all_library_permissions(self, trans, dataset, permissions):
def set_all_library_permissions(self, trans: "ProvidesAppContext", dataset, permissions):
raise Exception("Unimplemented Method")
def set_library_item_permission(self, library_item, permission):
@@ -123,10 +130,10 @@ class RBACAgent:
def make_library_public(self, library):
raise Exception("Unimplemented Method")
def get_accessible_libraries(self, trans, user):
def get_accessible_libraries(self, trans: "ProvidesAppContext", user):
raise Exception("Unimplemented Method")
def get_permitted_libraries(self, trans, user, actions):
def get_permitted_libraries(self, trans: "ProvidesAppContext", user, actions):
raise Exception("Unimplemented Method")
def folder_is_public(self, library):
@@ -144,13 +151,13 @@ class RBACAgent:
def get_permissions(self, library_dataset):
raise Exception("Unimplemented Method")
def get_all_roles(self, trans, cntrller):
def get_all_roles(self, trans: "ProvidesUserContext", cntrller):
raise Exception("Unimplemented Method")
def get_legitimate_roles(self, trans, item, cntrller):
def get_legitimate_roles(self, trans: "ProvidesUserContext", item, cntrller):
raise Exception("Unimplemented Method")
def derive_roles_from_access(self, trans, item_id, cntrller, library=False, **kwd):
def derive_roles_from_access(self, trans: "ProvidesUserContext", item_id, cntrller, library=False, **kwd):
raise Exception("Unimplemented Method")
def get_component_associations(self, **kwd):
+34 -5
View File
@@ -7,6 +7,11 @@ user inputs - so these methods do not need to be escaped.
import logging
import re
from typing import (
Any,
Protocol,
TYPE_CHECKING,
)
import dns.resolver
from dns.exception import DNSException
@@ -18,6 +23,26 @@ from typing_extensions import LiteralString
from galaxy.objectstore import ObjectStore
if TYPE_CHECKING:
from galaxy.model import User
class UserValidationContext(Protocol):
"""What the user input validators need from a transaction.
Galaxy and the tool shed keep separate context hierarchies with their own
app, config and User classes. Both offer a session and an app, so the
validators ask for that much rather than naming either hierarchy and
forcing one side to depend on the other.
"""
@property
def app(self) -> Any: ...
@property
def sa_session(self) -> Any: ...
log = logging.getLogger(__name__)
# Email validity parameters
@@ -75,7 +100,9 @@ def validate_publicname_str(publicname):
return ""
def validate_email(trans, email, user=None, check_dup=True, allow_empty=False, validate_domain=False):
def validate_email(
trans: UserValidationContext, email, user=None, check_dup=True, allow_empty=False, validate_domain=False
):
"""
Validates the email format.
Checks whether the domain is blocklisted in the disposable domains configuration.
@@ -134,7 +161,7 @@ def extract_domain(email, base_only=False):
return domain
def validate_publicname(trans, publicname, user=None):
def validate_publicname(trans: UserValidationContext, publicname, user=None):
"""
Check that publicname respects the minimum and maximum string length, the
allowed characters, and that the username is not taken already.
@@ -165,14 +192,16 @@ def transform_publicname(publicname):
return publicname
def validate_password(trans, password, confirm):
def validate_password(trans: UserValidationContext, password, confirm):
if password != confirm:
return "Passwords do not match."
return validate_password_str(password)
def validate_preferred_object_store_id(trans, object_store: ObjectStore, preferred_object_store_id: str | None) -> str:
return object_store.validate_selected_object_store_id(trans.user, preferred_object_store_id) or ""
def validate_preferred_object_store_id(
user: "User | None", object_store: ObjectStore, preferred_object_store_id: str | None
) -> str:
return object_store.validate_selected_object_store_id(user, preferred_object_store_id) or ""
def is_email_banned(email: str, filepath: str | None, canonical_email_rules: dict | None) -> bool:
+1 -1
View File
@@ -122,6 +122,7 @@ class MinimalApp(BasicSharedApp):
class MinimalManagerApp(MinimalApp):
# Minimal App that is sufficient to run Celery tasks
amqp_internal_connection_obj: Connection | None
vault: Vault
execution_timer_factory: "ExecutionTimerFactory"
carbon_intensity: float
file_sources: ConfiguredFileSources
@@ -174,7 +175,6 @@ class StructuredApp(MinimalManagerApp):
tool_dependency_dir: str | None
test_data_resolver: test_data.TestDataResolver
trs_proxy: TrsProxy
vault: Vault
webhooks_registry: WebhooksRegistry
queue_worker: Any # 'galaxy.queue_worker.GalaxyQueueWorker'
data_provider_registry: Any # 'galaxy.visualization.data_providers.registry.DataProviderRegistry'
@@ -3,6 +3,7 @@ import re
from urllib.parse import quote
from galaxy import util
from galaxy.managers.context import ProvidesAppContext
from galaxy.tool_shed.util import repository_util
from galaxy.util.tool_shed import common_util
@@ -29,7 +30,7 @@ def can_eliminate_repository_dependency(metadata_dict, tool_shed_url, name, owne
return True
def clean_dependency_relationships(trans, metadata_dict, tool_shed_repository, tool_shed_url):
def clean_dependency_relationships(trans: ProvidesAppContext, metadata_dict, tool_shed_repository, tool_shed_url):
"""
Repositories of type tool_dependency_definition allow for defining a
package dependency at some point in the change log and then removing the
+6
View File
@@ -327,6 +327,12 @@ class ToolDataTable(Dictifiable):
def is_current_version(self, other_version):
return self._loaded_content_version == other_version
def get_fields(self) -> list[list[str]]:
raise NotImplementedError("Abstract method")
def get_named_fields_list(self) -> list[dict[str | int, str]]:
raise NotImplementedError("Abstract method")
def merge_tool_data_table(
self,
other_table: "ToolDataTable",
+19 -7
View File
@@ -12,6 +12,7 @@ from typing import (
Literal,
Optional,
TYPE_CHECKING,
TypeAlias,
Union,
)
from urllib.parse import urlparse
@@ -70,6 +71,10 @@ from .views.interface import (
from .views.static import StaticToolPanelView
if TYPE_CHECKING:
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
DynamicTool,
User,
@@ -78,6 +83,11 @@ if TYPE_CHECKING:
from galaxy.model.tool_shed_install import ToolShedRepository
from galaxy.tools import Tool
from galaxy.tools.cache import ToolCache
from galaxy.webapps.base.webapp import GalaxyWebTransaction
from galaxy.work.context import SessionRequestContext
# both web transactions and FastAPI/agents request contexts render tool panels
PanelViewTrans: TypeAlias = "GalaxyWebTransaction | SessionRequestContext"
log = logging.getLogger(__name__)
@@ -315,7 +325,7 @@ class AbstractToolBox(ManagesIntegratedToolPanelMixin):
if save_integrated_tool_panel:
self._save_integrated_tool_panel()
def _default_panel_view(self, trans):
def _default_panel_view(self, trans: "PanelViewTrans"):
config = self.app.config
if hasattr(config, "config_value_for_host"):
config_value = config.config_value_for_host("default_panel_view", trans.host)
@@ -1366,7 +1376,7 @@ class AbstractToolBox(ManagesIntegratedToolPanelMixin):
self._tool_edam_topics = self._collect_tool_attribute_set("edam_topics")
return self._tool_edam_topics
def package_tool(self, trans, tool_id):
def package_tool(self, trans: "GalaxyWebTransaction", tool_id):
"""
Create a tarball with the tool's xml, help images, and test data.
:param trans: the web transaction
@@ -1471,7 +1481,7 @@ class AbstractToolBox(ManagesIntegratedToolPanelMixin):
tool_panel_section_id = ""
return tool_panel_section_id
def tool_panel_contents(self, trans, view=None, **kwds):
def tool_panel_contents(self, trans: "PanelViewTrans", view=None, **kwds):
"""Filter tool_panel contents for displaying for user."""
if view is None:
view = self._default_panel_view(trans)
@@ -1495,7 +1505,9 @@ class AbstractToolBox(ManagesIntegratedToolPanelMixin):
if elt:
yield elt
def get_tool_to_dict(self, trans, tool: "Tool", tool_help: bool = False) -> dict[str, Any]:
def get_tool_to_dict(
self, trans: "ProvidesHistoryContext", tool: "Tool", tool_help: bool = False
) -> dict[str, Any]:
"""Return tool's panel payload.
Use cache if present, store to cache otherwise.
Note: The cached payload is specific to the calls from toolbox.
@@ -1524,7 +1536,7 @@ class AbstractToolBox(ManagesIntegratedToolPanelMixin):
def to_dict(
self,
trans,
trans: "PanelViewTrans",
in_panel: bool = True,
tool_help: bool = False,
view: str | None = None,
@@ -1553,7 +1565,7 @@ class AbstractToolBox(ManagesIntegratedToolPanelMixin):
rval.append(self.get_tool_to_dict(trans, tool, tool_help=tool_help))
return rval
def to_panel_view(self, trans, view="default_panel_view", **kwds):
def to_panel_view(self, trans: "PanelViewTrans", view="default_panel_view", **kwds):
"""
Create a panel view representation of the toolbox.
Uses the structure:
@@ -1602,7 +1614,7 @@ class AbstractToolBox(ManagesIntegratedToolPanelMixin):
else:
return self._tool_versions_by_id.get(lineage_tool_version.id, {}).get(lineage_tool_version.version)
def _build_filter_method(self, trans):
def _build_filter_method(self, trans: "ProvidesUserContext"):
context = Bunch(toolbox=self, trans=trans)
filters = self._filter_factory.build_filters(trans)
return lambda element, item_type: _filter_for_panel(element, item_type, filters, context)
@@ -4,10 +4,14 @@ from copy import deepcopy
from typing import (
Protocol,
runtime_checkable,
TYPE_CHECKING,
)
from galaxy.util import listify
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesUserContext
log = logging.getLogger(__name__)
@@ -63,7 +67,7 @@ class FilterFactory:
self.__init_filters("section", getattr(config, "tool_section_filters", ""), self.default_filters)
self.__init_filters("label", getattr(config, "tool_label_filters", ""), self.default_filters)
def build_filters(self, trans, **kwds):
def build_filters(self, trans: "ProvidesUserContext", **kwds):
"""
Build list of filters to check tools against given current context.
"""
+4 -1
View File
@@ -10,6 +10,7 @@ from galaxy.util.odict import odict
from .parser import ensure_tool_conf_item
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.tools import Tool
@@ -99,7 +100,9 @@ class ToolSection(UsesDictVisibleKeys, HasPanelItems):
return copy
def to_dict(self, trans, link_details=False, tool_help=False, toolbox=None, only_ids=False):
def to_dict(
self, trans: "ProvidesHistoryContext", link_details=False, tool_help=False, toolbox=None, only_ids=False
):
"""Return a dict that includes section's attributes.
if `only_ids` is `True`, we store only the ids of the section's tools in `section.tools`
+44 -38
View File
@@ -267,7 +267,11 @@ if TYPE_CHECKING:
from galaxy.app import UniverseApplication
from galaxy.jobs import JobToolConfiguration
from galaxy.jobs.job_destination import JobDestination
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.managers.jobs import JobSearch
from galaxy.model import (
DynamicTool,
@@ -760,7 +764,7 @@ class DefaultToolState:
self.rerun_remap_job_id = None
self.inputs = {}
def initialize(self, trans, tool):
def initialize(self, trans: "ProvidesHistoryContext", tool):
"""
Create a new `DefaultToolState` for this tool. It will be initialized
with default values for inputs. Grouping elements are filled in recursively.
@@ -2102,7 +2106,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
# outputs?
return True
def new_state(self, trans):
def new_state(self, trans: "ProvidesHistoryContext"):
"""
Create a new `DefaultToolState` for this tool. It will be initialized
with default values for inputs. Grouping elements are filled in recursively.
@@ -2333,7 +2337,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
def completed_jobs(
self,
trans,
trans: "ProvidesUserContext",
use_cached_job: bool,
all_params: list[ToolStateJobInstancePopulatedT],
) -> dict[int, Job | None]:
@@ -2391,7 +2395,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
def handle_input(
self,
trans,
trans: "ProvidesHistoryContext",
incoming: ToolRequestT,
history: History | None = None,
use_cached_job: bool = DEFAULT_USE_CACHED_JOB,
@@ -2480,7 +2484,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
def handle_single_execution(
self,
trans,
trans: "ProvidesHistoryContext",
rerun_remap_job_id: int | None,
execution_slice: ExecutionSlice,
history: History,
@@ -2569,7 +2573,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
params.append(input_param)
return params
def get_static_param_values(self, trans):
def get_static_param_values(self, trans: "ProvidesHistoryContext"):
"""
Returns a map of parameter names and values if the tool does not
require any user input. Will raise an exception if any parameter
@@ -2589,7 +2593,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
def execute(
self,
trans,
trans: "ProvidesHistoryContext",
incoming: ToolStateJobInstancePopulatedT | None = None,
history: History | None = None,
set_output_hid: bool = DEFAULT_SET_OUTPUT_HID,
@@ -2617,7 +2621,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
def _execute(
self,
trans,
trans: "ProvidesHistoryContext",
incoming: ToolStateJobInstancePopulatedT | None = None,
validated_parameters: JobInternalToolState | None = None,
history: History | None = None,
@@ -2678,7 +2682,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
return self.params_from_strings(param_dict, ignore_errors=ignore_errors)
def check_and_update_param_values(
self, values, trans, update_values: bool = True, workflow_building_mode: bool = False
self, values, trans: "ProvidesHistoryContext", update_values: bool = True, workflow_building_mode: bool = False
):
"""
Check that all parameters have values, and fill in with default
@@ -3000,7 +3004,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
os.remove(temp_file)
return tarball_archive
def to_panel_entry(self, trans) -> dict[str, Any]:
def to_panel_entry(self, trans: "ProvidesUserContext") -> dict[str, Any]:
"""The complete per-tool panel/listing payload — see
:class:`galaxy.tool_util.toolbox.entry.ToolPanelEntry` for the
contract. ``to_dict`` layers io/help extras on top of this.
@@ -3041,7 +3045,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
kwargs["config_file"] = None if not self.config_file else os.path.abspath(self.config_file)
return ToolPanelEntry(**kwargs).model_dump(exclude_unset=True)
def to_dict(self, trans, link_details=False, io_details=False, tool_help=False):
def to_dict(self, trans: "ProvidesHistoryContext", link_details=False, io_details=False, tool_help=False):
"""Returns dict of tool.
``link_details`` is accepted for backwards compatibility and no
@@ -3072,7 +3076,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
def to_json(
self,
trans,
trans: "ProvidesHistoryContext",
kwd=None,
job: Job | None = None,
workflow_building_mode=False,
@@ -3093,7 +3097,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
or workflow_building_mode is workflow_building_modes.DISABLED
):
# We don't need a history when exporting a workflow for the workflow editor or when downloading a workflow
history = history or trans.get_history()
history = history or trans.history
if history is None and job is not None:
assert job.history
history = self.history_manager.get_owned(job.history.id, trans.user, current_history=trans.history)
@@ -3319,7 +3323,7 @@ class Tool(UsesDictVisibleKeys, MaybeToolParameterBundle):
raise exceptions.MessageException(unicodify(e))
return message
def get_default_history_by_trans(self, trans, create=False):
def get_default_history_by_trans(self, trans: "ProvidesHistoryContext", create=False):
return trans.get_history(create=create)
@classmethod
@@ -3832,7 +3836,7 @@ class DataManagerTool(OutputParameterJSONTool):
else:
raise Exception("Unknown data manager mode encountered type...")
def get_default_history_by_trans(self, trans, create=False):
def get_default_history_by_trans(self, trans: "ProvidesHistoryContext", create=False):
def _create_data_manager_history(user):
history = History(name="Data Manager History (automatically created)", user=user)
data_manager_association = model.DataManagerHistoryAssociation(user=user, history=history)
@@ -3967,7 +3971,7 @@ class UnzipCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
has_collection = incoming["input"]
if hasattr(has_collection, "element_type"):
# It is a DCE
@@ -3993,7 +3997,7 @@ class ZipCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
forward_o = incoming["input_forward"]
reverse_o = incoming["input_reverse"]
@@ -4015,7 +4019,7 @@ class CrossProductFlatCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
input_a = incoming["input_a"]
input_b = incoming["input_b"]
join_identifier = incoming["join_identifier"]
@@ -4051,7 +4055,7 @@ class CrossProductNestedCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
input_a = incoming["input_a"]
input_b = incoming["input_b"]
@@ -4103,7 +4107,7 @@ class BuildListCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
new_elements = {}
for i, incoming_repeat in enumerate(incoming["datasets"]):
@@ -4134,7 +4138,7 @@ class SplitPairedAndUnpairedTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
has_collection = incoming["input"]
if hasattr(has_collection, "element_type"):
# It is a DCE
@@ -4218,7 +4222,7 @@ class ExtractDatasetCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
has_collection = incoming["input"]
if hasattr(has_collection, "element_type"):
# It is a DCE
@@ -4261,7 +4265,7 @@ class MergeCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
input_lists = []
for incoming_repeat in incoming["inputs"]:
@@ -4370,7 +4374,7 @@ class FilterDatasetsTool(DatabaseOperationTool):
assert isinstance(element_object, model.DatasetInstance)
return element_object.is_ok
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
collection = incoming["input"]
replacement_dataset = incoming.get("replacement")
if hasattr(collection, "element_object"):
@@ -4493,7 +4497,7 @@ class FlattenTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca = incoming["input"]
join_identifier = incoming["join_identifier"]
new_elements = {}
@@ -4531,7 +4535,7 @@ class NestTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca = incoming["input"]
new_elements = {}
copied_datasets = []
@@ -4563,7 +4567,7 @@ class SortTool(DatabaseOperationTool):
require_terminal_states = True
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca = incoming["input"]
sorttype = incoming["sort_type"]["sort_type"]
new_elements = {}
@@ -4631,7 +4635,7 @@ class HarmonizeTool(DatabaseOperationTool):
}
super().check_inputs_ready(input_datasets, filtered_collections, security)
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca1 = incoming["input1"]
hdca2 = incoming.get("input2")
elements1 = hdca1.collection.elements
@@ -4690,7 +4694,9 @@ class HarmonizeTool(DatabaseOperationTool):
output_with_selected_identifiers(old_elements1_dict, "output1")
output_with_selected_identifiers(old_elements2_dict, "output2")
def _produce_outputs_with_optional_nulls(self, trans, output_collections, hdca1, elements1, history):
def _produce_outputs_with_optional_nulls(
self, trans: "ProvidesUserContext", output_collections, hdca1, elements1, history
):
"""When input2 is not provided, output1 is a copy of input1 and output2
mirrors input1's structure but with expression.json null datasets."""
object_store_populator = ObjectStorePopulator(trans.app, trans.user)
@@ -4738,7 +4744,7 @@ class HarmonizeTool(DatabaseOperationTool):
propagate_hda_tags=False,
)
def _create_null_dataset(self, trans, history, object_store_populator):
def _create_null_dataset(self, trans: "ProvidesUserContext", history, object_store_populator):
"""Create a new HDA with expression.json null content (skipped marker)."""
null_hda = HistoryDatasetAssociation(
extension="expression.json",
@@ -4763,7 +4769,7 @@ class HarmonizeTool(DatabaseOperationTool):
class RelabelFromFileTool(DatabaseOperationTool):
tool_type = "relabel_from_file"
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca = incoming["input"]
how_type = incoming["how"]["how_select"]
new_labels_dataset_assoc = incoming["how"]["labels"]
@@ -4846,7 +4852,7 @@ class RelabelFromFileTool(DatabaseOperationTool):
class ApplyRulesTool(DatabaseOperationTool):
tool_type = "apply_rules"
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca = incoming["input"]
rule_set = RuleSet(incoming["rules"])
copied_datasets = []
@@ -4884,7 +4890,7 @@ class TagFromFileTool(DatabaseOperationTool):
# require_terminal_states = True
# require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca = incoming["input"]
how = incoming["how"]
new_tags_dataset_assoc = incoming["tags"]
@@ -4961,7 +4967,7 @@ class TagFromFileTool(DatabaseOperationTool):
class FilterFromFileTool(DatabaseOperationTool):
tool_type = "filter_from_file"
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hdca = incoming["input"]
how_filter = incoming["how"]["how_filter"]
filter_dataset_assoc = incoming["how"]["filter_source"]
@@ -5017,7 +5023,7 @@ class DuplicateFileToCollectionTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
hda = incoming["input"]
number = int(incoming["number"])
element_identifier = incoming["element_identifier"]
@@ -5042,7 +5048,7 @@ class ConvertSampleSheetTool(DatabaseOperationTool):
require_terminal_states = False
require_dataset_ok = False
def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds):
def produce_outputs(self, trans: "ProvidesUserContext", out_data, output_collections, incoming, history, **kwds):
has_collection = incoming["input"]
if hasattr(has_collection, "element_type"):
# It is a DCE
@@ -5117,7 +5123,7 @@ tool_types = {tool_class.tool_type: tool_class for tool_class in TOOL_CLASSES}
# ---- Utility classes to be factored out -----------------------------------
def _rerun_remap_job_id(trans, incoming, tool_id: str | None) -> int | None:
def _rerun_remap_job_id(trans: "ProvidesAppContext", incoming, tool_id: str | None) -> int | None:
rerun_remap_job_id = None
if "rerun_remap_job_id" in incoming:
try:
+31 -16
View File
@@ -24,7 +24,10 @@ from galaxy.exceptions import (
ToolInputsNotReadyException,
)
from galaxy.job_execution.actions.post import ActionBox
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
Dataset,
History,
@@ -79,6 +82,7 @@ if TYPE_CHECKING:
)
from galaxy.tool_util.parser.output_objects import ToolOutput
from galaxy.tools import Tool
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -97,7 +101,7 @@ class ToolAction:
def execute(
self,
tool: "Tool",
trans,
trans: ProvidesHistoryContext,
incoming: ToolStateJobInstancePopulatedT | None = None,
history: History | None = None,
job_params=None,
@@ -122,7 +126,7 @@ class ToolAction:
dataset=None,
tool=None,
on_text=None,
trans=None,
trans: ProvidesHistoryContext | None = None,
incoming=None,
history=None,
params=None,
@@ -398,10 +402,12 @@ class DefaultToolAction(ToolAction):
tool.visit_inputs(param_values, visitor)
return input_dataset_collections
def _check_access(self, tool, trans):
def _check_access(self, tool, trans: ProvidesUserContext):
assert tool.allow_user_access(trans.user), f"User ({trans.user}) is not allowed to access this tool."
def _collect_inputs(self, tool, trans, incoming, history, current_user_roles, collection_info):
def _collect_inputs(
self, tool, trans: ProvidesHistoryContext, incoming, history, current_user_roles, collection_info
):
"""Collect history as well as input datasets and collections."""
# Set history.
if not history:
@@ -442,7 +448,7 @@ class DefaultToolAction(ToolAction):
def execute(
self,
tool: "Tool",
trans,
trans: ProvidesHistoryContext,
incoming: ToolStateJobInstancePopulatedT | None = None,
history: History | None = None,
job_params=None,
@@ -797,7 +803,7 @@ class DefaultToolAction(ToolAction):
job.info = f"Redirected to: {redirect_url}"
trans.sa_session.add(job)
trans.sa_session.commit()
trans.response.send_redirect(redirect_url)
cast("GalaxyWebTransaction", trans).response.send_redirect(redirect_url)
else:
if flush_job:
# Set HID and add to history.
@@ -920,7 +926,7 @@ class DefaultToolAction(ToolAction):
def _wrapped_params(
self,
trans,
trans: ProvidesHistoryContext,
tool: "Tool",
incoming: "ToolStateJobInstancePopulatedT",
input_datasets: LegacyUnprefixedDict | None = None,
@@ -950,7 +956,7 @@ class DefaultToolAction(ToolAction):
return on_text_for_dataset_and_collections(dataset_hids=input_hids, collection_hids=collection_hids)
def _new_job_for_session(
self, trans, tool: "Tool", history: History | None
self, trans: ProvidesHistoryContext, tool: "Tool", history: History | None
) -> tuple[Job, model.GalaxySession | None]:
job = Job()
job.galaxy_version = trans.app.config.version_major
@@ -995,7 +1001,7 @@ class DefaultToolAction(ToolAction):
)
sa_session.add(association)
def _record_inputs(self, trans, tool, job, incoming, inp_data, inp_dataset_collections):
def _record_inputs(self, trans: ProvidesHistoryContext, tool, job, incoming, inp_data, inp_dataset_collections):
# FIXME: Don't need all of incoming here, just the defined parameters
# from the tool. We need to deal with tools that pass all post
# parameters to the command as a special case.
@@ -1070,7 +1076,7 @@ class DefaultToolAction(ToolAction):
job.add_output_dataset_collection(name, dataset_collection_instance)
dataset_collection_instance.job = job
def _record_input_datasets(self, trans, job, inp_data):
def _record_input_datasets(self, trans: ProvidesHistoryContext, job, inp_data):
for name, dataset in inp_data.items():
# TODO: figure out why can't pass dataset_id here.
job.add_input_dataset(name, dataset=dataset)
@@ -1081,7 +1087,7 @@ class DefaultToolAction(ToolAction):
dataset=None,
tool=None,
on_text=None,
trans=None,
trans: ProvidesHistoryContext | None = None,
incoming=None,
history=None,
params=None,
@@ -1104,7 +1110,16 @@ class DefaultToolAction(ToolAction):
)
def _get_default_data_name(
self, dataset, tool, on_text=None, trans=None, incoming=None, history=None, params=None, job_params=None, **kwd
self,
dataset,
tool,
on_text=None,
trans: ProvidesHistoryContext | None = None,
incoming=None,
history=None,
params=None,
job_params=None,
**kwd,
):
name = tool.name
if on_text:
@@ -1122,7 +1137,7 @@ class OutputCollections:
def __init__(
self,
trans,
trans: ProvidesHistoryContext,
history,
tool,
tool_action,
@@ -1146,8 +1161,8 @@ class OutputCollections:
self.incoming = incoming
self.params = params
self.job_params = job_params
self.out_collections = {}
self.out_collection_instances = {}
self.out_collections: dict[str, DatasetCollection] = {}
self.out_collection_instances: dict[str, HistoryDatasetCollectionAssociation] = {}
self.tags = tags # all inherited tags
self.hdca_tags = hdca_tags # only tags inherited from input HDCAs
+16 -7
View File
@@ -4,8 +4,11 @@ import os
from galaxy.exceptions import RequestParameterMissingException
from galaxy.job_execution.output_collect import copy_collection_metadata_from_target_dict
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.model import (
History,
HistoryDatasetAssociation,
HistoryDatasetCollectionAssociation,
Job,
)
from galaxy.model.dataset_collections.matching import MatchingCollections
@@ -39,7 +42,7 @@ class BaseUploadToolAction(ToolAction):
def execute(
self,
tool,
trans,
trans: ProvidesHistoryContext,
incoming: ToolStateJobInstancePopulatedT | None = None,
history: History | None = None,
job_params=None,
@@ -69,7 +72,9 @@ class BaseUploadToolAction(ToolAction):
rval = self._setup_job(tool, trans, incoming, dataset_upload_inputs, history, preferred_object_store_id)
return rval
def _setup_job(self, tool, trans, incoming, dataset_upload_inputs, history, preferred_object_store_id):
def _setup_job(
self, tool, trans: ProvidesHistoryContext, incoming, dataset_upload_inputs, history, preferred_object_store_id
):
"""Take persisted uploads and create a job for given tool."""
def _create_job(self, *args, **kwds):
@@ -81,7 +86,9 @@ class BaseUploadToolAction(ToolAction):
class UploadToolAction(BaseUploadToolAction):
def _setup_job(self, tool, trans, incoming, dataset_upload_inputs, history, preferred_object_store_id):
def _setup_job(
self, tool, trans: ProvidesHistoryContext, incoming, dataset_upload_inputs, history, preferred_object_store_id
):
check_timer = ExecutionTimer()
uploaded_datasets = upload_common.get_uploaded_datasets(
trans, "", incoming, dataset_upload_inputs, history=history
@@ -105,7 +112,9 @@ class UploadToolAction(BaseUploadToolAction):
class FetchUploadToolAction(BaseUploadToolAction):
def _setup_job(self, tool, trans, incoming, dataset_upload_inputs, history, preferred_object_store_id):
def _setup_job(
self, tool, trans: ProvidesHistoryContext, incoming, dataset_upload_inputs, history, preferred_object_store_id
):
# Now replace references in requests with these.
files = incoming.get("files", [])
files_iter = iter(files)
@@ -135,7 +144,7 @@ class FetchUploadToolAction(BaseUploadToolAction):
replace_file_srcs(request)
outputs = []
outputs: list[HistoryDatasetAssociation | HistoryDatasetCollectionAssociation] = []
for target in request.get("targets", []):
destination = target.get("destination")
destination_type = destination.get("type")
@@ -161,7 +170,7 @@ class FetchUploadToolAction(BaseUploadToolAction):
)
def _precreate_fetched_hdas(trans, history, target, outputs):
def _precreate_fetched_hdas(trans: ProvidesHistoryContext, history, target, outputs):
for item in target.get("elements", []):
name = item.get("name", None)
if name is None:
@@ -186,7 +195,7 @@ def _precreate_fetched_hdas(trans, history, target, outputs):
item["object_id"] = data.id
def _precreate_fetched_collection_instance(trans, history, target, outputs):
def _precreate_fetched_collection_instance(trans: ProvidesHistoryContext, history, target, outputs):
collection_type = target.get("collection_type")
if not collection_type:
# Can't precreate collections of unknown type at this time.
+28 -12
View File
@@ -18,7 +18,11 @@ from galaxy.files.uris import (
stream_to_file,
validate_non_local,
)
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
DatasetPermissions,
FormDefinition,
@@ -43,7 +47,7 @@ def validate_datatype_extension(datatypes_registry, ext):
raise RequestParameterInvalidException(f"Requested extension '{ext}' unknown, cannot upload dataset.")
def persist_uploads(params, trans):
def persist_uploads(params, trans: ProvidesAppContext):
"""
Turn any uploads in the submitted form to persisted files.
"""
@@ -87,7 +91,7 @@ class LibraryParams:
def handle_library_params(
trans, params, folder_id: int, replace_dataset: LibraryDataset | None = None
trans: ProvidesAppContext, params, folder_id: int, replace_dataset: LibraryDataset | None = None
) -> LibraryParams:
session = trans.sa_session
# FIXME: the received params has already been parsed by util.Params() by the time it reaches here,
@@ -125,7 +129,7 @@ def handle_library_params(
)
def __new_history_upload(trans, uploaded_dataset, history=None, state=None):
def __new_history_upload(trans: ProvidesHistoryContext, uploaded_dataset, history=None, state=None):
if not history:
history = trans.history
hda = HistoryDatasetAssociation(
@@ -143,12 +147,15 @@ def __new_history_upload(trans, uploaded_dataset, history=None, state=None):
hda.state = hda.states.QUEUED
history.add_dataset(hda, genome_build=uploaded_dataset.dbkey, quota=False)
permissions = trans.app.security_agent.history_get_default_permissions(history)
assert hda.dataset is not None
trans.app.security_agent.set_all_dataset_permissions(hda.dataset, permissions, new=True, flush=False)
trans.sa_session.commit()
return hda
def __new_library_upload(trans, cntrller, uploaded_dataset, library_bunch, tag_handler, state=None):
def __new_library_upload(
trans: ProvidesUserContext, cntrller, uploaded_dataset, library_bunch, tag_handler, state=None
):
current_user_roles = trans.get_current_user_roles()
if not (
(trans.user_is_admin and cntrller in ["library_admin", "api"])
@@ -213,6 +220,7 @@ def __new_library_upload(trans, cntrller, uploaded_dataset, library_bunch, tag_h
)
else:
# Copy the current user's DefaultUserPermissions to the new LibraryDatasetDatasetAssociation.dataset
assert ldda.dataset is not None
trans.app.security_agent.set_all_dataset_permissions(
ldda.dataset, trans.app.security_agent.user_get_default_permissions(trans.user), new=True
)
@@ -252,7 +260,13 @@ def __new_library_upload(trans, cntrller, uploaded_dataset, library_bunch, tag_h
def new_upload(
trans: ProvidesUserContext, cntrller, uploaded_dataset, library_bunch=None, history=None, state=None, tag_list=None
trans: ProvidesHistoryContext,
cntrller,
uploaded_dataset,
library_bunch=None,
history=None,
state=None,
tag_list=None,
):
tag_handler = trans.tag_handler
if library_bunch:
@@ -280,7 +294,9 @@ def new_upload(
return upload_target_dataset_instance
def get_uploaded_datasets(trans, cntrller, params, dataset_upload_inputs, library_bunch=None, history=None):
def get_uploaded_datasets(
trans: ProvidesHistoryContext, cntrller, params, dataset_upload_inputs, library_bunch=None, history=None
):
uploaded_datasets = []
for dataset_upload_input in dataset_upload_inputs:
uploaded_datasets.extend(dataset_upload_input.get_uploaded_datasets(trans, params))
@@ -290,7 +306,7 @@ def get_uploaded_datasets(trans, cntrller, params, dataset_upload_inputs, librar
return uploaded_datasets
def create_paramfile(trans, uploaded_datasets):
def create_paramfile(trans: ProvidesUserContext, uploaded_datasets):
"""
Create the upload tool's JSON "param" file.
"""
@@ -333,7 +349,7 @@ def create_paramfile(trans, uploaded_datasets):
except Exception:
purge_source = True
try:
user_ftp_dir = os.path.abspath(trans.user_ftp_dir)
user_ftp_dir = os.path.abspath(trans.user_ftp_dir) if trans.user_ftp_dir is not None else None
except Exception:
user_ftp_dir = None
if user_ftp_dir and uploaded_dataset.path.startswith(user_ftp_dir):
@@ -379,7 +395,7 @@ def create_paramfile(trans, uploaded_datasets):
def create_job(
trans,
trans: ProvidesHistoryContext,
params,
tool,
json_file_path,
@@ -395,7 +411,7 @@ def create_job(
job = Job()
trans.sa_session.add(job)
job.galaxy_version = trans.app.config.version_major
galaxy_session = trans.get_galaxy_session()
galaxy_session = trans.galaxy_session
if isinstance(galaxy_session, GalaxySession):
job.session_id = galaxy_session.id
if trans.user is not None:
@@ -438,7 +454,7 @@ def create_job(
return job, output
def active_folders(trans, folder):
def active_folders(trans: ProvidesAppContext, folder):
# Stolen from galaxy.web.controllers.library_common (importing from which causes a circular issues).
# Much faster way of retrieving all active sub-folders within a given folder than the
# performance of the mapper. This query also eagerloads the permissions on each folder.
+2 -1
View File
@@ -8,6 +8,7 @@ from galaxy.exceptions import (
ItemAccessibilityException,
UserRequiredException,
)
from galaxy.managers.context import ProvidesUserContext
from galaxy.util import plugin_config
log = logging.getLogger(__name__)
@@ -64,7 +65,7 @@ class ErrorPlugin:
roles = []
return self.app.security_agent.can_access_dataset(roles, dataset.dataset)
def _check_invocation_accessibility(self, trans, invocation, user):
def _check_invocation_accessibility(self, trans: ProvidesUserContext | None, invocation, user):
if not user:
raise UserRequiredException("User is not logged in", type="error")
if not trans or not self.app.workflow_manager.check_security(trans, invocation, check_ownership=False):
+15 -15
View File
@@ -49,13 +49,13 @@ from galaxy.tools.execution_helpers import (
)
from galaxy.tools.parameters.workflow_utils import is_runtime_value
from galaxy.util.json import swap_inf_nan
from galaxy.work.context import WorkRequestContext
from ._types import (
ToolRequestT,
ToolStateJobInstancePopulatedT,
)
if typing.TYPE_CHECKING:
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.tools import Tool
log = logging.getLogger(__name__)
@@ -96,7 +96,7 @@ class MappingParameters(NamedTuple):
assert self.validated_param_template is not None
assert self.validated_param_combinations is not None
def example_params(self, trans: WorkRequestContext) -> ToolStateJobInstancePopulatedT:
def example_params(self, trans: "ProvidesHistoryContext") -> ToolStateJobInstancePopulatedT:
"""Representative per-job params for output-structure determination.
Normally returns ``param_combinations[0]``. When the request
@@ -113,11 +113,11 @@ class MappingParameters(NamedTuple):
return _resolve_template(self.param_template, trans)
def _resolve_template(template: ToolRequestT, trans: WorkRequestContext) -> ToolStateJobInstancePopulatedT:
def _resolve_template(template: ToolRequestT, trans: "ProvidesHistoryContext") -> ToolStateJobInstancePopulatedT:
return {key: _resolve_template_value(value, trans) for key, value in template.items()}
def _resolve_template_value(value: Any, trans: WorkRequestContext) -> Any:
def _resolve_template_value(value: Any, trans: "ProvidesHistoryContext") -> Any:
if isinstance(value, dict):
values = value.get("values")
if (
@@ -138,7 +138,7 @@ def _resolve_template_value(value: Any, trans: WorkRequestContext) -> Any:
def _resolve_collection_ref(
ref: dict[str, Any],
trans: WorkRequestContext,
trans: "ProvidesHistoryContext",
raw_fallback: Any,
) -> model.HistoryDatasetCollectionAssociation | model.DatasetCollectionElement | Any:
src = ref.get("src")
@@ -163,7 +163,7 @@ def _resolve_collection_ref(
def execute_async(
trans,
trans: "ProvidesHistoryContext",
tool: "Tool",
mapping_params: MappingParameters,
history: model.History,
@@ -204,7 +204,7 @@ def execute_async(
def execute(
trans,
trans: "ProvidesHistoryContext",
tool: "Tool",
mapping_params: MappingParameters,
history: model.History,
@@ -247,7 +247,7 @@ def execute(
def _execute(
trans,
trans: "ProvidesHistoryContext",
tool: "Tool",
mapping_params: MappingParameters,
history: model.History,
@@ -465,7 +465,7 @@ class ExecutionTracker:
def __init__(
self,
trans,
trans: "ProvidesHistoryContext",
tool: "Tool",
mapping_params: MappingParameters,
collection_info: MatchingCollections | None,
@@ -555,7 +555,7 @@ class ExecutionTracker:
)
return self._on_text
def output_name(self, trans, history, params, output):
def output_name(self, trans: "ProvidesHistoryContext", history, params, output):
on_text = self.on_text
try:
@@ -619,7 +619,7 @@ class ExecutionTracker:
leaf_subcollection_type=subcollection_mapping_type,
)
def _structure_for_output(self, trans, tool_output):
def _structure_for_output(self, trans: "ProvidesHistoryContext", tool_output):
collection_info = self.collection_info
assert collection_info
structure = collection_info.structure
@@ -640,7 +640,7 @@ class ExecutionTracker:
return structure
def _mapped_output_structure(self, trans, tool_output):
def _mapped_output_structure(self, trans: "ProvidesHistoryContext", tool_output):
collections_manager = trans.app.dataset_collection_manager
output_structure = tool_output_to_structure(
self.sliced_input_collection_structure, tool_output, collections_manager
@@ -727,7 +727,7 @@ class ExecutionTracker:
else:
return None
def finalize_dataset_collections(self, trans):
def finalize_dataset_collections(self, trans: "ProvidesHistoryContext"):
# TODO: this probably needs to be reworked some, we should have the collection methods
# return a list of changed objects to add to the session and flush and we should only
# be finalizing collections to a depth of self.collection_info.structure. So for instance
@@ -848,7 +848,7 @@ class ExecutionTracker:
class ToolExecutionTracker(ExecutionTracker):
def __init__(
self,
trans,
trans: "ProvidesHistoryContext",
tool: "Tool",
mapping_params: MappingParameters,
collection_info: MatchingCollections | None,
@@ -893,7 +893,7 @@ class ToolExecutionTracker(ExecutionTracker):
class WorkflowStepExecutionTracker(ExecutionTracker):
def __init__(
self,
trans,
trans: "ProvidesHistoryContext",
tool: "Tool",
mapping_params: MappingParameters,
collection_info: MatchingCollections | None,
+6 -3
View File
@@ -5,9 +5,12 @@ tool execution code, and tool action code.
"""
import logging
from typing import Any
from more_itertools import consecutive_groups
from galaxy.managers.context import ProvidesUserContext
log = logging.getLogger(__name__)
@@ -16,11 +19,11 @@ class ToolExecutionCache:
the same tool by the same user with slightly different parameters.
"""
def __init__(self, trans):
def __init__(self, trans: ProvidesUserContext):
self.trans = trans
self.current_user_roles = trans.get_current_user_roles()
self.chrom_info = {}
self.cached_collection_elements = {}
self.chrom_info: dict[str, Any] = {}
self.cached_collection_elements: dict[Any, Any] = {}
def get_chrom_info(self, tool_id, input_dbkey):
genome_builds = self.trans.app.genome_builds
+101 -79
View File
@@ -132,7 +132,7 @@ def contains_workflow_parameter(value, search=False):
return False
def is_runtime_context(trans, other_values):
def is_runtime_context(trans: "ProvidesHistoryContext", other_values):
if trans.workflow_building_mode:
return True
for context_value in other_values.values():
@@ -241,14 +241,14 @@ class ToolParameter(UsesDictVisibleKeys):
"""Return user friendly name for the parameter"""
return self.label if self.label else self.name
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
"""
Convert a value from an HTML POST into the parameters preferred value
format.
"""
return value
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
"""
Return the starting value of the parameter
"""
@@ -346,7 +346,7 @@ class ToolParameter(UsesDictVisibleKeys):
value = sanitize_param(value)
return value
def validate(self, value, trans=None) -> None:
def validate(self, value, trans: "ProvidesHistoryContext | None" = None) -> None:
if value in ["", None] and self.optional:
return
for validator in self.validators:
@@ -355,7 +355,7 @@ class ToolParameter(UsesDictVisibleKeys):
except ValueError as e:
raise ParameterValueError(str(e), self.name, value) from None
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
"""to_dict tool parameter. This can be overridden by subclasses."""
other_values = other_values or {}
tool_dict = self._dictify_view_keys()
@@ -402,7 +402,7 @@ class SimpleTextToolParameter(ToolParameter):
else:
self.value = ""
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
return self.value
@@ -436,7 +436,7 @@ class TextToolParameter(SimpleTextToolParameter):
self.value = input_source.get("value")
self.area = input_source.get_bool("area", False)
def validate(self, value, trans=None):
def validate(self, value, trans: "ProvidesHistoryContext | None" = None):
search = self.type == "text"
if not (
trans
@@ -455,7 +455,7 @@ class TextToolParameter(SimpleTextToolParameter):
default_value = ""
return default_value
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
d = super().to_dict(trans)
other_values = other_values or {}
d["area"] = self.area
@@ -504,7 +504,7 @@ class IntegerToolParameter(TextToolParameter):
if self.min is not None or self.max is not None:
self.validators.append(validation.InRangeValidator.simple_range_validator(self.min, self.max))
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
try:
return int(value)
@@ -530,7 +530,7 @@ class IntegerToolParameter(TextToolParameter):
return None
raise ParameterValueError("an integer is required", self.name, value)
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
if self.value is not None and self.value != "":
return int(self.value)
else:
@@ -577,7 +577,7 @@ class FloatToolParameter(TextToolParameter):
if self.min is not None or self.max is not None:
self.validators.append(validation.InRangeValidator.simple_range_validator(self.min, self.max))
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
try:
return float(value)
@@ -603,7 +603,7 @@ class FloatToolParameter(TextToolParameter):
return None
raise ParameterValueError("a float is required", self.name, value)
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
if self.value is None:
return None
try:
@@ -652,7 +652,7 @@ class BooleanToolParameter(ToolParameter):
self.optional = input_source.get_bool("optional", False)
self.checked = boolean_is_checked(input_source)
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
return self.to_python(value)
def to_python(self, value, app=None):
@@ -665,7 +665,7 @@ class BooleanToolParameter(ToolParameter):
def to_json(self, value, app, use_security):
return self.to_python(value, app)
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
return self.checked
def to_param_dict_string(self, value, other_values=None):
@@ -674,7 +674,7 @@ class BooleanToolParameter(ToolParameter):
else:
return self.falsevalue
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
d = super().to_dict(trans)
d["truevalue"] = self.truevalue
d["falsevalue"] = self.falsevalue
@@ -700,7 +700,7 @@ class FileToolParameter(ToolParameter):
[('argument', None), ('help', ''), ('help_format', 'html'), ('hidden', False), ('is_dynamic', False), ('label', ''), ('model_class', 'FileToolParameter'), ('name', '_name'), ('optional', False), ('refresh_on_change', False), ('type', 'file'), ('value', None)]
"""
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
# Middleware or proxies may encode files in special ways (TODO: this
# should be pluggable)
if isinstance(value, FilesPayload):
@@ -781,7 +781,7 @@ class FTPFileToolParameter(ToolParameter):
self.optional = input_source.parse_optional(True)
self.user_ftp_dir = ""
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
if trans is not None:
if trans.user is not None:
self.user_ftp_dir = f"{trans.user_ftp_dir}/"
@@ -803,7 +803,7 @@ class FTPFileToolParameter(ToolParameter):
else:
return lst[0]
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
return self.to_python(value, trans.app, validate=True)
def to_json(self, value, app, use_security):
@@ -831,7 +831,7 @@ class FTPFileToolParameter(ToolParameter):
raise ValueError("The FTP directory is not configured.")
return lst
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
d = super().to_dict(trans)
d["multiple"] = self.multiple
return d
@@ -855,7 +855,7 @@ class HiddenToolParameter(ToolParameter):
self.value = input_source.get("value")
self.hidden = True
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
return self.value
def get_label(self):
@@ -891,7 +891,7 @@ class ColorToolParameter(ToolParameter):
self.value = get_color_value(input_source)
self.rgb = input_source.get_bool("rgb", False)
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
if self.value is not None:
return self.value.lower()
@@ -922,22 +922,25 @@ class BaseURLToolParameter(HiddenToolParameter):
super().__init__(tool, input_source)
self.value = input_source.get("value", "")
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
if trans is None:
return self.value
return self._get_value(trans)
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
return self._get_value(trans)
def _get_value(self, trans):
def _get_value(self, trans: "ProvidesHistoryContext"):
try:
if not self.value.startswith("/"):
raise Exception("baseurl value must start with a /")
assert trans.url_builder is not None
return trans.url_builder(self.value, qualified=True)
except Exception as e:
log.debug('Url creation failed for "%s": %s', self.name, unicodify(e))
return self.value
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
d = super().to_dict(trans)
return d
@@ -1004,14 +1007,16 @@ class SelectToolParameter(ToolParameter):
self.legal_values.add(value)
self.is_dynamic = (self.dynamic_options is not None) or (self.options is not None)
def _get_dynamic_options_call_other_values(self, trans, other_values):
def _get_dynamic_options_call_other_values(self, trans: "ProvidesHistoryContext", other_values):
call_other_values = ExpressionContext({"__trans__": trans})
if other_values:
call_other_values.parent = other_values.parent
call_other_values.update(other_values.dict)
return call_other_values
def get_options(self, trans, other_values) -> Sequence[ParameterOption | DrillDownOptionsDict]:
def get_options(
self, trans: "ProvidesHistoryContext", other_values
) -> Sequence[ParameterOption | DrillDownOptionsDict]:
if self.options:
return self.options.get_options(trans, other_values)
elif self.dynamic_options:
@@ -1032,24 +1037,24 @@ class SelectToolParameter(ToolParameter):
else:
return [ParameterOption(*o) for o in self.static_options]
def get_legal_values(self, trans, other_values, value):
def get_legal_values(self, trans: "ProvidesHistoryContext", other_values, value):
"""
determine the set of values of legal options
"""
options = cast(list[ParameterOption], self.get_options(trans, other_values))
return {option.dataset or option.value for option in options}
def get_legal_names(self, trans, other_values):
def get_legal_names(self, trans: "ProvidesHistoryContext", other_values):
"""
determine the set of values of legal options
"""
options = cast(list[ParameterOption], self.get_options(trans, other_values))
return {option.name: option.value for option in options}
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
return self._select_from_json(value, trans, other_values=other_values, require_legal_value=True)
def _select_from_json(self, value, trans, other_values=None, require_legal_value=True):
def _select_from_json(self, value, trans: "ProvidesHistoryContext", other_values=None, require_legal_value=True):
other_values = other_values or {}
try:
legal_values = self.get_legal_values(trans, other_values, value)
@@ -1178,11 +1183,18 @@ class SelectToolParameter(ToolParameter):
return history_item_dict_to_python(value, app, self.name)
return super().to_python(value, app)
def get_initial_value(self, trans, other_values):
try:
options = cast(list[ParameterOption], self.get_options(trans, other_values))
except ImplicitConversionRequired:
return None
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
options: list[ParameterOption]
if trans is None:
# Conditional case inference walks tool state without a transaction
# (see galaxy.tools.parameters.visit_input_values); only statically
# declared options can be resolved without one.
options = [ParameterOption(*o) for o in self.static_options]
else:
try:
options = cast(list[ParameterOption], self.get_options(trans, other_values))
except ImplicitConversionRequired:
return None
if not options:
return None
value = [option.value for option in options if option.selected]
@@ -1225,7 +1237,7 @@ class SelectToolParameter(ToolParameter):
else:
return []
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
d = super().to_dict(trans, other_values)
@@ -1237,7 +1249,7 @@ class SelectToolParameter(ToolParameter):
d["textable"] = is_runtime_context(trans, other_values)
return d
def validate(self, value, trans=None):
def validate(self, value, trans: "ProvidesHistoryContext | None" = None):
if not value:
super().validate(value, trans)
if self.multiple:
@@ -1276,7 +1288,7 @@ class GenomeBuildParameter(SelectToolParameter):
self.static_options = [(value, key, False) for key, value in self._get_dbkey_names()]
self.is_dynamic = True
def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
def get_options(self, trans: "ProvidesHistoryContext", other_values) -> Sequence[ParameterOption]:
last_used_build = object()
if trans.history:
last_used_build = trans.history.genome_build
@@ -1285,10 +1297,10 @@ class GenomeBuildParameter(SelectToolParameter):
for dbkey, build_name in self._get_dbkey_names(trans=trans)
]
def get_legal_values(self, trans, other_values, value):
def get_legal_values(self, trans: "ProvidesHistoryContext", other_values, value):
return {dbkey for dbkey, _ in self._get_dbkey_names(trans=trans)}
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
# skip SelectToolParameter (the immediate parent) bc we need to get options in a different way here
d = ToolParameter.to_dict(self, trans)
@@ -1302,7 +1314,7 @@ class GenomeBuildParameter(SelectToolParameter):
d.update(
{
"options": serialize_options(trans, options),
"options": serialize_options(trans.security, options),
"value": value,
"display": self.display,
"multiple": self.multiple,
@@ -1311,7 +1323,7 @@ class GenomeBuildParameter(SelectToolParameter):
return d
def _get_dbkey_names(self, trans=None):
def _get_dbkey_names(self, trans: "ProvidesHistoryContext | None" = None):
if not self.tool:
# Hack for unit tests, since we have no tool
return read_dbnames(None)
@@ -1341,7 +1353,7 @@ class SelectTagParameter(SelectToolParameter):
self.default_value = input_source.get("value", None)
self.is_dynamic = True
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
if self.multiple:
tag_list = []
@@ -1384,7 +1396,7 @@ class SelectTagParameter(SelectToolParameter):
tags.add(tag.user_value)
return list(tags)
def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
def get_options(self, trans: "ProvidesHistoryContext", other_values) -> Sequence[ParameterOption]:
"""
Show tags
"""
@@ -1393,12 +1405,12 @@ class SelectTagParameter(SelectToolParameter):
options.append(ParameterOption(f"Tags: {tag}", tag, False))
return options
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
if self.default_value is not None:
return self.default_value
return super().get_initial_value(trans, other_values)
def get_legal_values(self, trans, other_values, value):
def get_legal_values(self, trans: "ProvidesHistoryContext", other_values, value):
if self.data_ref not in other_values and not trans.workflow_building_mode:
raise ValueError("Value for associated data reference not found (data_ref).")
return set(self.get_tag_list(other_values))
@@ -1406,7 +1418,7 @@ class SelectTagParameter(SelectToolParameter):
def get_dependencies(self):
return [self.data_ref]
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
d = super().to_dict(trans, other_values=other_values)
d["data_ref"] = self.data_ref
@@ -1467,7 +1479,7 @@ class ColumnListParameter(SelectToolParameter):
return value.strip()
return value
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
"""
Label convention prepends column number with a 'c', but tool uses the integer. This
removes the 'c' when entered into a workflow.
@@ -1500,7 +1512,7 @@ class ColumnListParameter(SelectToolParameter):
column = column.lower()[1:]
return column
def get_column_list(self, trans, other_values):
def get_column_list(self, trans: "ProvidesHistoryContext", other_values):
"""
Generate a select list containing the columns of the associated
dataset (if found).
@@ -1545,7 +1557,7 @@ class ColumnListParameter(SelectToolParameter):
column_list = [c for c in column_list if c in this_column_list]
return column_list
def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
def get_options(self, trans: "ProvidesHistoryContext", other_values) -> Sequence[ParameterOption]:
"""
Show column labels rather than c1..cn if use_header_names=True
"""
@@ -1585,12 +1597,12 @@ class ColumnListParameter(SelectToolParameter):
options = [ParameterOption(f"Column: {col}", col, False) for col in column_list]
return options
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
if self.default_value is not None:
return self.default_value
return super().get_initial_value(trans, other_values)
def get_legal_values(self, trans, other_values, value):
def get_legal_values(self, trans: "ProvidesHistoryContext", other_values, value):
if self.data_ref not in other_values:
raise ValueError("Value for associated data reference not found (data_ref).")
legal_values = self.get_column_list(trans, other_values)
@@ -1604,7 +1616,7 @@ class ColumnListParameter(SelectToolParameter):
return set(legal_values)
def is_file_empty(self, trans, other_values):
def is_file_empty(self, trans: "ProvidesHistoryContext", other_values):
for dataset in util.listify(other_values.get(self.data_ref)):
# Use representative dataset if a dataset collection is parsed
if isinstance(dataset, HistoryDatasetCollectionAssociation):
@@ -1628,7 +1640,7 @@ class ColumnListParameter(SelectToolParameter):
def get_dependencies(self):
return [self.data_ref]
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
d = super().to_dict(trans, other_values=other_values)
d["data_ref"] = self.data_ref
@@ -1701,7 +1713,7 @@ class DrillDownSelectToolParameter(SelectToolParameter):
self.dynamic_options = None
self.options = input_source.parse_drill_down_static_options(tool_data_path)
def _get_options_from_code(self, trans=None, other_values=None):
def _get_options_from_code(self, trans: "ProvidesHistoryContext | None" = None, other_values=None):
assert self.dynamic_options, Exception("dynamic_options was not specifed")
call_other_values = ExpressionContext({"__trans__": trans, "__value__": None})
if other_values:
@@ -1713,7 +1725,9 @@ class DrillDownSelectToolParameter(SelectToolParameter):
except Exception:
return []
def get_options(self, trans=None, other_values=None) -> list[DrillDownOptionsDict]:
def get_options(
self, trans: "ProvidesHistoryContext | None" = None, other_values=None
) -> list[DrillDownOptionsDict]:
other_values = other_values or {}
if self.is_dynamic:
if self.dynamic_options:
@@ -1722,7 +1736,7 @@ class DrillDownSelectToolParameter(SelectToolParameter):
return self.options
def get_legal_values(self, trans, other_values, value):
def get_legal_values(self, trans: "ProvidesHistoryContext", other_values, value):
def recurse_options(legal_values, options: list[DrillDownOptionsDict]):
for option in options:
legal_values.append(option["value"])
@@ -1732,7 +1746,7 @@ class DrillDownSelectToolParameter(SelectToolParameter):
recurse_options(legal_values, self.get_options(trans=trans, other_values=other_values))
return legal_values
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
legal_values = self.get_legal_values(trans, other_values, value)
if not legal_values and trans.workflow_building_mode:
@@ -1813,7 +1827,7 @@ class DrillDownSelectToolParameter(SelectToolParameter):
rval = sanitize_param(rval)
return rval
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
def recurse_options(initial_values, options: list[DrillDownOptionsDict]):
for option in options:
if option["selected"]:
@@ -1865,7 +1879,7 @@ class DrillDownSelectToolParameter(SelectToolParameter):
def get_dependencies(self):
return []
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
# skip SelectToolParameter (the immediate parent) bc we need to get options in a different way here
d = ToolParameter.to_dict(self, trans)
@@ -1961,7 +1975,7 @@ class BaseDataToolParameter(ToolParameter):
# with None" (which is a legitimate return for parameters with no formats).
_ACCEPTABLE_EXTENSIONS_UNSET: Any = object()
def __init__(self, tool: Optional["Tool"], input_source, trans):
def __init__(self, tool: Optional["Tool"], input_source, trans: "ProvidesHistoryContext | None"):
super().__init__(tool, input_source)
self.min = input_source.get("min")
self.max = input_source.get("max")
@@ -1993,7 +2007,7 @@ class BaseDataToolParameter(ToolParameter):
self.tool.app.datatypes_registry
) # can be None if self.tool.app is a ValidationContext
def _parse_formats(self, trans, input_source):
def _parse_formats(self, trans: "ProvidesHistoryContext | None", input_source):
"""
Build list of classes for supported data formats
"""
@@ -2058,7 +2072,8 @@ class BaseDataToolParameter(ToolParameter):
return True
return False
def get_initial_value(self, trans, other_values):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", other_values):
assert trans is not None
if trans.workflow_building_mode is workflow_building_modes.ENABLED or trans.app.name == "tool_shed":
return RuntimeValue()
if self.optional:
@@ -2157,7 +2172,7 @@ class BaseDataToolParameter(ToolParameter):
else:
return app.model.context.get(HistoryDatasetAssociation, int(value))
def validate(self, value, trans=None):
def validate(self, value, trans: "ProvidesHistoryContext | None" = None):
def do_validate(v):
for validator in self.validators:
if (
@@ -2297,7 +2312,7 @@ class DataToolParameter(BaseDataToolParameter):
security stuff will dramatically alter this anyway.
"""
def __init__(self, tool: Optional["Tool"], input_source, trans=None):
def __init__(self, tool: Optional["Tool"], input_source, trans: "ProvidesHistoryContext | None" = None):
input_source = ensure_input_source(input_source)
super().__init__(tool, input_source, trans)
self.load_contents = int(input_source.get("load_contents", 0))
@@ -2348,7 +2363,7 @@ class DataToolParameter(BaseDataToolParameter):
allow_uri_if_protocol = input_source.get("allow_uri_if_protocol", None)
self.allow_uri_if_protocol = allow_uri_if_protocol.split(",") if allow_uri_if_protocol else []
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
session = trans.sa_session
other_values = other_values or {}
@@ -2358,6 +2373,7 @@ class DataToolParameter(BaseDataToolParameter):
raise ParameterValueError("specify a dataset of the required format / build for parameter", self.name)
if value in [None, "None", ""]:
if self.default_object:
assert trans.history is not None
return raw_to_galaxy(trans.app, trans.history, self.default_object)
return None
batch_wrapper = False
@@ -2529,7 +2545,7 @@ class DataToolParameter(BaseDataToolParameter):
else:
return []
def converter_safe(self, other_values, trans):
def converter_safe(self, other_values, trans: "ProvidesHistoryContext"):
if (
self.tool is None
or self.tool.has_multiple_pages
@@ -2574,7 +2590,9 @@ class DataToolParameter(BaseDataToolParameter):
ref = ref()
return str(ref)
def to_dict(self, trans, other_values=None, pagination: ParameterPaginationT | None = None):
def to_dict(
self, trans: "ProvidesHistoryContext", other_values=None, pagination: ParameterPaginationT | None = None
):
other_values = other_values or {}
d = super().to_dict(trans)
self._fill_to_dict_static(d)
@@ -2793,7 +2811,7 @@ class DataToolParameter(BaseDataToolParameter):
make_hdca_entry(builder.security, hdca, name, keep=False, subcollection_type=subcollection_type)
)
def _history_query(self, trans):
def _history_query(self, trans: "ProvidesHistoryContext"):
assert self.multiple
dataset_collection_type_descriptions = trans.app.dataset_collection_manager.collection_type_descriptions
# If multiple data parameter, treat like a list parameter.
@@ -2803,7 +2821,7 @@ class DataToolParameter(BaseDataToolParameter):
class DataCollectionToolParameter(BaseDataToolParameter):
""" """
def __init__(self, tool: Optional["Tool"], input_source, trans=None):
def __init__(self, tool: Optional["Tool"], input_source, trans: "ProvidesHistoryContext | None" = None):
input_source = ensure_input_source(input_source)
super().__init__(tool, input_source, trans)
self._parse_formats(trans, input_source)
@@ -2830,11 +2848,11 @@ class DataCollectionToolParameter(BaseDataToolParameter):
def collection_types(self) -> list[str] | None:
return self._collection_types
def _history_query(self, trans):
def _history_query(self, trans: "ProvidesHistoryContext"):
dataset_collection_type_descriptions = trans.app.dataset_collection_manager.collection_type_descriptions
return query.HistoryQuery.from_parameter(self, dataset_collection_type_descriptions)
def match_collections(self, trans, history, dataset_collection_matcher):
def match_collections(self, trans: "ProvidesHistoryContext", history, dataset_collection_matcher):
dataset_collections = trans.app.dataset_collection_manager.history_dataset_collections(
history, self._history_query(trans)
)
@@ -2850,7 +2868,7 @@ class DataCollectionToolParameter(BaseDataToolParameter):
continue
yield dataset_collection_instance, match.implicit_conversion
def match_multirun_collections(self, trans, history, dataset_collection_matcher):
def match_multirun_collections(self, trans: "ProvidesHistoryContext", history, dataset_collection_matcher):
for history_dataset_collection in history.active_visible_dataset_collections:
if not self._history_query(trans).can_map_over(history_dataset_collection):
continue
@@ -2859,7 +2877,7 @@ class DataCollectionToolParameter(BaseDataToolParameter):
if match:
yield history_dataset_collection, match.implicit_conversion
def from_json(self, value, trans, other_values=None):
def from_json(self, value, trans: "ProvidesHistoryContext", other_values=None):
session = trans.sa_session
other_values = other_values or {}
@@ -2870,6 +2888,7 @@ class DataCollectionToolParameter(BaseDataToolParameter):
raise ParameterValueError("specify a dataset collection of the correct type", self.name)
if value in [None, "None"]:
if self.default_object:
assert trans.history is not None
return raw_to_galaxy(trans.app, trans.history, self.default_object)
return None
if isinstance(value, MutableMapping) and "values" in value:
@@ -2933,7 +2952,9 @@ class DataCollectionToolParameter(BaseDataToolParameter):
display_text = "No dataset collection."
return display_text
def to_dict(self, trans, other_values=None, pagination: ParameterPaginationT | None = None):
def to_dict(
self, trans: "ProvidesHistoryContext", other_values=None, pagination: ParameterPaginationT | None = None
):
other_values = other_values or {}
d = super().to_dict(trans)
d["collection_types"] = self.collection_types
@@ -3098,10 +3119,11 @@ class BaseJsonToolParameter(ToolParameter):
class DirectoryUriToolParameter(SimpleTextToolParameter):
"""galaxy.files URIs for directories."""
def validate(self, value, trans=None):
def validate(self, value, trans: "ProvidesHistoryContext | None" = None):
super().validate(value, trans=trans)
if not value:
return # value is not set yet, do not validate
assert trans is not None
# Skip file source validation in workflow building mode to allow workflows
# referencing removed file sources to be exported/viewed. Users can then
# download and edit them. Validation still occurs during tool execution.
@@ -3135,7 +3157,7 @@ class RulesListToolParameter(BaseJsonToolParameter):
super().__init__(tool, input_source)
self.data_ref = input_source.get("data_ref", None)
def to_dict(self, trans, other_values=None):
def to_dict(self, trans: "ProvidesHistoryContext", other_values=None):
other_values = other_values or {}
d = ToolParameter.to_dict(self, trans)
if target := other_values.get(self.data_ref):
@@ -3146,7 +3168,7 @@ class RulesListToolParameter(BaseJsonToolParameter):
}
return d
def validate(self, value, trans=None):
def validate(self, value, trans: "ProvidesHistoryContext | None" = None):
super().validate(value, trans=trans)
if not isinstance(value, MutableMapping):
raise ValueError("No rules specified for rules parameter.")
+16 -9
View File
@@ -1,19 +1,26 @@
from logging import getLogger
from typing import (
Any,
TYPE_CHECKING,
)
import galaxy.model
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesHistoryContext
log = getLogger(__name__)
def set_dataset_matcher_factory(trans, tool):
def set_dataset_matcher_factory(trans: "ProvidesHistoryContext", tool):
trans.dataset_matcher_factory = DatasetMatcherFactory(trans, tool)
def unset_dataset_matcher_factory(trans):
def unset_dataset_matcher_factory(trans: "ProvidesHistoryContext"):
trans.dataset_matcher_factory = None
def get_dataset_matcher_factory(trans):
def get_dataset_matcher_factory(trans: "ProvidesHistoryContext"):
dataset_matcher_factory = getattr(trans, "dataset_matcher_factory", None)
return dataset_matcher_factory or DatasetMatcherFactory(trans)
@@ -21,11 +28,11 @@ def get_dataset_matcher_factory(trans):
class DatasetMatcherFactory:
""""""
def __init__(self, trans, tool=None):
def __init__(self, trans: "ProvidesHistoryContext", tool=None):
self._trans = trans
self._tool = tool
self._data_inputs = []
self._matches_format_cache = {}
self._data_inputs: list[Any] = []
self._matches_format_cache: dict[str, dict[str, bool]] = {}
if tool:
valid_input_states = tool.valid_input_states
else:
@@ -96,7 +103,7 @@ class DatasetMatcher:
and permission handling.
"""
def __init__(self, dataset_matcher_factory, trans, param, other_values):
def __init__(self, dataset_matcher_factory, trans: "ProvidesHistoryContext", param, other_values):
self.dataset_matcher_factory = dataset_matcher_factory
self.trans = trans
self.param = param
@@ -202,7 +209,7 @@ class HdcaImplicitMatch:
class SummaryDatasetCollectionMatcher:
def __init__(self, dataset_matcher_factory, trans, dataset_matcher):
def __init__(self, dataset_matcher_factory, trans: "ProvidesHistoryContext", dataset_matcher):
self.dataset_matcher_factory = dataset_matcher_factory
self._trans = trans
self.dataset_matcher = dataset_matcher
@@ -238,7 +245,7 @@ class SummaryDatasetCollectionMatcher:
class DatasetCollectionMatcher:
def __init__(self, trans, dataset_matcher):
def __init__(self, trans: "ProvidesHistoryContext", dataset_matcher):
self.dataset_matcher = dataset_matcher
self._trans = trans
+17 -17
View File
@@ -18,6 +18,7 @@ from typing import (
Literal,
)
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.model import (
DatasetCollectionElement,
HistoryDatasetAssociation,
@@ -39,7 +40,6 @@ from galaxy.util import (
string_as_bool,
)
from galaxy.util.template import fill_template
from galaxy.work.context import WorkRequestContext
from . import validation
from .cancelable_request import request
@@ -66,7 +66,7 @@ class Filter:
"""Returns the name of any dependencies, otherwise None"""
return None
def filter_options(self, options, trans, other_values):
def filter_options(self, options, trans: ProvidesHistoryContext, other_values):
"""Returns a list of options after the filter is applied"""
raise TypeError("Abstract Method")
@@ -94,7 +94,7 @@ class StaticValueFilter(Filter):
self.column = d_option.column_spec_to_index(column)
self.keep = string_as_bool(elem.get("keep", "True"))
def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
def filter_options(self, options: Sequence[ParameterOption], trans: ProvidesHistoryContext, other_values):
rval = []
filter_value = self.value
try:
@@ -130,7 +130,7 @@ class RegexpFilter(Filter):
self.column = d_option.column_spec_to_index(column)
self.keep = string_as_bool(elem.get("keep", "True"))
def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
def filter_options(self, options: Sequence[ParameterOption], trans: ProvidesHistoryContext, other_values):
rval = []
filter_value = self.value
try:
@@ -186,7 +186,7 @@ class DataMetaFilter(Filter):
def get_dependency_name(self):
return self.ref_name
def filter_options(self, options: Sequence[ParameterOption], trans: WorkRequestContext | None, other_values):
def filter_options(self, options: Sequence[ParameterOption], trans: ProvidesHistoryContext, other_values):
options = list(options)
if trans and trans.workflow_building_mode is workflow_building_modes.USE_HISTORY:
# We're in the run form, can't possibly apply a data_meta filter.
@@ -295,7 +295,7 @@ class ParamValueFilter(Filter):
def get_dependency_name(self):
return self.ref_name
def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
def filter_options(self, options: Sequence[ParameterOption], trans: ProvidesHistoryContext, other_values):
ref = other_values.get(self.ref_name, None)
if ref is None:
ref = []
@@ -348,7 +348,7 @@ class UniqueValueFilter(Filter):
def get_dependency_name(self):
return self.dynamic_option.dataset_ref_name
def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
def filter_options(self, options: Sequence[ParameterOption], trans: ProvidesHistoryContext, other_values):
rval = []
seen = set()
for fields in options:
@@ -377,7 +377,7 @@ class MultipleSplitterFilter(Filter):
assert columns is not None, "Required 'column' attribute missing from filter"
self.columns = [d_option.column_spec_to_index(column) for column in columns.split(",")]
def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
def filter_options(self, options: Sequence[ParameterOption], trans: ProvidesHistoryContext, other_values):
rval = []
for fields in options:
for column in self.columns:
@@ -412,7 +412,7 @@ class AttributeValueSplitterFilter(Filter):
assert columns is not None, "Required 'column' attribute missing from filter"
self.columns = [d_option.column_spec_to_index(column) for column in columns.split(",")]
def filter_options(self, options, trans, other_values):
def filter_options(self, options, trans: ProvidesHistoryContext, other_values):
attr_names = set()
rval = []
for fields in options:
@@ -451,7 +451,7 @@ class AdditionalValueFilter(Filter):
if self.index is not None:
self.index = int(self.index)
def filter_options(self, options, trans, other_values):
def filter_options(self, options, trans: ProvidesHistoryContext, other_values):
rval = list(options)
add_value = []
for _ in range(self.dynamic_option.largest_index + 1):
@@ -499,7 +499,7 @@ class RemoveValueFilter(Filter):
self.multiple = string_as_bool(elem.get("multiple", "False"))
self.separator = elem.get("separator", ",")
def filter_options(self, options, trans, other_values):
def filter_options(self, options, trans: ProvidesHistoryContext, other_values):
from galaxy.tools.wrappers import DatasetFilenameWrapper
if trans is not None and trans.workflow_building_mode:
@@ -551,7 +551,7 @@ class SortByColumnFilter(Filter):
self.column = d_option.column_spec_to_index(column)
self.reverse = string_as_bool(elem.get("reverse_sort_order", "False"))
def filter_options(self, options, trans, other_values):
def filter_options(self, options, trans: ProvidesHistoryContext, other_values):
return sorted(options, key=lambda x: x[self.column], reverse=self.reverse)
@@ -586,7 +586,7 @@ class DataTableFilter(Filter):
assert self.data_table_column is not None, "Required 'data_table_column' attribute missing from filter"
self.keep = string_as_bool(elem.get("keep", "True"))
def filter_options(self, options, trans, other_values):
def filter_options(self, options, trans: ProvidesHistoryContext, other_values):
# get column from data table, by index or column name
entries = None
try:
@@ -786,7 +786,7 @@ class DynamicOptions:
rval.append(depend)
return rval
def get_fields(self, trans, other_values):
def get_fields(self, trans: ProvidesHistoryContext, other_values):
if self.dataset_ref_name:
try:
datasets = _get_ref_data(other_values, self.dataset_ref_name)
@@ -914,7 +914,7 @@ class DynamicOptions:
assert len(entries) == 1, "Cannot pass tool data bundle with more than 1 data entry per table"
return next(iter(entries.values()))
def get_fields_by_value(self, value, trans, other_values):
def get_fields_by_value(self, value, trans: ProvidesHistoryContext, other_values):
"""
Return a list of fields with column 'value' matching provided value.
"""
@@ -925,7 +925,7 @@ class DynamicOptions:
rval.append(fields)
return rval
def get_field_by_name_for_value(self, field_name, value, trans, other_values):
def get_field_by_name_for_value(self, field_name, value, trans: ProvidesHistoryContext, other_values):
"""
Get contents of field by name for specified value.
"""
@@ -942,7 +942,7 @@ class DynamicOptions:
rval.append(fields[field_index])
return rval
def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
def get_options(self, trans: ProvidesHistoryContext, other_values) -> Sequence[ParameterOption]:
rval: list[ParameterOption] = []
+21 -19
View File
@@ -34,6 +34,7 @@ from galaxy.util.dictifiable import UsesDictVisibleKeys
from galaxy.util.expressions import ExpressionContext
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesHistoryContext
from galaxy.tools import Tool
from galaxy.tools.parameters import ToolInputsT
from galaxy.tools.parameters.basic import ToolParameter
@@ -84,13 +85,13 @@ class Group(UsesDictVisibleKeys):
"""
return value
def get_initial_value(self, trans, context):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", context):
"""
Return the initial state/value for this group
"""
raise TypeError("Not implemented")
def to_dict(self, trans):
def to_dict(self, trans: "ProvidesHistoryContext"):
group_dict = self._dictify_view_keys()
return group_dict
@@ -167,7 +168,7 @@ class Repeat(Group):
raise
return rval
def get_initial_value(self, trans, context):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", context):
if self.inputs is None:
raise Exception("Must set 'inputs' attribute to use.")
rval = []
@@ -179,7 +180,7 @@ class Repeat(Group):
rval.append(rval_dict)
return rval
def to_dict(self, trans):
def to_dict(self, trans: "ProvidesHistoryContext"):
if self.inputs is None:
raise Exception("Must set 'inputs' attribute to use.")
repeat_dict = super().to_dict(trans)
@@ -235,7 +236,7 @@ class Section(Group):
raise
return rval
def get_initial_value(self, trans, context):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", context):
if self.inputs is None:
raise Exception("Must set 'inputs' attribute to use.")
rval: dict[str, Any] = {}
@@ -244,7 +245,7 @@ class Section(Group):
rval[child_input.name] = child_input.get_initial_value(trans, child_context)
return rval
def to_dict(self, trans):
def to_dict(self, trans: "ProvidesHistoryContext"):
if self.inputs is None:
raise Exception("Must set 'inputs' attribute to use.")
section_dict = super().to_dict(trans)
@@ -328,7 +329,7 @@ class UploadDataset(Group):
dbkey = parent_context.get("dbkey", dbkey)
return dbkey
def get_datatype_ext(self, trans, context, parent_context=None):
def get_datatype_ext(self, trans: "ProvidesHistoryContext", context, parent_context=None):
ext = self.get_file_type(context, parent_context=parent_context)
if ext in self.file_type_to_ext:
ext = self.file_type_to_ext[
@@ -336,7 +337,7 @@ class UploadDataset(Group):
] # when using autodetect, we will use composite info from 'text', i.e. only the main file
return ext
def get_datatype(self, trans, context, parent_context=None):
def get_datatype(self, trans: "ProvidesHistoryContext", context, parent_context=None):
ext = self.get_datatype_ext(trans, context, parent_context=parent_context)
return trans.app.datatypes_registry.get_datatype_by_extension(ext)
@@ -347,7 +348,7 @@ class UploadDataset(Group):
def group_title(self, context):
return f"{self.title} ({context.get(self.file_type_name, self.default_file_type)})"
def title_by_index(self, trans, index, context):
def title_by_index(self, trans: "ProvidesHistoryContext", index, context):
d_type = self.get_datatype(trans, context)
for i, (composite_name, composite_file) in enumerate(d_type.writable_files.items()):
if i == index:
@@ -398,7 +399,7 @@ class UploadDataset(Group):
raise
return rval
def get_file_count(self, trans, context):
def get_file_count(self, trans: "ProvidesHistoryContext", context):
file_count = context.get("file_count", "auto")
if file_count == "auto":
d_type = self.get_datatype(trans, context)
@@ -406,9 +407,10 @@ class UploadDataset(Group):
else:
return int(file_count)
def get_initial_value(self, trans, context):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", context):
if self.inputs is None:
raise Exception("Must set 'inputs' attribute to use.")
assert trans is not None
file_count = self.get_file_count(trans, context)
rval = []
for i in range(file_count):
@@ -419,7 +421,7 @@ class UploadDataset(Group):
rval.append(rval_dict)
return rval
def get_uploaded_datasets(self, trans, context, override_name=None, override_info=None):
def get_uploaded_datasets(self, trans: "ProvidesHistoryContext", context, override_name=None, override_info=None):
def get_data_file_filename(data_file, override_name=None, override_info=None, purge=True):
dataset_name = override_name
@@ -510,6 +512,7 @@ class UploadDataset(Group):
warnings.append("All FTP uploaded file selections were ignored.")
elif ftp_files is not None and trans.user is not None: # look for files uploaded via FTP
user_ftp_dir = trans.user_ftp_dir
assert user_ftp_dir is not None
assert not os.path.islink(user_ftp_dir), "User FTP directory cannot be a symbolic link"
for dirpath, _dirnames, filenames in os.walk(user_ftp_dir):
for filename in filenames:
@@ -598,16 +601,14 @@ class UploadDataset(Group):
# TODO: warning to the user (could happen if session has become invalid)
else:
user_ftp_dir = trans.user_ftp_dir
assert user_ftp_dir is not None
assert not os.path.islink(user_ftp_dir), "User FTP directory cannot be a symbolic link"
for dirpath, _dirnames, filenames in os.walk(user_ftp_dir):
for filename in filenames:
path = relpath(os.path.join(dirpath, filename), user_ftp_dir)
if not os.path.islink(os.path.join(dirpath, filename)):
# Normalize filesystem paths
if isinstance(path, str):
valid_files.append(unicodedata.normalize("NFC", path))
else:
valid_files.append(path)
valid_files.append(unicodedata.normalize("NFC", path))
else:
ftp_files = []
@@ -616,6 +617,7 @@ class UploadDataset(Group):
log.warning(f"User passed an invalid file path in ftp_files: {ftp_file}")
continue
# TODO: warning to the user (could happen if file is already imported)
assert user_ftp_dir is not None
ftp_data_file = {
"local_filename": os.path.abspath(os.path.join(user_ftp_dir, ftp_file)),
"filename": os.path.basename(ftp_file),
@@ -804,7 +806,7 @@ class Conditional(Group):
raise
return rval
def get_initial_value(self, trans, context):
def get_initial_value(self, trans: "ProvidesHistoryContext | None", context):
if self.test_param is None:
raise Exception("Must set 'test_param' attribute to use.")
# State for a conditional is a plain dictionary.
@@ -823,7 +825,7 @@ class Conditional(Group):
rval[child_input.name] = child_input.get_initial_value(trans, child_context)
return rval
def to_dict(self, trans):
def to_dict(self, trans: "ProvidesHistoryContext"):
if self.test_param is None:
raise Exception("Must set 'test_param' attribute to use.")
cond_dict = super().to_dict(trans)
@@ -843,7 +845,7 @@ class ConditionalWhen(UsesDictVisibleKeys):
self.value = None
self.inputs = None
def to_dict(self, trans):
def to_dict(self, trans: "ProvidesHistoryContext"):
if self.inputs is None:
raise Exception("Must set 'inputs' attribute to use.")
when_dict = self._dictify_view_keys()
+20 -11
View File
@@ -3,11 +3,19 @@
import json
import logging
import os
from collections.abc import Iterable
from typing import (
Any,
)
import h5py
import numpy as np
import yaml
from galaxy.managers.context import (
ProvidesAppContext,
ProvidesHistoryContext,
)
from galaxy.tools.parameters import populate_state
from galaxy.tools.parameters.workflow_utils import workflow_building_modes
from galaxy.util import (
@@ -115,7 +123,7 @@ class ToolRecommendations:
outputs = Dense(vocab_size, activation="sigmoid")(x)
return Model(inputs=inputs, outputs=[outputs, weights])
def get_predictions(self, trans, tool_sequence, remote_model_url):
def get_predictions(self, trans: ProvidesHistoryContext, tool_sequence, remote_model_url):
"""
Compute tool predictions
"""
@@ -126,7 +134,7 @@ class ToolRecommendations:
recommended_tools = self.__compute_tool_prediction(trans, tool_sequence)
return tool_sequence, recommended_tools
def __set_model(self, trans, remote_model_url):
def __set_model(self, trans: ProvidesAppContext, remote_model_url):
"""
Create model and associated dictionaries for recommendations
"""
@@ -185,7 +193,7 @@ class ToolRecommendations:
model_file.write(model_binary.content)
return local_dir
def __get_tool_extensions(self, trans, tool_id):
def __get_tool_extensions(self, trans: ProvidesHistoryContext, tool_id):
"""
Get the input and output extensions of a tool
"""
@@ -194,7 +202,7 @@ class ToolRecommendations:
trans.workflow_building_mode = workflow_building_modes.ENABLED
module = module_factory.from_dict(trans, payload)
if "tool_state" not in payload:
module_state = {}
module_state: dict[str, Any] = {}
populate_state(trans, module.get_inputs(), inputs, module_state, check=False)
module.recover_state(module_state)
inputs = module.get_all_inputs(connectable_only=True)
@@ -207,7 +215,9 @@ class ToolRecommendations:
output_extensions.extend(o_ext["extensions"])
return input_extensions, output_extensions
def __filter_tool_predictions(self, trans, prediction_data, tool_ids, tool_scores, last_tool_name):
def __filter_tool_predictions(
self, trans: ProvidesHistoryContext, prediction_data, tool_ids, tool_scores, last_tool_name
):
"""
Filter tool predictions based on datatype compatibility and tool connections.
Add admin preferences to recommendations.
@@ -216,7 +226,7 @@ class ToolRecommendations:
# get the list of datatype extensions of the last tool of the tool sequence
_, last_output_extensions = self.__get_tool_extensions(trans, self.all_tools[last_tool_name][0])
prediction_data["o_extensions"] = list(set(last_output_extensions))
t_ids_scores = zip(tool_ids, tool_scores)
t_ids_scores: Iterable[tuple[Any, Any]] = zip(tool_ids, tool_scores)
# form the payload of the predicted tools to be shown
for child, score in t_ids_scores:
c_dict = {}
@@ -327,14 +337,14 @@ class ToolRecommendations:
sorted_c_t, sorted_c_v = self.__get_predicted_tools(last_base_tools, pred_tool_names, last_tool_name, topk)
return sorted_c_t, sorted_c_v
def __compute_tool_prediction(self, trans, tool_sequence):
def __compute_tool_prediction(self, trans: ProvidesHistoryContext, tool_sequence):
"""
Compute the predicted tools for a tool sequences
Return a payload with the tool sequences and recommended tools
Return an empty payload with just the tool sequence if anything goes wrong within the try block
"""
topk = trans.app.config.topk_recommendations
prediction_data = {}
prediction_data: dict[str, Any] = {}
tool_sequence = tool_sequence.split(",")[::-1]
prediction_data["name"] = ",".join(tool_sequence)
prediction_data["children"] = []
@@ -351,15 +361,14 @@ class ToolRecommendations:
except Exception:
log.exception(f"Failed to find tool {tool_name} in model")
return prediction_data
sample = np.reshape(sample, (1, self.max_seq_len))
model_input = np.reshape(sample, (1, self.max_seq_len))
# boost the predicted scores using tools' usage
weight_values = list(self.tool_weights_sorted.values())
# predict next tools for a test path
try:
import tensorflow as tf
sample = tf.convert_to_tensor(sample, dtype=tf.int64)
prediction, _ = self.loaded_model(sample, training=False)
prediction, _ = self.loaded_model(tf.convert_to_tensor(model_input, dtype=tf.int64), training=False)
except Exception as e:
log.exception(e)
return prediction_data
+2 -1
View File
@@ -49,6 +49,7 @@ from galaxy.util import (
)
if TYPE_CHECKING:
from galaxy.datatypes.data import Data
from galaxy.datatypes.registry import Registry
from galaxy.job_execution.compute_environment import ComputeEnvironment
from galaxy.model.metadata import MetadataCollection
@@ -359,7 +360,7 @@ class DatasetFilenameWrapper(ToolParameterValueWrapper):
compute_environment: Optional["ComputeEnvironment"] = None,
identifier: str | None = None,
io_type: str = "input",
formats: list[str] | None = None,
formats: Sequence[Union[str, "Data"]] | None = None,
tool_evaluator: Optional["ToolEvaluator"] = None,
) -> None:
dataset_instance: DatasetInstance | None = None
+2 -1
View File
@@ -12,6 +12,7 @@ from galaxy import util
from galaxy.util.tool_shed import encoding_util
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
from .tool_shed_registry import Registry as ToolShedRegistry
log = logging.getLogger(__name__)
@@ -149,7 +150,7 @@ def get_tool_shed_repository_url(app: HasToolShedRegistry, tool_shed: str, owner
return tool_shed_url
def handle_galaxy_url(trans, **kwd):
def handle_galaxy_url(trans: "GalaxyWebTransaction", **kwd):
galaxy_url = kwd.get("galaxy_url", None)
if galaxy_url:
trans.set_cookie(galaxy_url, name="toolshedgalaxyurl")
@@ -21,6 +21,7 @@ from galaxy.datatypes.tabular import (
)
from galaxy.datatypes.xml import Phyloxml
from galaxy.exceptions import RequestParameterInvalidException
from galaxy.managers.context import ProvidesAppContext
from galaxy.model import NoConverterException
from galaxy.visualization.data_providers import genome
from galaxy.visualization.data_providers.basic import (
@@ -61,7 +62,7 @@ class DataProviderRegistry:
"column_with_stats": ColumnDataProvider,
}
def get_data_provider(self, trans, name=None, source="data", raw=False, original_dataset=None):
def get_data_provider(self, trans: ProvidesAppContext, name=None, source="data", raw=False, original_dataset=None):
"""
Returns data provider matching parameter values. For standalone data
sources, source parameter is ignored.
+6 -2
View File
@@ -10,6 +10,10 @@ from galaxy.exceptions import (
ObjectNotFound,
ReferenceDataError,
)
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.model import (
HistoryDatasetAssociation,
User,
@@ -286,7 +290,7 @@ class Genomes:
return dbkeys
def chroms(self, trans, dbkey=None, num=None, chrom=None, low=None):
def chroms(self, trans: ProvidesHistoryContext, dbkey=None, num=None, chrom=None, low=None):
"""
Returns a naturally sorted list of chroms/contigs for a given dbkey.
Use either chrom or low to specify the starting chrom in the return list.
@@ -364,7 +368,7 @@ class Genomes:
return False
def reference(self, trans, dbkey, chrom, low, high):
def reference(self, trans: ProvidesUserContext, dbkey, chrom, low, high):
"""
Return reference data for a build.
"""
@@ -1,5 +1,7 @@
import logging
from galaxy.managers.context import ProvidesAppContext
log = logging.getLogger(__name__)
@@ -29,7 +31,7 @@ def _deferred_source_uri(target_object) -> str | None:
return None
def is_object_applicable(trans, target_object, data_source_tests):
def is_object_applicable(trans: ProvidesAppContext, target_object, data_source_tests):
"""
Run a visualization's data_source tests to find out if
it can be applied to the target_object.
+3 -2
View File
@@ -20,6 +20,7 @@ from galaxy.visualization.plugins.datasource_testing import is_object_applicable
from galaxy.visualization.plugins.plugin import VisualizationPlugin
if TYPE_CHECKING:
from galaxy.managers.context import ProvidesAppContext
from galaxy.structured_app import StructuredApp
log = logging.getLogger(__name__)
@@ -150,7 +151,7 @@ class VisualizationsRegistry:
return self.plugins[key]
# -- building links to visualizations from objects --
def get_visualizations(self, trans, target_object=None, embeddable=None):
def get_visualizations(self, trans: "ProvidesAppContext", target_object=None, embeddable=None):
"""
Get the names of visualizations usable on the `target_object` and
the urls to call in order to render the visualizations.
@@ -166,7 +167,7 @@ class VisualizationsRegistry:
result.append(vis_plugin.to_dict())
return sorted(result, key=lambda k: k.get("html"))
def get_visualization(self, trans, visualization_name, target_object):
def get_visualization(self, trans: "ProvidesAppContext", visualization_name, target_object):
"""
Return data to build a url to the visualization with the given
`visualization_name` if it's applicable to `target_object` or
@@ -7,6 +7,7 @@ import json
import logging
import weakref
from collections.abc import Callable
from typing import TYPE_CHECKING
import galaxy.exceptions
import galaxy.util
@@ -14,6 +15,7 @@ from galaxy.managers import (
hdas as hda_manager,
visualizations as visualization_manager,
)
from galaxy.managers.context import ProvidesUserContext
from galaxy.model import (
HistoryDatasetAssociation,
LibraryDatasetDatasetAssociation,
@@ -21,6 +23,9 @@ from galaxy.model import (
)
from galaxy.util import bunch
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -59,7 +64,9 @@ class ResourceParser:
hda=app[hda_manager.HDAManager],
)
def parse_parameter_dictionary(self, trans, param_config_dict, query_params, param_modifiers=None):
def parse_parameter_dictionary(
self, trans: "GalaxyWebTransaction", param_config_dict, query_params, param_modifiers=None
):
"""
Parse all expected params from the query dictionary `query_params`.
@@ -111,7 +118,7 @@ class ResourceParser:
return resources
def parse_config(self, trans, param_config_dict, query_params):
def parse_config(self, trans: ProvidesUserContext, param_config_dict, query_params):
"""
Return `query_params` dict parsing only JSON serializable params.
Complex params such as models, etc. are left as the original query value.
@@ -149,7 +156,7 @@ class ResourceParser:
# TODO: I would LOVE to rip modifiers out completely
def parse_parameter_modifiers(
self, trans, param_modifiers, query_params
self, trans: ProvidesUserContext, param_modifiers, query_params
) -> dict[str, dict[str, ParameterType | None]]:
"""
Parse and return parameters that are meant to modify other parameters,
@@ -177,7 +184,7 @@ class ResourceParser:
return parsed_modifiers
def parse_parameter_default(self, trans, param_config) -> ParameterType | None:
def parse_parameter_default(self, trans: ProvidesUserContext, param_config) -> ParameterType | None:
"""
Parse any default values for the given param, defaulting the default
to `None`.
@@ -192,7 +199,9 @@ class ResourceParser:
# (and adding this code to the xml parser)
return self.parse_parameter(trans, param_config, default)
def parse_parameter(self, trans, expected_param_data, query_param, recurse=True, param_modifiers=None):
def parse_parameter(
self, trans: ProvidesUserContext, expected_param_data, query_param, recurse=True, param_modifiers=None
):
"""
Use data in `expected_param_data` to parse `query_param` from a string into
a resource usable directly by a template.
+15 -5
View File
@@ -16,7 +16,10 @@ from http.cookies import (
SimpleCookie,
)
from importlib import import_module
from typing import NoReturn
from typing import (
NoReturn,
TYPE_CHECKING,
)
from urllib.parse import urljoin
import routes
@@ -31,6 +34,9 @@ from paste.response import HeaderDict
from galaxy.util import smart_str
from galaxy.util.resources import resource_string
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
#: time of the most recent server startup
@@ -281,7 +287,7 @@ class WebApplication:
body_renderer = body_renderer or self._render_body
return body_renderer(trans, body, environ, start_response)
def _render_body(self, trans, body, environ, start_response):
def _render_body(self, trans: "GalaxyWebTransaction", body, environ, start_response):
# Now figure out what we got back and try to get it to the browser in
# a smart way
if callable(body):
@@ -299,7 +305,7 @@ class WebApplication:
start_response(trans.response.wsgi_status(), trans.response.wsgi_headeritems())
return self.make_body_iterable(trans, body)
def make_body_iterable(self, trans, body):
def make_body_iterable(self, trans: "DefaultWebTransaction", body):
if isinstance(body, (types.GeneratorType, list, tuple)):
# Recursively stream the iterable
return flatten(body)
@@ -310,7 +316,7 @@ class WebApplication:
# Worst case scenario
return [smart_str(body)]
def handle_controller_exception(self, e, trans, method, kwargs):
def handle_controller_exception(self, e, trans: "GalaxyWebTransaction", method, kwargs):
"""
Allow handling of exceptions raised in controller methods.
"""
@@ -366,6 +372,10 @@ class DefaultWebTransaction:
self.environ = environ
self.request = Request(environ)
self.response = Response()
# Set by WebApplication.handle_request() once the route is resolved.
self.request_id: str | None = None
self.controller: str | None = None
self.action: str | None = None
@lazy_property
def session(self):
@@ -547,7 +557,7 @@ class Response:
CHUNK_SIZE = 2**16
def send_file(start_response, trans, body):
def send_file(start_response, trans: "GalaxyWebTransaction", body):
# If configured use X-Accel-Redirect header for nginx
base = trans.app.config.nginx_x_accel_redirect_base
apache_xsendfile = trans.app.config.apache_xsendfile
+15 -11
View File
@@ -3,6 +3,7 @@ from functools import wraps
from inspect import getfullargspec
from json import loads
from traceback import format_exc
from typing import TYPE_CHECKING
import paste.httpexceptions
from pydantic import (
@@ -26,6 +27,9 @@ from galaxy.util import (
from galaxy.util.json import safe_dumps
from galaxy.web.framework import url_for
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
JSON_CONTENT_TYPE = "application/json; charset=UTF-8"
@@ -59,7 +63,7 @@ def json(func, pretty=False):
"""
@wraps(func)
def call_and_format(self, trans, *args, **kwargs):
def call_and_format(self, trans: "GalaxyWebTransaction", *args, **kwargs):
# pull out any callback argument to the api endpoint and set the content type to json or javascript
jsonp_callback = kwargs.pop(JSONP_CALLBACK_KEY, None)
if jsonp_callback:
@@ -84,7 +88,7 @@ def json_pretty(func):
def require_login(verb="perform this action", use_panels=False):
def argcatcher(func):
@wraps(func)
def decorator(self, trans, *args, **kwargs):
def decorator(self, trans: "GalaxyWebTransaction", *args, **kwargs):
if trans.get_user():
return func(self, trans, *args, **kwargs)
else:
@@ -105,7 +109,7 @@ def require_login(verb="perform this action", use_panels=False):
def require_admin(func):
@wraps(func)
def decorator(self, trans, *args, **kwargs):
def decorator(self, trans: "GalaxyWebTransaction", *args, **kwargs):
if not trans.user_is_admin:
msg = require_admin_message(trans.app.config, trans.get_user())
trans.response.status = 403
@@ -136,7 +140,7 @@ def do_not_cache(func):
"""
@wraps(func)
def set_nocache_headers(self, trans, *args, **kwargs):
def set_nocache_headers(self, trans: "GalaxyWebTransaction", *args, **kwargs):
trans.response.headers["Cache-Control"] = ["no-cache", "no-store", "must-revalidate"]
trans.response.headers["Pragma"] = "no-cache"
trans.response.headers["Expires"] = "0"
@@ -152,7 +156,7 @@ def legacy_expose_api(func, to_json=True, user_required=True):
"""
@wraps(func)
def decorator(self, trans, *args, **kwargs):
def decorator(self, trans: "GalaxyWebTransaction", *args, **kwargs):
def error(environ, start_response):
start_response(error_status, [("Content-type", "text/plain")])
return [smart_str(error_message)]
@@ -211,7 +215,7 @@ def legacy_expose_api(func, to_json=True, user_required=True):
return expose(_save_orig_fn(decorator, func))
def __extract_payload_from_request(trans, func, kwargs):
def __extract_payload_from_request(trans: "GalaxyWebTransaction", func, kwargs):
content_type = trans.request.headers.get("content-type", "")
if content_type.startswith("application/x-www-form-urlencoded") or content_type.startswith("multipart/form-data"):
# If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body
@@ -276,7 +280,7 @@ def expose_api(func, to_json=True, user_required=True, user_or_session_required=
"""
@wraps(func)
def decorator(self, trans, *args, **kwargs):
def decorator(self, trans: "GalaxyWebTransaction", *args, **kwargs):
# errors passed in from trans._authenticate_api
if trans.error_message:
return __api_error_response(
@@ -385,14 +389,14 @@ def format_return_as_json(rval, jsonp_callback=None, pretty=False):
return json
def __api_error_dict(trans, **kwds):
def __api_error_dict(trans: "GalaxyWebTransaction", **kwds):
error_dict = api_error_to_dict(debug=trans.debug, **kwds)
exception = kwds.get("exception", None)
# If we are given an status code directly - use it - otherwise check
# the exception for a status_code attribute.
if "status_code" in kwds:
status_code = int(kwds.get("status_code"))
elif hasattr(exception, "status_code"):
status_code = int(kwds["status_code"])
elif exception is not None and hasattr(exception, "status_code"):
status_code = int(exception.status_code)
else:
status_code = 500
@@ -405,7 +409,7 @@ def __api_error_dict(trans, **kwds):
return error_dict
def __api_error_response(trans, **kwds):
def __api_error_response(trans: "GalaxyWebTransaction", **kwds):
error_dict = __api_error_dict(trans, **kwds)
return safe_dumps(error_dict)
+13 -3
View File
@@ -1,4 +1,5 @@
import logging
from typing import TYPE_CHECKING
from markupsafe import escape
@@ -7,6 +8,9 @@ from galaxy.util import (
unicodify,
)
if TYPE_CHECKING:
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -28,7 +32,7 @@ class GridColumn:
self.format = format
self.escape = escape
def get_value(self, trans, grid, item):
def get_value(self, trans: "GalaxyWebTransaction", grid, item):
if self.method:
value = getattr(grid, self.method)(trans, item)
elif self.key and hasattr(item, self.key):
@@ -42,7 +46,7 @@ class GridColumn:
else:
return value
def sort(self, trans, query, ascending, column_name=None):
def sort(self, trans: "GalaxyWebTransaction", query, ascending, column_name=None):
"""Sort query using this column."""
if column_name is None:
column_name = self.key
@@ -64,6 +68,8 @@ class GridData:
model_class: type | None = None
columns: list[GridColumn] = []
default_limit: int = 1000
# Subclasses provide the default sort column key.
default_sort_key: str
def __init__(self):
# If a column does not have a model class, set the column's model class
@@ -72,7 +78,11 @@ class GridData:
if not column.model_class:
column.model_class = self.model_class
def __call__(self, trans, **kwargs):
def apply_query_filter(self, query, **kwargs):
# Subclasses override this to restrict the grid's base query.
raise NotImplementedError
def __call__(self, trans: "GalaxyWebTransaction", **kwargs):
limit = kwargs.get("limit", self.default_limit)
offset = kwargs.get("offset", 0)
+23 -21
View File
@@ -3,6 +3,7 @@ import logging
import os
import time
from collections import namedtuple
from typing import TYPE_CHECKING
from galaxy.util import (
requests,
@@ -17,6 +18,10 @@ from galaxy.util.lazy_process import (
)
from galaxy.web.framework import url_for
if TYPE_CHECKING:
from galaxy.config import GalaxyAppConfiguration
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -31,23 +36,20 @@ class ProxyManager:
"port",
)
def __init__(self, config):
for option in [
"manage_dynamic_proxy",
"dynamic_proxy_bind_port",
"dynamic_proxy_bind_ip",
"dynamic_proxy_debug",
"dynamic_proxy_external_proxy",
"dynamic_proxy_prefix",
"proxy_session_map",
"dynamic_proxy",
"cookie_path",
"dynamic_proxy_golang_noaccess",
"dynamic_proxy_golang_clean_interval",
"dynamic_proxy_golang_docker_address",
"dynamic_proxy_golang_api_key",
]:
setattr(self, option, getattr(config, option))
def __init__(self, config: "GalaxyAppConfiguration"):
self.manage_dynamic_proxy = config.manage_dynamic_proxy
self.dynamic_proxy_bind_port = config.dynamic_proxy_bind_port
self.dynamic_proxy_bind_ip = config.dynamic_proxy_bind_ip
self.dynamic_proxy_debug = config.dynamic_proxy_debug
self.dynamic_proxy_external_proxy = config.dynamic_proxy_external_proxy
self.dynamic_proxy_prefix = config.dynamic_proxy_prefix
self.proxy_session_map = config.proxy_session_map
self.dynamic_proxy = config.dynamic_proxy
self.cookie_path = config.cookie_path
self.dynamic_proxy_golang_noaccess = config.dynamic_proxy_golang_noaccess
self.dynamic_proxy_golang_clean_interval = config.dynamic_proxy_golang_clean_interval
self.dynamic_proxy_golang_docker_address = config.dynamic_proxy_golang_docker_address
self.dynamic_proxy_golang_api_key = config.dynamic_proxy_golang_api_key
if self.manage_dynamic_proxy:
self.lazy_process = self.__setup_lazy_process(config)
@@ -64,7 +66,7 @@ class ProxyManager:
def setup_proxy(
self,
trans,
trans: "GalaxyWebTransaction",
host=DEFAULT_PROXY_TO_HOST,
port=None,
proxy_prefix="",
@@ -105,14 +107,14 @@ class ProxyManager:
"proxied_host": proxy_requests.host,
}
def update_proxy(self, trans, **kwargs):
def update_proxy(self, trans: "GalaxyWebTransaction", **kwargs):
authentication = AuthenticationToken(trans)
for k in kwargs.keys():
if k not in self.valid_update_keys:
raise Exception(f"Invalid proxy request update key: {k}")
return self.proxy_ipc.update_requests(authentication, **kwargs)
def query_proxy(self, trans):
def query_proxy(self, trans: "GalaxyWebTransaction"):
authentication = AuthenticationToken(trans)
return self.proxy_ipc.fetch_requests(authentication)
@@ -182,7 +184,7 @@ class GolangProxyLauncher:
class AuthenticationToken:
def __init__(self, trans):
def __init__(self, trans: "GalaxyWebTransaction"):
self.cookie_name = SECURE_COOKIE
self.cookie_value = trans.get_cookie(self.cookie_name)
+108 -57
View File
@@ -6,6 +6,7 @@ import logging
from collections.abc import Callable
from typing import (
Any,
Protocol,
TYPE_CHECKING,
)
@@ -27,6 +28,10 @@ from galaxy.managers import (
users,
workflows,
)
from galaxy.managers.context import (
ProvidesHistoryContext,
ProvidesUserContext,
)
from galaxy.managers.forms import (
get_filtered_form_definitions_current,
get_form_definitions,
@@ -62,6 +67,7 @@ from galaxy.workflow.modules import WorkflowModuleInjector
if TYPE_CHECKING:
from galaxy.structured_app import StructuredApp
from galaxy.webapps.base.webapp import GalaxyWebTransaction
log = logging.getLogger(__name__)
@@ -88,7 +94,9 @@ class BaseController:
"""Returns the class object that a string denotes. Without this method, we'd have to do eval(<class_name>)."""
return managers_base.get_class(class_name)
def get_object(self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None):
def get_object(
self, trans: ProvidesUserContext, id, class_name, check_ownership=False, check_accessible=False, deleted=None
):
"""
Convenience method to get a model object with the specified checks.
"""
@@ -103,20 +111,20 @@ class BaseController:
# # meant to be overridden in SharableSecurityMixin
# return item
def get_user(self, trans, id, check_ownership=False, check_accessible=False, deleted=None):
def get_user(self, trans: ProvidesUserContext, id, check_ownership=False, check_accessible=False, deleted=None):
return self.get_object(trans, id, "User", check_ownership=False, check_accessible=False, deleted=deleted)
def get_group(self, trans, id, check_ownership=False, check_accessible=False, deleted=None):
def get_group(self, trans: ProvidesUserContext, id, check_ownership=False, check_accessible=False, deleted=None):
return self.get_object(trans, id, "Group", check_ownership=False, check_accessible=False, deleted=deleted)
def get_role(self, trans, id, check_ownership=False, check_accessible=False, deleted=None):
def get_role(self, trans: ProvidesUserContext, id, check_ownership=False, check_accessible=False, deleted=None):
return self.get_object(trans, id, "Role", check_ownership=False, check_accessible=False, deleted=deleted)
# ---- parsing query params
def decode_id(self, id):
return managers_base.decode_id(self.app, id)
def encode_all_ids(self, trans, rval, recursive=False):
def encode_all_ids(self, trans: ProvidesUserContext, rval, recursive=False):
"""
Encodes all integer values in the dict rval whose keys are 'id' or end with '_id'
@@ -176,7 +184,9 @@ Root = BaseController
class BaseUIController(BaseController):
def get_object(self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None):
def get_object(
self, trans: ProvidesUserContext, id, class_name, check_ownership=False, check_accessible=False, deleted=None
):
try:
return BaseController.get_object(
self,
@@ -193,13 +203,15 @@ class BaseUIController(BaseController):
log.exception("Exception in get_object check for %s %s:", class_name, str(id))
raise Exception(f"Server error retrieving {class_name} id ( {str(id)} ).")
def message_exception(self, trans, message, sanitize=True):
def message_exception(self, trans: "GalaxyWebTransaction", message, sanitize=True):
trans.response.status = 400
return {"err_msg": util.sanitize_text(message) if sanitize else message}
class BaseAPIController(BaseController):
def get_object(self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None):
def get_object(
self, trans: ProvidesUserContext, id, class_name, check_ownership=False, check_accessible=False, deleted=None
):
try:
return BaseController.get_object(
self,
@@ -217,7 +229,7 @@ class BaseAPIController(BaseController):
log.exception("Exception in get_object check for %s %s.", class_name, str(id))
raise HTTPInternalServerError(comment=util.unicodify(e))
def not_implemented(self, trans, **kwd):
def not_implemented(self, trans: ProvidesUserContext, **kwd):
raise HTTPNotImplemented()
def _parse_serialization_params(self, kwd, default_view):
@@ -253,7 +265,7 @@ class Datatype:
class SharableItemSecurityMixin:
"""Mixin for handling security for sharable items."""
def security_check(self, trans, item, check_ownership=False, check_accessible=False):
def security_check(self, trans: ProvidesUserContext, item, check_ownership=False, check_accessible=False):
"""Security checks for an item: checks if (a) user owns item or (b) item is accessible to user."""
return managers_base.security_check(
trans, item, check_ownership=check_ownership, check_accessible=check_accessible
@@ -263,10 +275,12 @@ class SharableItemSecurityMixin:
class UsesLibraryMixinItems(SharableItemSecurityMixin):
get_object: Callable
def get_library_folder(self, trans, id: int, check_ownership=False, check_accessible=True):
def get_library_folder(self, trans: ProvidesUserContext, id: int, check_ownership=False, check_accessible=True):
return self.get_object(trans, id, "LibraryFolder", check_ownership=False, check_accessible=check_accessible)
def get_library_dataset_dataset_association(self, trans, id, check_ownership=False, check_accessible=True):
def get_library_dataset_dataset_association(
self, trans: ProvidesUserContext, id, check_ownership=False, check_accessible=True
):
# Deprecated in lieu to galaxy.managers.lddas.LDDAManager.get() but not
# reusing that exactly because of subtle differences in exception handling
# logic (API controller override get_object to be slightly different).
@@ -274,7 +288,7 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
trans, id, "LibraryDatasetDatasetAssociation", check_ownership=False, check_accessible=check_accessible
)
def get_library_dataset(self, trans, id, check_ownership=False, check_accessible=True):
def get_library_dataset(self, trans: ProvidesUserContext, id, check_ownership=False, check_accessible=True):
return self.get_object(trans, id, "LibraryDataset", check_ownership=False, check_accessible=check_accessible)
# TODO: it makes no sense that I can get roles from a user but not user.is_admin()
@@ -283,14 +297,14 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
# return ( ( user.is_admin() )
# or ( trans.app.security_agent.can_add_library_item( user.all_roles(), item ) ) )
def can_current_user_add_to_library_item(self, trans, item):
def can_current_user_add_to_library_item(self, trans: ProvidesUserContext, item):
if not trans.user:
return False
return trans.user_is_admin or trans.app.security_agent.can_add_library_item(
trans.get_current_user_roles(), item
)
def check_user_can_add_to_library_item(self, trans, item, check_accessible=True):
def check_user_can_add_to_library_item(self, trans: ProvidesUserContext, item, check_accessible=True):
"""
Raise exception if user cannot add to the specified library item (i.e.
Folder). Can set check_accessible to False if folder was loaded with
@@ -311,7 +325,9 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
# Slight misuse of ItemOwnershipException?
raise exceptions.ItemOwnershipException("User cannot add to library item.")
def _copy_hdca_to_library_folder(self, trans, hda_manager, from_hdca_id: int, folder_id: int, ldda_message=""):
def _copy_hdca_to_library_folder(
self, trans: ProvidesHistoryContext, hda_manager, from_hdca_id: int, folder_id: int, ldda_message=""
):
"""
Fetches the collection identified by `from_hcda_id` and dispatches individual collection elements to
_copy_hda_to_library_folder
@@ -337,7 +353,13 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
]
def _copy_hda_to_library_folder(
self, trans, hda_manager, from_hda_id: int, folder_id: int, ldda_message="", element_identifier=None
self,
trans: ProvidesHistoryContext,
hda_manager,
from_hda_id: int,
folder_id: int,
ldda_message="",
element_identifier=None,
):
"""
Copies hda ``from_hda_id`` to library folder ``folder_id``, optionally
@@ -383,7 +405,7 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
return rval
def copy_hda_to_library_folder(
self, trans, hda, library_folder, roles=None, ldda_message="", element_identifier=None
self, trans: ProvidesUserContext, hda, library_folder, roles=None, ldda_message="", element_identifier=None
):
# PRECONDITION: permissions for this action on hda and library_folder have been checked
roles = roles or []
@@ -407,7 +429,7 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
# then finally, re-applies hda -> ldda for missing actions in _apply_hda_permissions_to_ldda??
return ldda
def _apply_library_folder_permissions_to_ldda(self, trans, library_folder, ldda):
def _apply_library_folder_permissions_to_ldda(self, trans: ProvidesUserContext, library_folder, ldda):
"""
Copy actions/roles from library folder to an ldda (and its library_dataset).
"""
@@ -417,7 +439,7 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
security_agent.copy_library_permissions(trans, library_folder, ldda.library_dataset)
return security_agent.get_permissions(ldda)
def _apply_hda_permissions_to_ldda(self, trans, hda, ldda):
def _apply_hda_permissions_to_ldda(self, trans: ProvidesUserContext, hda, ldda):
"""
Copy actions/roles from hda to ldda.library_dataset (and then ldda) if ldda
doesn't already have roles for the given action.
@@ -432,8 +454,11 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
# except that: if DATASET_MANAGE_PERMISSIONS exists in the hda.dataset permissions,
# we need to instead apply those roles to the LIBRARY_MANAGE permission to the library dataset
dataset_manage_permissions_action = security_agent.get_action("DATASET_MANAGE_PERMISSIONS").action
library_manage_permissions_action = security_agent.get_action("LIBRARY_MANAGE").action
dataset_manage_permissions = security_agent.get_action("DATASET_MANAGE_PERMISSIONS")
library_manage_permissions = security_agent.get_action("LIBRARY_MANAGE")
assert dataset_manage_permissions is not None and library_manage_permissions is not None
dataset_manage_permissions_action = dataset_manage_permissions.action
library_manage_permissions_action = library_manage_permissions.action
# TODO: test this and remove if in loop below
# TODO: doesn't handle action.action
# if dataset_manage_permissions_action in dataset_permissions_dict:
@@ -454,11 +479,9 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin):
# NOTE: only apply an hda perm if it's NOT set in the library_dataset perms (don't overwrite)
if action not in library_dataset_actions:
for role in dataset_permissions_roles:
ldps = LibraryDatasetPermissions(action, library_dataset, role)
ldps = [ldps] if not isinstance(ldps, list) else ldps
for ldp in ldps:
trans.sa_session.add(ldp)
flush_needed = True
ldp = LibraryDatasetPermissions(action, library_dataset, role)
trans.sa_session.add(ldp)
flush_needed = True
if flush_needed:
trans.sa_session.commit()
@@ -473,22 +496,25 @@ class UsesVisualizationMixin(UsesLibraryMixinItems):
Mixin for controllers that use Visualization objects.
"""
app: "StructuredApp"
slug_builder = SlugBuilder()
def get_visualization_config(self, trans, visualization):
def get_visualization_config(self, trans: ProvidesUserContext, visualization):
"""Returns a visualization's configuration."""
latest_revision = visualization.latest_revision
config = latest_revision.config
return config
def get_hda_or_ldda(self, trans, hda_ldda, dataset_id):
def get_hda_or_ldda(self, trans: "GalaxyWebTransaction", hda_ldda, dataset_id):
"""Returns either HDA or LDDA for hda/ldda and id combination."""
if hda_ldda == "hda":
return self.get_hda(trans, dataset_id, check_ownership=False, check_accessible=True)
else:
return self.get_library_dataset_dataset_association(trans, dataset_id)
def get_hda(self, trans, dataset_id, check_ownership=True, check_accessible=False, check_state=True):
def get_hda(
self, trans: "GalaxyWebTransaction", dataset_id, check_ownership=True, check_accessible=False, check_state=True
):
"""
Get an HDA object by id performing security checks using
the current transaction.
@@ -528,7 +554,7 @@ class UsesVisualizationMixin(UsesLibraryMixinItems):
)
return data
def _get_genome_data(self, trans, dataset, dbkey=None):
def _get_genome_data(self, trans: ProvidesHistoryContext, dataset, dbkey=None):
"""
Returns genome-wide data for dataset if available; if not, message is returned.
"""
@@ -551,7 +577,7 @@ class UsesVisualizationMixin(UsesLibraryMixinItems):
if isinstance(dataset.datatype, ChromatinInteractions):
source = "data"
data_provider = trans.app.data_provider_registry.get_data_provider(
data_provider = self.app.data_provider_registry.get_data_provider(
trans, original_dataset=dataset, source=source
)
# HACK: pass in additional params which are used for only some
@@ -589,7 +615,7 @@ class UsesStoredWorkflowMixin(SharableItemSecurityMixin, UsesAnnotations):
app: "StructuredApp"
slug_builder = SlugBuilder()
def get_stored_workflow(self, trans, id, check_ownership=True, check_accessible=False):
def get_stored_workflow(self, trans: ProvidesUserContext, id, check_ownership=True, check_accessible=False):
"""Get a StoredWorkflow from the database by id, verifying ownership."""
# Load workflow from database
workflow_contents_manager = workflows.WorkflowsManager(self.app)
@@ -607,7 +633,7 @@ class UsesStoredWorkflowMixin(SharableItemSecurityMixin, UsesAnnotations):
return workflow
def get_stored_workflow_steps(self, trans, stored_workflow: StoredWorkflow):
def get_stored_workflow_steps(self, trans: ProvidesHistoryContext, stored_workflow: StoredWorkflow):
"""Restores states for a stored workflow's steps."""
module_injector = WorkflowModuleInjector(trans)
workflow = stored_workflow.latest_workflow
@@ -618,7 +644,7 @@ class UsesStoredWorkflowMixin(SharableItemSecurityMixin, UsesAnnotations):
except exceptions.ToolMissingException:
pass
def _import_shared_workflow(self, trans, stored: StoredWorkflow):
def _import_shared_workflow(self, trans: ProvidesUserContext, stored: StoredWorkflow):
"""Imports a shared workflow"""
# Copy workflow.
imported_stored = StoredWorkflow()
@@ -642,7 +668,7 @@ class UsesStoredWorkflowMixin(SharableItemSecurityMixin, UsesAnnotations):
session.commit()
return imported_stored
def _workflow_to_dict(self, trans, stored: StoredWorkflow) -> dict[str, Any]:
def _workflow_to_dict(self, trans: ProvidesHistoryContext, stored: StoredWorkflow) -> dict[str, Any]:
"""
Converts a workflow to a dict of attributes suitable for exporting.
"""
@@ -656,7 +682,7 @@ class UsesStoredWorkflowMixin(SharableItemSecurityMixin, UsesAnnotations):
class UsesFormDefinitionsMixin:
"""Mixin for controllers that use Galaxy form objects."""
def get_all_forms(self, trans, all_versions=False, filter=None, form_type="All"):
def get_all_forms(self, trans: ProvidesUserContext, all_versions=False, filter=None, form_type="All"):
"""
Return all the latest forms from the form_definition_current table
if all_versions is set to True. Otherwise return all the versions
@@ -673,7 +699,7 @@ class UsesFormDefinitionsMixin:
else:
return [fdc.latest_form for fdc in fdc_list if fdc.latest_form.type == form_type]
def save_widget_field(self, trans, field_obj, widget_name, **kwd):
def save_widget_field(self, trans: ProvidesUserContext, field_obj, widget_name, **kwd):
# Save a form_builder field object
params = util.Params(kwd)
if isinstance(field_obj, trans.model.UserAddress):
@@ -689,7 +715,7 @@ class UsesFormDefinitionsMixin:
trans.sa_session.add(field_obj)
trans.sa_session.commit()
def get_form_values(self, trans, user, form_definition, **kwd):
def get_form_values(self, trans: ProvidesUserContext, user, form_definition, **kwd):
"""
Returns the name:value dictionary containing all the form values
"""
@@ -699,6 +725,7 @@ class UsesFormDefinitionsMixin:
field_type = field["type"]
field_name = field["name"]
input_value = params.get(field_name, "")
field_value: int | str | bool
if field_type == AddressField.__name__:
input_text_value = util.restore_text(input_value)
if input_text_value == "new":
@@ -736,7 +763,7 @@ class SharableMixin:
@web.expose
@web.require_login("modify Galaxy items")
def set_slug_async(self, trans, id, new_slug):
def set_slug_async(self, trans: "GalaxyWebTransaction", id, new_slug):
item = self.get_item(trans, id)
if item:
# Only update slug if slug is not already in use.
@@ -757,38 +784,60 @@ class SharableMixin:
@web.expose
@web.require_login("share Galaxy items")
def share(self, trans, id=None, email="", **kwd):
def share(self, trans: "GalaxyWebTransaction", id=None, email="", **kwd):
"""Handle sharing an item with a particular user."""
raise NotImplementedError()
@web.expose
def display_by_username_and_slug(self, trans, username, slug, **kwargs):
def display_by_username_and_slug(self, trans: "GalaxyWebTransaction", username, slug, **kwargs):
"""Display item by username and slug."""
# Ensure slug is in the correct format.
slug = slug.encode("latin1").decode("utf-8")
self._display_by_username_and_slug(trans, username, slug, **kwargs)
def _display_by_username_and_slug(self, trans, username, slug, **kwargs):
def _display_by_username_and_slug(self, trans: "GalaxyWebTransaction", username, slug, **kwargs):
raise NotImplementedError()
def get_item(self, trans, id):
def get_item(self, trans: "GalaxyWebTransaction", id):
"""Return item based on id."""
raise NotImplementedError()
class LooksUpTaggedItems(Protocol):
"""What the tag helpers need from the object they run on.
get_object comes from the BaseController subclass UsesTagsMixin is combined
with, _get_tagged_item from the mixin itself.
"""
def get_object(
self,
trans: ProvidesUserContext,
id,
class_name,
check_ownership=False,
check_accessible=False,
deleted=None,
): ...
def _get_tagged_item(self, trans: ProvidesUserContext, item_class_name, id, check_ownership=True): ...
class UsesTagsMixin(SharableItemSecurityMixin):
def _get_user_tags(self, trans, item_class_name, id):
def _get_user_tags(self: LooksUpTaggedItems, trans: ProvidesUserContext, item_class_name, id):
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
return [tag for tag in tagged_item.tags if tag.user == user]
def _get_tagged_item(self, trans, item_class_name, id, check_ownership=True):
def _get_tagged_item(
self: LooksUpTaggedItems, trans: ProvidesUserContext, item_class_name, id, check_ownership=True
):
tagged_item = self.get_object(
trans, id, item_class_name, check_ownership=check_ownership, check_accessible=True
)
return tagged_item
def _remove_items_tag(self, trans, item_class_name, id, tag_name):
def _remove_items_tag(self: LooksUpTaggedItems, trans: ProvidesUserContext, item_class_name, id, tag_name):
"""Remove a tag from an item."""
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
@@ -796,27 +845,29 @@ class UsesTagsMixin(SharableItemSecurityMixin):
trans.sa_session.commit()
return deleted
def _apply_item_tag(self, trans, item_class_name, id, tag_name, tag_value=None):
def _apply_item_tag(
self: LooksUpTaggedItems, trans: ProvidesUserContext, item_class_name, id, tag_name, tag_value=None
):
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
tag_assoc = trans.tag_handler.apply_item_tag(user, tagged_item, tag_name, tag_value)
trans.sa_session.commit()
return tag_assoc
def _get_item_tag_assoc(self, trans, item_class_name, id, tag_name):
def _get_item_tag_assoc(self: LooksUpTaggedItems, trans: ProvidesUserContext, item_class_name, id, tag_name):
user = trans.user
tagged_item = self._get_tagged_item(trans, item_class_name, id)
log.debug(f"In get_item_tag_assoc with tagged_item {tagged_item}")
return trans.tag_handler._get_item_tag_assoc(user, tagged_item, tag_name)
def set_tags_from_list(self, trans, item, new_tags_list, user=None):
def set_tags_from_list(self, trans: ProvidesUserContext, item, new_tags_list, user=None):
return trans.tag_handler.set_tags_from_list(user, item, new_tags_list)
class UsesExtendedMetadataMixin(SharableItemSecurityMixin):
"""Mixin for getting and setting item extended metadata."""
def get_item_extended_metadata_obj(self, trans, item):
def get_item_extended_metadata_obj(self, trans: ProvidesUserContext, item):
"""
Given an item object (such as a LibraryDatasetDatasetAssociation), find the object
of the associated extended metadata
@@ -825,10 +876,10 @@ class UsesExtendedMetadataMixin(SharableItemSecurityMixin):
return item.extended_metadata
return None
def set_item_extended_metadata_obj(self, trans, item, extmeta_obj, check_writable=False):
def set_item_extended_metadata_obj(self, trans: ProvidesUserContext, item, extmeta_obj, check_writable=False):
if item.__class__ == LibraryDatasetDatasetAssociation:
if not check_writable or trans.app.security_agent.can_modify_library_item(
trans.get_current_user_roles(), item, trans.user
trans.get_current_user_roles(), item
):
item.extended_metadata = extmeta_obj
trans.sa_session.commit()
@@ -842,10 +893,10 @@ class UsesExtendedMetadataMixin(SharableItemSecurityMixin):
item.extended_metadata = extmeta_obj
trans.sa_session.commit()
def unset_item_extended_metadata_obj(self, trans, item, check_writable=False):
def unset_item_extended_metadata_obj(self, trans: ProvidesUserContext, item, check_writable=False):
if item.__class__ == LibraryDatasetDatasetAssociation:
if not check_writable or trans.app.security_agent.can_modify_library_item(
trans.get_current_user_roles(), item, trans.user
trans.get_current_user_roles(), item
):
item.extended_metadata = None
trans.sa_session.commit()
@@ -859,7 +910,7 @@ class UsesExtendedMetadataMixin(SharableItemSecurityMixin):
item.extended_metadata = None
trans.sa_session.commit()
def create_extended_metadata(self, trans, extmeta):
def create_extended_metadata(self, trans: ProvidesUserContext, extmeta):
"""
Create/index an extended metadata object. The returned object is
not associated with any items
@@ -873,7 +924,7 @@ class UsesExtendedMetadataMixin(SharableItemSecurityMixin):
trans.sa_session.commit()
return ex_meta
def delete_extended_metadata(self, trans, item):
def delete_extended_metadata(self, trans: ProvidesUserContext, item):
if item.__class__ == ExtendedMetadata:
trans.sa_session.delete(item)
trans.sa_session.commit()
+3 -3
View File
@@ -199,7 +199,7 @@ class WebApplication(base.WebApplication):
directories=paths, module_directory=galaxy_app.config.template_cache_path, collection_size=500
)
def handle_controller_exception(self, e, trans, method, kwargs):
def handle_controller_exception(self, e, trans: "GalaxyWebTransaction", method, kwargs):
if not isinstance(e, HTTPException):
# We're still logging too much here but at least it's not logging webob.exc.HTTPFound and friends
log.debug(f"Encountered exception in controller method: {method}", exc_info=True)
@@ -221,7 +221,7 @@ class WebApplication(base.WebApplication):
trans.response.status = e.status_code
return trans.show_message(sanitize_html(e.err_msg), e.type)
def make_body_iterable(self, trans, body):
def make_body_iterable(self, trans: "base.DefaultWebTransaction", body):
return base.WebApplication.make_body_iterable(self, trans, body)
def transaction_chooser(self, environ, galaxy_app: BasicSharedApp, session_cookie: str):
@@ -1128,7 +1128,7 @@ class GalaxyWebTransaction(base.DefaultWebTransaction, context.ProvidesHistoryCo
return url_for(path, qualified=True)
def create_new_session(trans, prev_galaxy_session=None, user_for_new_session=None):
def create_new_session(trans: "GalaxyWebTransaction", prev_galaxy_session=None, user_for_new_session=None):
"""
Create a new GalaxySession for this request, possibly with a connection
to a previous session (in `prev_galaxy_session`) and an existing user
+5 -4
View File
@@ -30,6 +30,7 @@ from galaxy.webapps.galaxy.api import (
DependsOnUser,
Router,
)
from galaxy.work.context import SessionRequestContext
log = logging.getLogger(__name__)
@@ -90,7 +91,7 @@ class AgentAPI:
async def query_agent(
self,
request: AgentQueryRequest,
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
user: User = DependsOnUser,
) -> AgentQueryResponse:
"""Query an AI agent. Use agent_type='auto' for automatic routing.
@@ -129,7 +130,7 @@ class AgentAPI:
job_id: DecodedDatabaseIdField | None = Body(None, description="Job ID for context"),
error_details: dict[str, Any] | None = Body(None, description="Additional error details"),
save_exchange: bool | None = Body(None, description="Save exchange for feedback tracking. Defaults to false."),
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
user: User = DependsOnUser,
) -> AgentResponse:
"""Analyze job errors and provide debugging assistance.
@@ -181,7 +182,7 @@ class AgentAPI:
query: str = Body(..., description="Description of the tool to create"),
context: dict[str, Any] | None = Body(None, description="Additional context for tool creation"),
save_exchange: bool | None = Body(None, description="Save exchange for feedback tracking. Defaults to false."),
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
user: User = DependsOnUser,
) -> AgentResponse:
"""Create a custom Galaxy tool.
@@ -216,7 +217,7 @@ class AgentAPI:
async def history_summary(
self,
history_id: str = Body(..., embed=True, description="Encoded id of the history to summarize."),
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
user: User = DependsOnUser,
) -> AgentResponse:
"""Produce a comprehensive markdown report for a history's analysis.
+8 -5
View File
@@ -27,7 +27,9 @@ from galaxy.exceptions import (
)
from galaxy.managers.agents import AgentService
from galaxy.managers.chat import ChatManager
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.context import (
ProvidesUserContext,
)
from galaxy.managers.jobs import JobManager
from galaxy.managers.markdown_util import ready_galaxy_markdown_for_export
from galaxy.managers.workflows import WorkflowsManager
@@ -50,6 +52,7 @@ from galaxy.webapps.galaxy.api import (
DependsOnUser,
Router,
)
from galaxy.work.context import SessionRequestContext
# Import agent system
try:
@@ -129,7 +132,7 @@ class ChatAPI:
payload: ChatPayload | None = None,
query: str | None = Query(default=None, description="Query string for general chat"),
agent_type: str = Query(default="auto", description="Agent type to use for the query"),
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
user: User = DependsOnUser,
) -> ChatResponse:
"""GalaxyAI endpoint - handles both job-based and general chat queries
@@ -409,7 +412,7 @@ class ChatAPI:
workflow_id: str = Path(..., description="Workflow ID to generate the report for"),
version: int | None = Query(None, description="Version of the workflow"),
instance: bool = Query(False, description="Whether the workflow_id is an instance ID"),
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
user: User = DependsOnUser,
) -> WorkflowReportResponse:
"""Generate a report for the specified workflow."""
@@ -563,7 +566,7 @@ class ChatAPI:
self,
query: str,
agent_type: str,
trans: ProvidesUserContext,
trans: SessionRequestContext,
user: User,
job=None,
context: dict[str, Any] | None = None,
@@ -576,7 +579,7 @@ class ChatAPI:
self,
query: str,
agent_type: str,
trans: ProvidesUserContext,
trans: SessionRequestContext,
user: User,
job=None,
context: dict[str, Any] | None = None,
@@ -11,6 +11,7 @@ from galaxy.web import (
expose_api,
require_admin,
)
from galaxy.webapps.base.webapp import GalaxyWebTransaction
from . import BaseGalaxyAPIController
log = logging.getLogger(__name__)
@@ -23,7 +24,7 @@ class ContainerResolutionAPIController(BaseGalaxyAPIController):
@expose_api
@require_admin
def index(self, trans, **kwd):
def index(self, trans: GalaxyWebTransaction, **kwd):
"""
GET /api/container_resolvers
"""
@@ -31,7 +32,7 @@ class ContainerResolutionAPIController(BaseGalaxyAPIController):
@expose_api
@require_admin
def show(self, trans, index):
def show(self, trans: GalaxyWebTransaction, index):
"""
GET /api/container_resolvers/<id>
"""
@@ -39,7 +40,7 @@ class ContainerResolutionAPIController(BaseGalaxyAPIController):
@expose_api
@require_admin
def resolve(self, trans, index=None, **kwds):
def resolve(self, trans: GalaxyWebTransaction, index=None, **kwds):
"""
GET /api/container_resolvers/resolve
GET /api/container_resolvers/{index}/resolve
@@ -69,7 +70,7 @@ class ContainerResolutionAPIController(BaseGalaxyAPIController):
@expose_api
@require_admin
def resolve_toolbox(self, trans, **kwds):
def resolve_toolbox(self, trans: GalaxyWebTransaction, **kwds):
"""
GET /api/container_resolvers/toolbox
GET /api/container_resolvers/{index}/toolbox
@@ -89,7 +90,7 @@ class ContainerResolutionAPIController(BaseGalaxyAPIController):
@expose_api
@require_admin
def resolve_toolbox_with_install(self, trans, payload, **kwds):
def resolve_toolbox_with_install(self, trans: GalaxyWebTransaction, payload, **kwds):
"""
POST /api/container_resolvers/toolbox/install
POST /api/container_resolvers/{index}/toolbox/install
@@ -108,7 +109,7 @@ class ContainerResolutionAPIController(BaseGalaxyAPIController):
@expose_api
@require_admin
def resolve_with_install(self, trans, payload, **kwds):
def resolve_with_install(self, trans: GalaxyWebTransaction, payload, **kwds):
"""
POST /api/container_resolvers/resolve/install
POST /api/container_resolvers/{index}/resolve/install
@@ -18,6 +18,7 @@ from galaxy.webapps.base.controller import (
UsesLibraryMixinItems,
UsesStoredWorkflowMixin,
)
from galaxy.webapps.base.webapp import GalaxyWebTransaction
from . import (
BaseGalaxyAPIController,
depends,
@@ -33,10 +34,10 @@ class BaseExtendedMetadataController(
):
exmeta_item_id: str
def _get_item_from_id(self, trans, idstr, check_writable=True) -> T | None: ...
def _get_item_from_id(self, trans: GalaxyWebTransaction, idstr, check_writable=True) -> T | None: ...
@web.expose_api
def index(self, trans, **kwd):
def index(self, trans: GalaxyWebTransaction, **kwd):
idnum = kwd[self.exmeta_item_id]
item = self._get_item_from_id(trans, idnum, check_writable=False)
if item is not None:
@@ -45,7 +46,7 @@ class BaseExtendedMetadataController(
return ex_meta.data
@web.expose_api
def create(self, trans, payload, **kwd):
def create(self, trans: GalaxyWebTransaction, payload, **kwd):
idnum = kwd[self.exmeta_item_id]
item = self._get_item_from_id(trans, idnum, check_writable=True)
if item is not None:
@@ -61,7 +62,9 @@ class LibraryDatasetExtendMetadataController(BaseExtendedMetadataController[mode
controller_name = "library_dataset_extended_metadata"
exmeta_item_id = "library_content_id"
def _get_item_from_id(self, trans, idstr, check_writable=True) -> model.LibraryDatasetDatasetAssociation | None:
def _get_item_from_id(
self, trans: GalaxyWebTransaction, idstr, check_writable=True
) -> model.LibraryDatasetDatasetAssociation | None:
if check_writable:
item = self.get_library_dataset_dataset_association(trans, idstr)
if trans.app.security_agent.can_modify_library_item(trans.get_current_user_roles(), item):
@@ -78,7 +81,9 @@ class HistoryDatasetExtendMetadataController(BaseExtendedMetadataController[mode
exmeta_item_id = "history_content_id"
hda_manager: managers.hdas.HDAManager = depends(managers.hdas.HDAManager)
def _get_item_from_id(self, trans, idstr, check_writable=True) -> model.HistoryDatasetAssociation | None:
def _get_item_from_id(
self, trans: GalaxyWebTransaction, idstr, check_writable=True
) -> model.HistoryDatasetAssociation | None:
decoded_idstr = self.decode_id(idstr)
if check_writable:
return self.hda_manager.get_owned(decoded_idstr, trans.user, current_history=trans.history)
@@ -23,6 +23,7 @@ from galaxy.webapps.galaxy.api import (
)
from galaxy.webapps.galaxy.api.common import FolderIdPathParam
from galaxy.webapps.galaxy.services.library_folder_contents import LibraryFolderContentsService
from galaxy.work.context import SessionRequestContext
log = logging.getLogger(__name__)
@@ -117,7 +118,7 @@ class FastAPILibraryFoldersContents:
def create(
self,
folder_id: FolderIdPathParam,
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
payload: CreateLibraryFilePayload = Body(...),
):
return self.service.create(trans, folder_id, payload)
+4 -3
View File
@@ -16,6 +16,7 @@ from galaxy.model import FormDefinition
from galaxy.schema.fields import DecodedDatabaseIdField
from galaxy.util import XML
from galaxy.webapps.base.controller import url_for
from galaxy.webapps.base.webapp import GalaxyWebTransaction
from galaxy.webapps.galaxy.api import (
depends,
DependsOnTrans,
@@ -50,7 +51,7 @@ class FastAPIForms:
class FormDefinitionAPIController(BaseGalaxyAPIController):
@web.legacy_expose_api
def index(self, trans, **kwd):
def index(self, trans: GalaxyWebTransaction, **kwd):
"""
GET /api/forms
Displays a collection (list) of forms.
@@ -70,7 +71,7 @@ class FormDefinitionAPIController(BaseGalaxyAPIController):
return rval
@web.legacy_expose_api
def show(self, trans, id, **kwd):
def show(self, trans: GalaxyWebTransaction, id, **kwd):
"""
GET /api/forms/{encoded_form_id}
Displays information about a form.
@@ -96,7 +97,7 @@ class FormDefinitionAPIController(BaseGalaxyAPIController):
return item
@web.legacy_expose_api
def create(self, trans, payload, **kwd):
def create(self, trans: GalaxyWebTransaction, payload, **kwd):
"""
POST /api/forms
Creates a new form.
+2 -1
View File
@@ -10,6 +10,7 @@ from fastapi.responses import Response
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.genomes import GenomesManager
from galaxy.work.context import SessionRequestContext
from . import (
depends,
DependsOnTrans,
@@ -82,7 +83,7 @@ class FastAPIGenomes:
)
def show(
self,
trans: ProvidesUserContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
id: str = IdPathParam,
reference: bool = ReferenceQueryParam,
num: int = NumQueryParam,
+8 -6
View File
@@ -4,7 +4,6 @@ API operations on Group objects.
import logging
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.group_roles import GroupRolesManager
from galaxy.model.db.role import get_private_role_user_emails_dict
from galaxy.schema.fields import Security
@@ -21,13 +20,16 @@ from galaxy.webapps.galaxy.api.common import (
GroupIDPathParam,
RoleIDPathParam,
)
from galaxy.work.context import SessionRequestContext
log = logging.getLogger(__name__)
router = Router(tags=["group_roles"])
def group_role_to_model(trans, group_id: int, role, displayed_name: str | None = None) -> GroupRoleResponse:
def group_role_to_model(
trans: SessionRequestContext, group_id: int, role, displayed_name: str | None = None
) -> GroupRoleResponse:
encoded_group_id = Security.security.encode_id(group_id)
encoded_role_id = Security.security.encode_id(role.id)
url = trans.url_builder("group_role", group_id=encoded_group_id, role_id=encoded_role_id)
@@ -48,7 +50,7 @@ class FastAPIGroupRoles:
def index(
self,
group_id: GroupIDPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupRoleListResponse:
group_roles = self.manager.index(trans, group_id)
role_ids = {gr.role.id for gr in group_roles}
@@ -70,7 +72,7 @@ class FastAPIGroupRoles:
self,
group_id: GroupIDPathParam,
role_id: RoleIDPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupRoleResponse:
role = self.manager.show(trans, role_id, group_id)
return group_role_to_model(trans, group_id, role)
@@ -80,7 +82,7 @@ class FastAPIGroupRoles:
self,
group_id: GroupIDPathParam,
role_id: RoleIDPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupRoleResponse:
role = self.manager.update(trans, role_id, group_id)
return group_role_to_model(trans, group_id, role)
@@ -90,7 +92,7 @@ class FastAPIGroupRoles:
self,
group_id: GroupIDPathParam,
role_id: RoleIDPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupRoleResponse:
role = self.manager.delete(trans, role_id, group_id)
return group_role_to_model(trans, group_id, role)
+6 -6
View File
@@ -4,7 +4,6 @@ API operations on Group objects.
import logging
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.group_users import GroupUsersManager
from galaxy.schema.fields import Security
from galaxy.schema.schema import (
@@ -20,13 +19,14 @@ from galaxy.webapps.galaxy.api.common import (
GroupIDPathParam,
UserIdPathParam,
)
from galaxy.work.context import SessionRequestContext
log = logging.getLogger(__name__)
router = Router(tags=["group_users"])
def group_user_to_model(trans, group_id, user) -> GroupUserResponse:
def group_user_to_model(trans: SessionRequestContext, group_id, user) -> GroupUserResponse:
encoded_group_id = Security.security.encode_id(group_id)
encoded_user_id = Security.security.encode_id(user.id)
url = trans.url_builder("group_user", group_id=encoded_group_id, user_id=encoded_user_id)
@@ -46,7 +46,7 @@ class FastAPIGroupUsers:
def index(
self,
group_id: GroupIDPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupUserListResponse:
"""
GET /api/groups/{encoded_group_id}/users
@@ -66,7 +66,7 @@ class FastAPIGroupUsers:
self,
group_id: GroupIDPathParam,
user_id: UserIdPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupUserResponse:
"""
Displays information about a group user.
@@ -84,7 +84,7 @@ class FastAPIGroupUsers:
self,
group_id: GroupIDPathParam,
user_id: UserIdPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupUserResponse:
"""
PUT /api/groups/{encoded_group_id}/users/{encoded_user_id}
@@ -103,7 +103,7 @@ class FastAPIGroupUsers:
self,
group_id: GroupIDPathParam,
user_id: UserIdPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupUserResponse:
"""
DELETE /api/groups/{encoded_group_id}/users/{encoded_user_id}
+8 -8
View File
@@ -7,7 +7,6 @@ from typing import Annotated
from fastapi import Body
from galaxy.managers.context import ProvidesAppContext
from galaxy.managers.groups import GroupsManager
from galaxy.schema.groups import (
GroupCreatePayload,
@@ -21,6 +20,7 @@ from galaxy.webapps.galaxy.api import (
Router,
)
from galaxy.webapps.galaxy.api.common import GroupIDPathParam
from galaxy.work.context import SessionRequestContext
log = logging.getLogger(__name__)
@@ -39,7 +39,7 @@ class FastAPIGroups:
)
def index(
self,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupListResponse:
return self.manager.index(trans)
@@ -52,7 +52,7 @@ class FastAPIGroups:
def create(
self,
payload: Annotated[GroupCreatePayload, Body(...)],
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupListResponse:
return self.manager.create(trans, payload)
@@ -65,7 +65,7 @@ class FastAPIGroups:
def show(
self,
group_id: GroupIDPathParam,
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupResponse:
return self.manager.show(trans, group_id)
@@ -79,18 +79,18 @@ class FastAPIGroups:
self,
group_id: GroupIDPathParam,
payload: Annotated[GroupUpdatePayload, Body(...)],
trans: ProvidesAppContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> GroupResponse:
return self.manager.update(trans, group_id, payload)
@router.delete("/api/groups/{group_id}", require_admin=True)
def delete(self, group_id: GroupIDPathParam, trans: ProvidesAppContext = DependsOnTrans):
def delete(self, group_id: GroupIDPathParam, trans: SessionRequestContext = DependsOnTrans):
self.manager.delete(trans, group_id)
@router.post("/api/groups/{group_id}/purge", require_admin=True)
def purge(self, group_id: GroupIDPathParam, trans: ProvidesAppContext = DependsOnTrans):
def purge(self, group_id: GroupIDPathParam, trans: SessionRequestContext = DependsOnTrans):
self.manager.purge(trans, group_id)
@router.post("/api/groups/{group_id}/undelete", require_admin=True)
def undelete(self, group_id: GroupIDPathParam, trans: ProvidesAppContext = DependsOnTrans):
def undelete(self, group_id: GroupIDPathParam, trans: SessionRequestContext = DependsOnTrans):
self.manager.undelete(trans, group_id)
+5 -4
View File
@@ -97,6 +97,7 @@ from galaxy.webapps.galaxy.api.common import (
)
from galaxy.webapps.galaxy.services.histories import HistoriesService
from galaxy.webapps.galaxy.services.workflows import WorkflowsService
from galaxy.work.context import SessionRequestContext
from .common import HistoryIDPathParam
log = logging.getLogger(__name__)
@@ -215,7 +216,7 @@ class FastAPIHistories:
def index(
self,
response: Response,
trans: ProvidesHistoryContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
limit: int | None = LimitQueryParam,
offset: int | None = OffsetQueryParam,
show_own: bool = ShowOwnQueryParam,
@@ -263,7 +264,7 @@ class FastAPIHistories:
)
def count(
self,
trans: ProvidesHistoryContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
) -> int:
return self.service.count(trans)
@@ -274,7 +275,7 @@ class FastAPIHistories:
)
def index_deleted(
self,
trans: ProvidesHistoryContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
filter_query_params: FilterQueryParams = Depends(get_filter_query_params),
serialization_params: SerializationParams = Depends(query_serialization_params),
all: bool | None = AllHistoriesQueryParam,
@@ -476,7 +477,7 @@ class FastAPIHistories:
)
def create(
self,
trans: ProvidesHistoryContext = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
payload: CreateHistoryPayload = Depends(CreateHistoryFormData.as_form), # type: ignore[attr-defined]
payload_as_json: Any | None = Depends(try_get_request_body_as_json),
serialization_params: SerializationParams = Depends(query_serialization_params),
@@ -1222,6 +1222,6 @@ class FastAPIHistoryContents:
rval = self.service.materialize(trans, materialize_request)
return rval
def _download_collection(self, trans, id):
def _download_collection(self, trans: ProvidesHistoryContext, id):
archive = self.service.get_dataset_collection_archive_for_download(trans, id)
return GalaxyStreamingResponse(archive.response(), headers=archive.get_headers())
+1 -1
View File
@@ -153,7 +153,7 @@ class JobFilesAPIController(BaseGalaxyAPIController):
return {"message": "ok"}
@expose_api_anonymous_and_sessionless
def tus_patch(self, trans, **kwds):
def tus_patch(self, trans: ProvidesAppContext, **kwds):
"""
Exposed as PATCH /api/job_files/resumable_upload.
+2 -1
View File
@@ -5,6 +5,7 @@ related to running and queued jobs.
from galaxy.job_execution.ports import JobPortsView
from galaxy.structured_app import StructuredApp
from galaxy.web import expose_api_anonymous_and_sessionless
from galaxy.webapps.base.webapp import GalaxyWebTransaction
from . import BaseGalaxyAPIController
@@ -22,7 +23,7 @@ class JobPortsAPIController(BaseGalaxyAPIController):
self._job_ports_view = JobPortsView(app)
@expose_api_anonymous_and_sessionless
def create(self, trans, job_id, payload, **kwargs):
def create(self, trans: GalaxyWebTransaction, job_id, payload, **kwargs):
"""
create( self, trans, job_id, payload, **kwargs )
* POST /api/jobs/{job_id}/ports

Some files were not shown because too many files have changed in this diff Show More