diff --git a/lib/galaxy/datatypes/text.py b/lib/galaxy/datatypes/text.py
index 872ce2b0d4ab..8a87cf1166f2 100644
--- a/lib/galaxy/datatypes/text.py
+++ b/lib/galaxy/datatypes/text.py
@@ -137,6 +137,7 @@ class DataManagerJson(Json):
MetadataElement(
name="data_tables", default=None, desc="Data tables represented by this dataset", readonly=True, visible=True
)
+ MetadataElement(name="is_bundle", default=False, desc="Dataset represents bundle", readonly=True, visible=True)
def set_meta(self, dataset: DatasetProtocol, overwrite: bool = True, **kwd):
super().set_meta(dataset=dataset, overwrite=overwrite, **kwd)
diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py
index 60680798057b..85b2cd981c2c 100644
--- a/lib/galaxy/managers/workflows.py
+++ b/lib/galaxy/managers/workflows.py
@@ -31,8 +31,6 @@
)
from sqlalchemy import (
and_,
- Cast,
- ColumnElement,
desc,
false,
func,
@@ -40,7 +38,6 @@
select,
true,
)
-from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import (
aliased,
joinedload,
@@ -75,6 +72,7 @@
StoredWorkflow,
StoredWorkflowTagAssociation,
StoredWorkflowUserShareAssociation,
+ to_json,
User,
Workflow,
WorkflowInvocation,
@@ -2070,26 +2068,14 @@ def get_workflow_by_trs_id_and_version(
) -> Optional[model.StoredWorkflow]:
sa_session = self.app.model.session
- def to_json(column, keys: List[str]):
- assert sa_session.bind
- if sa_session.bind.dialect.name == "postgresql":
- cast: Union[ColumnElement[Any], Cast[Any]] = func.cast(func.convert_from(column, "UTF8"), JSONB)
- for key in keys:
- cast = cast.__getitem__(key)
- return cast.astext
- else:
- for key in keys:
- column = func.json_extract(column, f"$.{key}")
- return column
-
stmnt = (
select(model.StoredWorkflow)
.join(model.Workflow, model.Workflow.id == model.StoredWorkflow.latest_workflow_id)
.filter(
and_(
model.StoredWorkflow.deleted == false(),
- to_json(model.Workflow.source_metadata, ["trs_tool_id"]) == trs_id,
- to_json(model.Workflow.source_metadata, ["trs_version_id"]) == trs_version,
+ to_json(sa_session, model.Workflow.source_metadata, ["trs_tool_id"]) == trs_id,
+ to_json(sa_session, model.Workflow.source_metadata, ["trs_version_id"]) == trs_version,
)
)
)
diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py
index 119454c93963..26d301024fca 100644
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -68,8 +68,10 @@
bindparam,
Boolean,
case,
+ Cast,
Column,
column,
+ ColumnElement,
DateTime,
delete,
desc,
@@ -100,6 +102,7 @@
update,
VARCHAR,
)
+from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.exc import (
CompileError,
OperationalError,
@@ -315,6 +318,19 @@ def get_uuid(uuid: Optional[Union[UUID, str]] = None) -> UUID:
return UUID(str(uuid))
+def to_json(sa_session, column, keys: List[str]):
+ assert sa_session.bind
+ if sa_session.bind.dialect.name == "postgresql":
+ cast: Union[ColumnElement[Any], Cast[Any]] = func.cast(func.convert_from(column, "UTF8"), JSONB)
+ for key in keys:
+ cast = cast.__getitem__(key)
+ return cast.astext
+ else:
+ for key in keys:
+ column = func.json_extract(column, f"$.{key}")
+ return column
+
+
class Base(DeclarativeBase, _HasTable):
__abstract__ = True
metadata = MetaData(naming_convention=NAMING_CONVENTION)
@@ -898,8 +914,8 @@ def get_user_data_tables(self, data_table: str):
Dataset.state == "ok",
# excludes data manager runs that actually populated tables.
# maybe track this formally by creating a different datatype for bundles ?
- Dataset.total_size != Dataset.file_size,
HistoryDatasetAssociation._metadata.contains(data_table),
+ to_json(session, HistoryDatasetAssociation._metadata, ["is_bundle"]) == "true",
)
.order_by(HistoryDatasetAssociation.id)
)
diff --git a/lib/galaxy/tool_util/xsd/galaxy.xsd b/lib/galaxy/tool_util/xsd/galaxy.xsd
index e2b90090bda7..88eb97f82b4a 100644
--- a/lib/galaxy/tool_util/xsd/galaxy.xsd
+++ b/lib/galaxy/tool_util/xsd/galaxy.xsd
@@ -8093,7 +8093,7 @@ favour of a ``has_size`` assertion.
-
+
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 90ccde24679e..e7125c055380 100644
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -3255,6 +3255,7 @@ def exec_after_process(self, app, inp_data, out_data, param_dict, job, final_job
create=True,
preserve_symlinks=True,
)
+ hda.metadata.is_bundle = True
else:
raise Exception("Unknown data manager mode encountered type...")
diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py
index 3befa87ae1e3..f977ab585594 100644
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -13,10 +13,11 @@
from collections.abc import MutableMapping
from typing import (
Any,
+ cast,
Dict,
List,
Optional,
- Tuple,
+ Sequence,
TYPE_CHECKING,
Union,
)
@@ -43,12 +44,14 @@
from galaxy.schema.fetch_data import FilesPayload
from galaxy.tool_util.parameters.factory import get_color_value
from galaxy.tool_util.parser import get_input_source as ensure_input_source
+from galaxy.tool_util.parser.interface import DrillDownOptionsDict
from galaxy.tool_util.parser.util import (
boolean_is_checked,
boolean_true_and_false_values,
ParameterParseException,
text_input_is_optional,
)
+from galaxy.tools.parameters.options import ParameterOption
from galaxy.tools.parameters.workflow_utils import (
NO_REPLACEMENT,
workflow_building_modes,
@@ -131,6 +134,10 @@ def parse_dynamic_options(param, input_source):
return dynamic_options.DynamicOptions(options_elem, param)
+def serialize_options(security: "IdEncodingHelper", options: Sequence[ParameterOption]):
+ return [option.serialize(security) for option in options]
+
+
# Describe a parameter value error where there is no actual supplied
# parameter - e.g. just a specification issue.
NO_PARAMETER_VALUE = object()
@@ -175,7 +182,7 @@ class ToolParameter(UsesDictVisibleKeys):
>>> from galaxy.util.bunch import Bunch
>>> from galaxy.util import XML
- >>> trans = Bunch(app=None)
+ >>> trans = Bunch(app=None, security=lambda x: x)
>>> p = ToolParameter(None, XML(''))
>>> assert p.name == 'parameter_name'
>>> assert sorted(p.to_dict(trans).items()) == [('argument', '--parameter-name'), ('help', ''), ('help_format', 'html'), ('hidden', False), ('is_dynamic', False), ('label', ''), ('model_class', 'ToolParameter'), ('name', 'parameter_name'), ('optional', False), ('refresh_on_change', False), ('type', 'text'), ('value', None)]
@@ -924,7 +931,7 @@ class SelectToolParameter(ToolParameter):
>>> from galaxy.util.bunch import Bunch
>>> from galaxy.util import XML
- >>> trans = Bunch(app=None, history=Bunch(), workflow_building_mode=False)
+ >>> trans = Bunch(app=None, history=Bunch(), workflow_building_mode=False, security=lambda x: x)
>>> p = SelectToolParameter(None, XML(
... '''
...
@@ -982,13 +989,15 @@ def _get_dynamic_options_call_other_values(self, trans, other_values):
call_other_values.update(other_values.dict)
return call_other_values
- def get_options(self, trans, other_values):
+ def get_options(self, trans, other_values) -> Sequence[Union[ParameterOption, DrillDownOptionsDict]]:
if self.options:
return self.options.get_options(trans, other_values)
elif self.dynamic_options:
call_other_values = self._get_dynamic_options_call_other_values(trans, other_values)
try:
- return eval(self.dynamic_options, self.tool.code_namespace, call_other_values)
+ return [
+ ParameterOption(*o) for o in eval(self.dynamic_options, self.tool.code_namespace, call_other_values)
+ ]
except Exception as e:
log.debug(
"Error determining dynamic options for parameter '%s' in tool '%s':",
@@ -998,22 +1007,21 @@ def get_options(self, trans, other_values):
)
return []
else:
- return self.static_options
+ return [ParameterOption(*o) for o in self.static_options]
def get_legal_values(self, trans, other_values, value):
"""
determine the set of values of legal options
"""
- return {
- history_item_dict_to_python(v, trans.app, self.name) or v
- for _, v, _ in self.get_options(trans, other_values)
- }
+ 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):
"""
- determine a mapping from names to values for all legal options
+ determine the set of values of legal options
"""
- return {n: v for n, v, _ in self.get_options(trans, other_values)}
+ 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):
return self._select_from_json(value, trans, other_values=other_values, require_legal_value=True)
@@ -1135,17 +1143,17 @@ def to_python(self, value, app):
def get_initial_value(self, trans, other_values):
try:
- options = list(self.get_options(trans, other_values))
+ options = cast(List[ParameterOption], self.get_options(trans, other_values))
except ImplicitConversionRequired:
return None
if not options:
return None
- value = [optval for _, optval, selected in options if selected]
+ value = [option.value for option in options if option.selected]
if len(value) == 0:
if not self.optional and not self.multiple and options:
# Nothing selected, but not optional and not a multiple select, with some values,
# so we have to default to something (the HTML form will anyway)
- value2 = options[0][1]
+ value2: Optional[Union[str, List[str]]] = options[0].value
else:
value2 = None
elif len(value) == 1 or not self.multiple:
@@ -1185,8 +1193,8 @@ def to_dict(self, trans, other_values=None):
d = super().to_dict(trans, other_values)
# Get options, value.
- options = self.get_options(trans, other_values)
- d["options"] = options
+ options = cast(List[ParameterOption], self.get_options(trans, other_values))
+ d["options"] = serialize_options(trans.security, options)
d["display"] = self.display
d["multiple"] = self.multiple
d["textable"] = is_runtime_context(trans, other_values)
@@ -1211,7 +1219,7 @@ class GenomeBuildParameter(SelectToolParameter):
>>> # Create a mock transaction with 'hg17' as the current build
>>> from galaxy.util.bunch import Bunch
>>> from galaxy.util import XML
- >>> trans = Bunch(app=None, history=Bunch(genome_build='hg17'), db_builds=read_dbnames(None))
+ >>> trans = Bunch(app=None, history=Bunch(genome_build='hg17'), db_builds=read_dbnames(None), security=lambda x:x)
>>> p = GenomeBuildParameter(None, XML(''))
>>> print(p.name)
_name
@@ -1231,12 +1239,14 @@ def __init__(self, *args, **kwds):
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):
+ def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
last_used_build = object()
if trans.history:
last_used_build = trans.history.genome_build
- for dbkey, build_name in self._get_dbkey_names(trans=trans):
- yield build_name, dbkey, (dbkey == last_used_build)
+ return [
+ ParameterOption(build_name, dbkey, (dbkey == last_used_build))
+ for dbkey, build_name in self._get_dbkey_names(trans=trans)
+ ]
def get_legal_values(self, trans, other_values, value):
return {dbkey for dbkey, _ in self._get_dbkey_names(trans=trans)}
@@ -1246,16 +1256,16 @@ def to_dict(self, trans, other_values=None):
d = ToolParameter.to_dict(self, trans)
# Get options, value - options is a generator here, so compile to list
- options = list(self.get_options(trans, {}))
- value = options[0][1]
+ options = self.get_options(trans, {})
+ value = options[0].value
for option in options:
- if option[2]:
+ if option.selected:
# Found selected option.
- value = option[1]
+ value = option.value
d.update(
{
- "options": options,
+ "options": serialize_options(trans, options),
"value": value,
"display": self.display,
"multiple": self.multiple,
@@ -1343,13 +1353,13 @@ def get_tag_list(self, other_values):
tags.add(tag.user_value)
return list(tags)
- def get_options(self, trans, other_values):
+ def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
"""
Show tags
"""
options = []
for tag in self.get_tag_list(other_values):
- options.append((f"Tags: {tag}", tag, False))
+ options.append(ParameterOption(f"Tags: {tag}", tag, False))
return options
def get_initial_value(self, trans, other_values):
@@ -1516,11 +1526,11 @@ def get_column_list(self, trans, other_values):
column_list = [c for c in column_list if c in this_column_list]
return column_list
- def get_options(self, trans, other_values):
+ def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
"""
Show column labels rather than c1..cn if use_header_names=True
"""
- options: List[Tuple[str, Union[str, Tuple[str, str]], bool]] = []
+ options: Sequence[ParameterOption] = []
column_list = self.get_column_list(trans, other_values)
if not column_list:
return options
@@ -1534,7 +1544,10 @@ def get_options(self, trans, other_values):
and dataset.metadata.element_is_set("column_names")
):
try:
- options = [(f"c{c}: {dataset.metadata.column_names[int(c) - 1]}", c, False) for c in column_list]
+ options = [
+ ParameterOption(f"c{c}: {dataset.metadata.column_names[int(c) - 1]}", c, False)
+ for c in column_list
+ ]
except IndexError:
# ignore and rely on fallback
pass
@@ -1543,13 +1556,13 @@ def get_options(self, trans, other_values):
with open(dataset.get_file_name()) as f:
head = f.readline()
cnames = head.rstrip("\n\r ").split("\t")
- options = [(f"c{c}: {cnames[int(c) - 1]}", c, False) for c in column_list]
+ options = [ParameterOption(f"c{c}: {cnames[int(c) - 1]}", c, False) for c in column_list]
except Exception:
# ignore and rely on fallback
pass
if not options:
# fallback if no options list could be built so far
- options = [(f"Column: {col}", col, False) for col in column_list]
+ options = [ParameterOption(f"Column: {col}", col, False) for col in column_list]
return options
def get_initial_value(self, trans, other_values):
@@ -1612,7 +1625,7 @@ class DrillDownSelectToolParameter(SelectToolParameter):
>>> from galaxy.util.bunch import Bunch
>>> app = Bunch(config=Bunch(tool_data_path=None))
>>> tool = Bunch(app=app)
- >>> trans = Bunch(app=app, history=Bunch(genome_build='hg17'), db_builds=read_dbnames(None))
+ >>> trans = Bunch(app=app, history=Bunch(genome_build='hg17'), db_builds=read_dbnames(None), security=lambda x: x)
>>> p = DrillDownSelectToolParameter(tool, XML(
... '''
...
@@ -1679,18 +1692,17 @@ def _get_options_from_code(self, trans=None, other_values=None):
except Exception:
return []
- def get_options(self, trans=None, other_values=None):
+ def get_options(self, trans=None, other_values=None) -> List[DrillDownOptionsDict]:
other_values = other_values or {}
if self.is_dynamic:
if self.dynamic_options:
- options = self._get_options_from_code(trans=trans, other_values=other_values)
- else:
- options = []
- return options
+ return self._get_options_from_code(trans=trans, other_values=other_values)
+ return []
+
return self.options
def get_legal_values(self, trans, other_values, value):
- def recurse_options(legal_values, options):
+ def recurse_options(legal_values, options: List[DrillDownOptionsDict]):
for option in options:
legal_values.append(option["value"])
recurse_options(legal_values, option["options"])
@@ -1736,16 +1748,16 @@ def to_param_dict_string(self, value, other_values=None):
other_values = other_values or {}
def get_options_list(value):
- def get_base_option(value, options):
+ def get_base_option(value, options: List[DrillDownOptionsDict]):
for option in options:
if value == option["value"]:
return option
- rval = get_base_option(value, option["options"])
+ rval = get_base_option(value, option["options"] or [])
if rval:
return rval
return None # not found
- def recurse_option(option_list, option):
+ def recurse_option(option_list, option: DrillDownOptionsDict):
if not option["options"]:
option_list.append(option["value"])
else:
@@ -1753,8 +1765,10 @@ def recurse_option(option_list, option):
recurse_option(option_list, opt)
rval: List[str] = []
- base_option = get_base_option(value, self.get_options(other_values=other_values))
- recurse_option(rval, base_option)
+ options = self.get_options(other_values=other_values)
+ base_option = get_base_option(value, options)
+ if base_option:
+ recurse_option(rval, base_option)
return rval or [value]
if value is None:
@@ -1780,11 +1794,11 @@ def recurse_option(option_list, option):
return rval
def get_initial_value(self, trans, other_values):
- def recurse_options(initial_values, options):
+ def recurse_options(initial_values, options: List[DrillDownOptionsDict]):
for option in options:
if option["selected"]:
initial_values.append(option["value"])
- recurse_options(initial_values, option["options"])
+ recurse_options(initial_values, option["options"] or [])
# More working around dynamic options for workflow
options = self.get_options(trans=trans, other_values=other_values)
@@ -1797,11 +1811,11 @@ def recurse_options(initial_values, options):
return initial_values
def to_text(self, value):
- def get_option_display(value, options):
+ def get_option_display(value, options: List[DrillDownOptionsDict]):
for option in options:
if value == option["value"]:
return option["name"]
- rval = get_option_display(value, option["options"])
+ rval = get_option_display(value, option["options"] or [])
if rval:
return rval
return None # not found
@@ -1823,7 +1837,7 @@ def get_option_display(value, options):
else:
rval = []
for val in value:
- rval.append(get_option_display(val, self.options) or val)
+ rval.append(get_option_display(val, self.options or val))
if rval:
return "\n".join(map(str, rval))
return "Nothing selected."
diff --git a/lib/galaxy/tools/parameters/dynamic_options.py b/lib/galaxy/tools/parameters/dynamic_options.py
index bb5f070bd60b..49010bab392b 100644
--- a/lib/galaxy/tools/parameters/dynamic_options.py
+++ b/lib/galaxy/tools/parameters/dynamic_options.py
@@ -15,7 +15,10 @@
cast,
Dict,
get_args,
+ List,
Optional,
+ Sequence,
+ Set,
)
from typing_extensions import Literal
@@ -29,6 +32,7 @@
User,
)
from galaxy.tools.expressions import do_eval
+from galaxy.tools.parameters.options import ParameterOption
from galaxy.tools.parameters.workflow_utils import (
is_runtime_value,
workflow_building_modes,
@@ -92,7 +96,7 @@ def __init__(self, d_option, elem):
self.column = d_option.column_spec_to_index(column)
self.keep = string_as_bool(elem.get("keep", "True"))
- def filter_options(self, options, trans, other_values):
+ def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
rval = []
filter_value = self.value
try:
@@ -128,7 +132,7 @@ def __init__(self, d_option, elem):
self.column = d_option.column_spec_to_index(column)
self.keep = string_as_bool(elem.get("keep", "True"))
- def filter_options(self, options, trans, other_values):
+ def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
rval = []
filter_value = self.value
try:
@@ -184,7 +188,9 @@ def __init__(self, d_option, elem):
def get_dependency_name(self):
return self.ref_name
- def filter_options(self, options, trans, other_values):
+ def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
+ options = list(options)
+
def _add_meta(meta_value, m):
if isinstance(m, list):
meta_value |= set(m)
@@ -224,7 +230,7 @@ def compare_meta_value(file_value, dataset_value):
# - for data sets: the meta data value
# in both cases only meta data that is set (i.e. differs from the no_value)
# is considered
- meta_value = set()
+ meta_value: Set[Any] = set()
for r in ref:
if not r.metadata.element_is_set(self.key):
continue
@@ -236,7 +242,7 @@ def compare_meta_value(file_value, dataset_value):
return copy.deepcopy(options)
if self.column is not None:
- rval = []
+ rval: List[ParameterOption] = []
for fields in options:
if compare_meta_value(fields[self.column], meta_value):
rval.append(fields)
@@ -246,7 +252,7 @@ def compare_meta_value(file_value, dataset_value):
self.dynamic_option.columns = {"name": 0, "value": 1, "selected": 2}
self.dynamic_option.largest_index = 2
for value in meta_value:
- options.append((value, value, False))
+ options.append(ParameterOption(value, value, False))
return options
@@ -286,7 +292,7 @@ def __init__(self, d_option, elem):
def get_dependency_name(self):
return self.ref_name
- def filter_options(self, options, trans, other_values):
+ def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
ref = other_values.get(self.ref_name, None)
if ref is None or is_runtime_value(ref):
ref = []
@@ -336,7 +342,7 @@ def __init__(self, d_option, elem):
def get_dependency_name(self):
return self.dynamic_option.dataset_ref_name
- def filter_options(self, options, trans, other_values):
+ def filter_options(self, options: Sequence[ParameterOption], trans, other_values):
rval = []
seen = set()
for fields in options:
@@ -365,12 +371,17 @@ def __init__(self, d_option, elem):
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: Sequence[ParameterOption], trans, other_values):
rval = []
for fields in options:
for column in self.columns:
- for field in fields[column].split(self.separator):
- rval.append(fields[0:column] + [field] + fields[column + 1 :])
+ field = fields[column]
+ if isinstance(field, str):
+ for split_field in field.split(self.separator):
+ new_options = list(fields[0:column]) + [split_field] + list(fields[column + 1 :])
+ # tested in filter_multiple_splitter.xml
+ option = tuple(new_options)
+ rval.append(option)
return rval
@@ -826,6 +837,19 @@ def get_fields(self, trans, other_values):
options = filter.filter_options(options, trans, other_values)
return options
+ @staticmethod
+ def to_parameter_options(options):
+ rval: List[ParameterOption] = []
+ for option in options:
+ if isinstance(option, ParameterOption):
+ rval.append(option)
+ else:
+ if len(option) == 1:
+ rval.append(ParameterOption(option[0], option[0]))
+ else:
+ rval.append(ParameterOption(*option[:3]))
+ return rval
+
def get_user_options(self, user: User):
# stored metadata are key: value pairs, turn into flat lists of correct order
fields = []
@@ -843,9 +867,19 @@ def get_user_options(self, user: User):
by_dbkey.update(table_entries)
for data_table_entry in by_dbkey.values():
field_entry = []
+ if hda := data_table_entry.get("__hda__"):
+ field_entry.append(hda)
+ missing_columns = False
for column_key in self.tool_data_table.columns.keys():
+ if column_key not in data_table_entry:
+ # currrent data table definition (as in self.tool_data_table)
+ # may not match against the data manager bundle.
+ # Breaking here fixes https://github.com/galaxyproject/galaxy/issues/18749.
+ missing_columns = True
+ break
field_entry.append(data_table_entry[column_key])
- fields.append(field_entry)
+ if not missing_columns:
+ fields.append(field_entry)
return fields
@staticmethod
@@ -857,7 +891,7 @@ def hda_to_table_entries(hda, table_name):
if path := value.get("path"):
# maybe a hack, should probably pass around dataset or src id combinations ?
value["path"] = os.path.join(hda.extra_files_path, path)
- value["value"] = {"src": "hda", "id": hda.id}
+ value["__hda__"] = hda
return table_entries
def get_option_from_dataset(self, dataset):
@@ -894,15 +928,15 @@ def get_field_by_name_for_value(self, field_name, value, trans, other_values):
rval.append(fields[field_index])
return rval
- def get_options(self, trans, other_values):
+ def get_options(self, trans, other_values) -> Sequence[ParameterOption]:
- rval = []
+ rval: List[ParameterOption] = []
- def to_triple(values):
+ def to_option(values):
if len(values) == 2:
- return [str(values[0]), str(values[1]), False]
+ return ParameterOption(str(values[0]), str(values[1]), False)
else:
- return [str(values[0]), str(values[1]), bool(values[2])]
+ return ParameterOption(str(values[0]), str(values[1]), bool(values[2]))
if from_url_options := self.from_url_options:
context = User.user_template_environment(trans.user)
@@ -940,7 +974,7 @@ def to_triple(values):
data = []
# We only support the very specific ["name", "value", "selected"] format for now.
- rval = [to_triple(d) for d in data]
+ rval = [to_option(d) for d in data]
if (
self.file_fields is not None
or self.tool_data_table is not None
@@ -949,11 +983,14 @@ def to_triple(values):
):
options = self.get_fields(trans, other_values)
for fields in options:
- rval.append((fields[self.columns["name"]], fields[self.columns["value"]], False))
+ name = fields[self.columns["name"]]
+ value = fields[self.columns["value"]]
+ hda = fields[-1] if isinstance(fields[-1], HistoryDatasetAssociation) else None
+ rval.append(ParameterOption(name, value, False, dataset=hda))
else:
for filter in self.filters:
rval = filter.filter_options(rval, trans, other_values)
- return rval
+ return self.to_parameter_options(rval)
def column_spec_to_index(self, column_spec):
"""
diff --git a/lib/galaxy/tools/parameters/options.py b/lib/galaxy/tools/parameters/options.py
new file mode 100644
index 000000000000..4fd23c3fcaed
--- /dev/null
+++ b/lib/galaxy/tools/parameters/options.py
@@ -0,0 +1,29 @@
+from typing import (
+ NamedTuple,
+ Optional,
+)
+
+from galaxy.model import (
+ DatasetInstance,
+ HistoryDatasetAssociation,
+)
+from galaxy.security.idencoding import IdEncodingHelper
+
+
+class ParameterOption(NamedTuple):
+ name: str
+ value: str
+ selected: bool = False
+ dataset: Optional["DatasetInstance"] = None
+
+ def serialize(self, security: "IdEncodingHelper"):
+ if self.dataset:
+ return (
+ self.name,
+ {
+ "src": "hda" if isinstance(self.dataset, HistoryDatasetAssociation) else "ldda",
+ "id": security.encode_id(self.dataset.id),
+ },
+ self.selected,
+ )
+ return (self.name, self.value, self.selected)
diff --git a/test/unit/app/tools/test_dynamic_options.py b/test/unit/app/tools/test_dynamic_options.py
index 05ec365a379e..3de57c332e54 100644
--- a/test/unit/app/tools/test_dynamic_options.py
+++ b/test/unit/app/tools/test_dynamic_options.py
@@ -1,5 +1,6 @@
from galaxy.app_unittest_utils.galaxy_mock import MockApp
from galaxy.tools.parameters.dynamic_options import DynamicOptions
+from galaxy.tools.parameters.options import ParameterOption
from galaxy.util import XML
from galaxy.util.bunch import Bunch
from galaxy.work.context import WorkRequestContext
@@ -60,4 +61,4 @@ def test_dynamic_option_cache():
"start_index": 0,
},
)
- assert from_url_option.get_options(trans, {}) == [["chr2L", "23513712", False]]
+ assert from_url_option.get_options(trans, {}) == [ParameterOption("chr2L", "23513712", False)]
diff --git a/test/unit/app/tools/test_evaluation.py b/test/unit/app/tools/test_evaluation.py
index 27e4b4a147b5..f3451cf92d61 100644
--- a/test/unit/app/tools/test_evaluation.py
+++ b/test/unit/app/tools/test_evaluation.py
@@ -27,6 +27,7 @@
ConditionalWhen,
Repeat,
)
+from galaxy.tools.parameters.options import ParameterOption
from galaxy.util import XML
from galaxy.util.bunch import Bunch
from galaxy.util.unittest import TestCase
@@ -171,7 +172,7 @@ def get_field_by_name_for_value(name, value, trans, other_values):
return ["/old/path/human"]
def get_options(trans, other_values):
- return [["", "/old/path/human", ""]]
+ return [ParameterOption("", "/old/path/human", False)]
parameter.options = Bunch(get_field_by_name_for_value=get_field_by_name_for_value, get_options=get_options)
self.tool.set_params({"index_path": parameter})
diff --git a/test/unit/app/tools/test_select_parameters.py b/test/unit/app/tools/test_select_parameters.py
index 30b025137e2b..d2a05f687f6e 100644
--- a/test/unit/app/tools/test_select_parameters.py
+++ b/test/unit/app/tools/test_select_parameters.py
@@ -3,6 +3,7 @@
import pytest
from galaxy import model
+from galaxy.tools.parameters.options import ParameterOption
from galaxy.tools.parameters.workflow_utils import RuntimeValue
from .util import BaseParameterTestCase
@@ -41,15 +42,23 @@ def test_unvalidated_datasets(self):
def test_filter_param_value(self):
self.options_xml = """"""
- assert ("testname1", "testpath1", False) in self.param.get_options(self.trans, {"input_bam": "testname1"})
- assert ("testname2", "testpath2", False) in self.param.get_options(self.trans, {"input_bam": "testname2"})
+ assert ParameterOption("testname1", "testpath1", False) in self.param.get_options(
+ self.trans, {"input_bam": "testname1"}
+ )
+ assert ParameterOption("testname2", "testpath2", False) in self.param.get_options(
+ self.trans, {"input_bam": "testname2"}
+ )
assert len(self.param.get_options(self.trans, {"input_bam": "testname3"})) == 0
def test_filter_param_value2(self):
# Same test as above, but filtering on a different column.
self.options_xml = """"""
- assert ("testname1", "testpath1", False) in self.param.get_options(self.trans, {"input_bam": "testpath1"})
- assert ("testname2", "testpath2", False) in self.param.get_options(self.trans, {"input_bam": "testpath2"})
+ assert ParameterOption("testname1", "testpath1", False) in self.param.get_options(
+ self.trans, {"input_bam": "testpath1"}
+ )
+ assert ParameterOption("testname2", "testpath2", False) in self.param.get_options(
+ self.trans, {"input_bam": "testpath2"}
+ )
assert len(self.param.get_options(self.trans, {"input_bam": "testpath3"})) == 0
# TODO: Good deal of overlap here with TestDataToolParameter, refactor.