From 94ce87cf13c6258ebe94b6bf5a2131a182bc36f8 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 22 Dec 2023 16:05:02 +0100 Subject: [PATCH] Fix Display Application link generation for metadata files and use same interface also for datasets, which should effectively give us test coverage? Fixes: ``` Stack Trace(most recent call first) AttributeError: 'Bunch' object has no attribute 'get_file_name' File "starlette/applications.py", line 122, in __call__ await self.middleware_stack(scope, receive, send) File "starlette/middleware/errors.py", line 184, in __call__ raise exc File "starlette/middleware/errors.py", line 162, in __call__ await self.app(scope, receive, _send) File "starlette_context/middleware/raw_middleware.py", line 93, in __call__ await self.app(scope, receive, send_wrapper) File "starlette/middleware/base.py", line 109, in __call__ await response(scope, receive, send) File "starlette/responses.py", line 270, in __call__ async with anyio.create_task_group() as task_group: File "anyio/_backends/_asyncio.py", line 597, in __aexit__ raise exceptions[0] File "starlette/responses.py", line 273, in wrap await func() File "starlette/middleware/base.py", line 134, in stream_response return await super().stream_response(send) File "starlette/responses.py", line 262, in stream_response async for chunk in self.body_iterator: File "starlette/middleware/base.py", line 98, in body_stream raise app_exc File "starlette/middleware/base.py", line 70, in coro await self.app(scope, receive_or_disconnect, send_no_error) File "starlette/middleware/exceptions.py", line 79, in __call__ raise exc File "starlette/middleware/exceptions.py", line 68, in __call__ await self.app(scope, receive, sender) File "fastapi/middleware/asyncexitstack.py", line 20, in __call__ raise e File "fastapi/middleware/asyncexitstack.py", line 17, in __call__ await self.app(scope, receive, send) File "starlette/routing.py", line 718, in __call__ await route.handle(scope, receive, send) File "starlette/routing.py", line 443, in handle await self.app(scope, receive, send) File "a2wsgi/wsgi.py", line 157, in __call__ return await responder(scope, receive, send) File "a2wsgi/wsgi.py", line 198, in __call__ raise self.exc_info[0].with_traceback( File "galaxy/web/framework/middleware/error.py", line 165, in __call__ app_iter = self.application(environ, sr_checker) File "galaxy/web/framework/middleware/statsd.py", line 29, in __call__ req = self.application(environ, start_response) File "/cvmfs/main.galaxyproject.org/venv/lib/python3.11/site-packages/paste/httpexceptions.py", line 640, in __call__ return self.application(environ, start_response) File "galaxy/web/framework/base.py", line 173, in __call__ return self.handle_request(request_id, path_info, environ, start_response) File "galaxy/web/framework/base.py", line 262, in handle_request body = method(trans, **kwargs) File "galaxy/web/framework/decorators.py", line 138, in set_nocache_headers return func(self, trans, *args, **kwargs) File "galaxy/webapps/galaxy/controllers/dataset.py", line 677, in display_application file_name = value.get_file_name() File "galaxy/datatypes/display_applications/parameters.py", line 256, in __getattr__ return getattr(self.value, key) ``` from https://sentry.galaxyproject.org/share/issue/c68d4f4d94f941c09e6eb5f62b163a69/ --- .../display_applications/parameters.py | 32 ++++++++++++------- .../webapps/galaxy/controllers/dataset.py | 2 +- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/datatypes/display_applications/parameters.py b/lib/galaxy/datatypes/display_applications/parameters.py index ede98fc73f6..c5cb95caa7f 100644 --- a/lib/galaxy/datatypes/display_applications/parameters.py +++ b/lib/galaxy/datatypes/display_applications/parameters.py @@ -1,11 +1,12 @@ # Contains parameters that are used in Display Applications import mimetypes +from dataclasses import dataclass from typing import Optional from urllib.parse import quote_plus from galaxy.model.base import transaction +from galaxy.schema.schema import DatasetState from galaxy.util import string_as_bool -from galaxy.util.bunch import Bunch from galaxy.util.template import fill_template DEFAULT_DATASET_NAME = "dataset" @@ -57,6 +58,13 @@ class DisplayApplicationParameter: return fill_template(self.url, context=other_values) +@dataclass +class DatasetLikeObject: + file_name: str + state: DatasetState + extension: str + + class DisplayApplicationDataParameter(DisplayApplicationParameter): """Parameter that returns a file_name containing the requested content""" @@ -95,27 +103,29 @@ class DisplayApplicationDataParameter(DisplayApplicationParameter): ) return None - def _get_dataset_like_object(self, other_values): + def _get_dataset_like_object(self, other_values) -> Optional[DatasetLikeObject]: # this returned object has file_name, state, and states attributes equivalent to a DatasetAssociation data = other_values.get(self.dataset, None) assert data, "Base dataset could not be found in values provided to DisplayApplicationDataParameter" if isinstance(data, DisplayDataValueWrapper): data = data.value - if data.state != data.states.OK: + if data.state != DatasetState.OK: return None if self.metadata: rval = getattr(data.metadata, self.metadata, None) assert rval, f'Unknown metadata name "{self.metadata}" provided for dataset type "{data.ext}".' - return Bunch(file_name=rval.get_file_name(), state=data.state, states=data.states, extension="data") - elif self.extensions and (self.force_conversion or not isinstance(data.datatype, self.formats)): + return DatasetLikeObject(file_name=rval.get_file_name(), state=data.state, extension="data") + elif ( + self.formats and self.extensions and (self.force_conversion or not isinstance(data.datatype, self.formats)) + ): for ext in self.extensions: rval = data.get_converted_files_by_type(ext) if rval: - return rval + return DatasetLikeObject(file_name=rval.get_file_name(), state=rval.state, extension=rval.extension) direct_match, target_ext, _ = data.find_conversion_destination(self.formats) assert direct_match or target_ext is not None, f"No conversion path found for data param: {self.name}" return None - return data + return DatasetLikeObject(file_name=data.get_file_name(), state=data.state, extension=data.extension) def get_value(self, other_values, dataset_hash, user_hash, trans): data = self._get_dataset_like_object(other_values) @@ -154,22 +164,22 @@ class DisplayApplicationDataParameter(DisplayApplicationParameter): trans.sa_session.add(assoc) with transaction(trans.sa_session): trans.sa_session.commit() - elif converted_dataset and converted_dataset.state == converted_dataset.states.ERROR: + elif converted_dataset and converted_dataset.state == DatasetState.ERROR: raise Exception(f"Dataset conversion failed for data parameter: {self.name}") return self.get_value(other_values, dataset_hash, user_hash, trans) def is_preparing(self, other_values): value = self._get_dataset_like_object(other_values) - if value and value.state in (value.states.NEW, value.states.UPLOAD, value.states.QUEUED, value.states.RUNNING): + if value and value.state in (DatasetState.NEW, DatasetState.UPLOAD, DatasetState.QUEUED, DatasetState.RUNNING): return True return False def ready(self, other_values): value = self._get_dataset_like_object(other_values) if value: - if value.state == value.states.OK: + if value.state == DatasetState.OK: return True - elif value.state == value.states.ERROR: + elif value.state == DatasetState.ERROR: raise Exception(f"A data display parameter is in the error state: {self.name}") return False diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index df9f1f2f9e9..6396db3d62b 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -674,7 +674,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE ), f"Extra file content requested ({action_param_extra}), but allow_extra_files_access is False." file_name = os.path.join(value.extra_files_path, action_param_extra) else: - file_name = value.get_file_name() + file_name = value.file_name content_length = os.path.getsize(file_name) rval = open(file_name, "rb") except OSError as e: