PR #18641: Parameter Model Improvements - Research Summary
PR: galaxyproject/galaxy#18641
Branch: model_parameter_improvements_1 -> dev
Merged: August 2024
Diff: 1974 lines across ~20 files
Context: Enhancements from downstream work validating workflows and tool test cases with models from #18524, part of broader structured tool state work #17393.
Overview
This PR extended the parameter model system with three new parameter types (drill_down, data_column, conditional_select), improved request_requires_value logic across several parameter models, relocated ToolTestDescriptionDict to a canonical location, and added JSON assertion error handling. It also refactored DrillDownSelectToolParameter to use the parser abstraction layer instead of raw XML.
1. New Parameter Model Classes
DrillDownParameterModel
PR introduced (models.py):
parameter_type: Literal["gx_drill_down"]- Fields:
options: Optional[List[DrillDownOptionsDict]],multiple: bool,hierarchy: DrillDownHierarchyT DrillDownHierarchyT = Literal["recurse", "exact"]- Helper functions:
drill_down_possible_values(),any_drill_down_options_selected() py_typebuilds Literal unions from possible values; falls back toStrictStrfor dynamic optionsrequest_requires_valuechecks if any static option is selected (serves as default)
Current location: lib/galaxy/tool_util_models/parameters.py, lines 1311-1403
Changes since PR:
- Added
type: Literal["drill_down"]field (PR only hadparameter_type) drill_down_possible_values()now acceptshierarchyparameter (3-arg vs PR’s 2-arg). The function at line 1287 now checkshierarchy == "recurse"to decide whether non-leaf nodes are selectable, instead of the PR’s simplernot multiple and not is_leafcheck- Added
py_type_test_case_xmlproperty (line 1335) returningstrtype for XML test cases - Added
pydantic_templatewithstate_representationrouting fortest_case_xml,test_case_json,job_internal,job_runtime - Added
default_optionanddefault_optionsproperties (lines 1362-1377) that extract selected options - Added
selected_drill_down_options()helper function (line 1392) - complementingany_drill_down_options_selected()
DataColumnParameterModel
PR introduced (models.py):
parameter_type: Literal["gx_data_column"]- Simple model:
py_typewasStrictInt,request_requires_valuereturnedFalse
Current location: lib/galaxy/tool_util_models/parameters.py, lines 1405-1475
Changes since PR:
- Added
type: Literal["data_column"]field - Added
multiple: boolfield andvalue: Optional[Union[int, List[int]]] py_typenow handlesmultiple(list of StrictInt) andoptionalcases- Added
split_strstatic method for comma-separated string parsing in test cases pydantic_templatenow routes bystate_representation(test_case_xmlwith string splitting,test_case_json, default)request_requires_valuenow considersoptionalstate (returnsnot self.optional and not self.value)- Significantly more sophisticated than the PR’s bare-bones version
SelectParameterModel enhancements (not new, but extended)
PR added to SelectParameterModel:
default_valueproperty (returns first selected option, or first option if not optional)request_requires_valuechanged fromnot self.optional and not self.has_selected_static_optiontoself.multiple and not self.optional(only requires value for required multiple selects)
Current location: lib/galaxy/tool_util_models/parameters.py, lines 1151-1280
Changes since PR:
default_valueproperty still present (line 1227)default_valuesproperty added for multiple selects (line 1240)request_requires_valueat line 1247 now:self.multiple and not self.optional- Added
py_type_if_required,py_type_workflow_stepproperties - Added extensive
pydantic_templaterouting for different state representations - Added
split_strfor test case string handling - Added
validators: List[SelectCompatiableValidators]field (line 1156)
2. Factory Changes
PR file: lib/galaxy/tool_util/parameters/factory.py
Current file: lib/galaxy/tool_util/parameters/factory.py
PR changes:
- Fixed select
is_staticdetection: removed buggydynamic_options/dynamic_options_elemlogic, simplified tois_static = dynamic_options_config is None(line 213 current) - Added
drill_downhandler (lines 236-249 current) - Added
data_columnhandler - PR was minimal (nameonly) - Cast conditional test parameter to
Union[BooleanParameterModel, SelectParameterModel] - Added select default value handling for conditionals
Changes since PR:
data_columnhandler (lines 250-272 current) now parsesmultiple,optional,value,accept_default- Factory function signature changed:
_from_input_source_galaxy(input_source, profile)-profileparam was added after the PR from_input_sourcegainedprofileparameter- Conditional default value logic extracted to
cond_test_parameter_default_value()function (imported from models) - Added handling for
group_tag,baseurl,genomebuild,directory_uriparameter types (not in PR) - Added
ParameterDefinitionErrorandUnknownParameterTypeErrorexception classes - All model constructors now pass a
type=argument (e.g.,type="drill_down")
3. Parser Changes
interface.py
PR file: lib/galaxy/tool_util/parser/interface.py
Current file: lib/galaxy/tool_util/parser/interface.py
PR introduced:
DrillDownOptionsDictTypedDict (name, value, options, selected)DrillDownDynamicFilterstype aliasDrillDownDynamicOptionsabstract class withfrom_code_block()andfrom_filters()abstract methodsparse_drill_down_dynamic_options()andparse_drill_down_static_options()onInputSource
Current state:
DrillDownOptionsDicthas been moved tolib/galaxy/tool_util_models/tool_source.py(line 158) and is re-imported viainterface.pyline 32DrillDownDynamicFilterstype alias has been removed entirely from the codebaseDrillDownDynamicOptionsstill at line 500 of interface.py but simplified: only hasfrom_code_block(), thefrom_filters()method was removedparse_drill_down_dynamic_options()at line 588,parse_drill_down_static_options()at line 599
xml.py
PR file: lib/galaxy/tool_util/parser/xml.py
Current file: lib/galaxy/tool_util/parser/xml.py
PR introduced:
XmlDrillDownDynamicOptionsclass withcode_blockandfiltersproperties_recurse_drill_down_elems()functionparse_drill_down_dynamic_options()with complex filter parsing logicparse_drill_down_static_options()withfrom_filesupport
Current state:
XmlDrillDownDynamicOptionsat line 1640 - simplified: only takescode_block, nofiltersparameter_recurse_drill_down_elems()at line 1656 - unchanged from PRparse_drill_down_dynamic_options()at line 1475 - significantly simplified: only checksdynamic_optionsattribute, no filter parsingparse_drill_down_static_options()at line 1486 - largely same as PR
The filter-based drill down dynamic option parsing was removed. The basic.py DrillDownSelectToolParameter no longer sets self.filtered from the parser abstraction; the filtering logic must have been moved elsewhere or handled differently.
4. DrillDownSelectToolParameter Refactoring (basic.py)
PR file: lib/galaxy/tools/parameters/basic.py
Current file: lib/galaxy/tools/parameters/basic.py
PR replaced:
- Direct XML manipulation (
elem.get(),elem.findall(),from_filereading,recurse_option_elems()) with parser abstraction methods input_source.parse_drill_down_dynamic_options(tool_data_path)andinput_source.parse_drill_down_static_options(tool_data_path)- Removed ~30 lines of inline XML parsing, added ~15 lines using parser abstraction
- Changed
__init__to useinput_source.get_bool(),input_source.get()instead ofelem.get()
Current state (line 1663):
- Init uses
input_source.parse_drill_down_dynamic_options(tool_data_path)andinput_source.parse_drill_down_static_options(tool_data_path) - Sets
self.dynamic_options = drill_down_dynamic_options.from_code_block()(nofiltersreference) - No longer sets
self.filteredfrom dynamic options - Added
self.tool.app.config.tool_data_path if self.tool else Noneguard (handlestool=Nonecase)
Key difference from PR: The PR set self.filtered = drill_down_dynamic_options.filters but the current code does not set self.filtered at all in the dynamic path; the filtered attribute is entirely gone from the init.
5. RepeatParameterModel Improvements
PR changes (models.py):
- Changed
request_requires_valuefromreturn Trueto checkingminand child parameter requirements - Changed repeat
pydantic_templateto useinitialize_repeatbased onrequest_requires_valueinstead of always...
Current state (line 1623-1668):
request_requires_valuelogic is identical to PR: checksmin is None or min == 0, then checks child parameterspydantic_templatehas been extended withstate_representationrouting (job_internal,job_runtime, landing requests)- Core logic from PR is preserved
6. Verify Module Changes
_types.py
PR moved ToolTestDescriptionDict from interactor.py to _types.py
Current file: lib/galaxy/tool_util/verify/_types.py
Changes since PR:
ToolTestDescriptionDictat line 40 now has additional fields:request: NotRequired[Optional[Dict[str, Any]]](line 46)request_schema: NotRequired[Optional[Dict[str, Any]]](line 47)request_unavailable_reason: NotRequired[Optional[str]](line 63)value_state_representation: NotRequired[ValueStateRepresentationT](line 65)
- Added
ValueStateRepresentationT = Literal["test_case_xml", "test_case_json"](line 37) - Added
RawTestToolRequest = Dict[str, Any](line 30)
init.py
PR added __all__ list and re-export of ToolTestDescriptionDict and ExpandedToolInputsJsonified
Current file: lib/galaxy/tool_util/verify/__init__.py - still re-exports from _types (line 51-54), __all__ at line 675
interactor.py
PR removed ToolTestDescriptionDict class from interactor.py, imported from _types
Current file: lib/galaxy/tool_util/verify/interactor.py - imports from _types at line 75
script.py
PR changed import from interactor to verify.__init__
Current file: lib/galaxy/tool_util/verify/script.py - imports from verify at line 26
api/tools.py
PR changed import from verify.interactor to verify
Current file: lib/galaxy/webapps/galaxy/api/tools.py - imports from verify at line 64
asserts/json.py
PR added assert_json_and_load() helper wrapping json.loads with AssertionError
Current file: lib/galaxy/tool_util/verify/asserts/json.py - renamed to _assert_json_and_load() (line 78, private function)
7. Test Infrastructure Changes
unittest_utils/parameters.py
PR refactored:
- Removed
tool_parameter(),parameter_source()single-parameter functions - Changed
parameter_bundle_for_file()to returnToolParameterBundleModel(whole tool, not single param) - Added type annotations
Current file: lib/galaxy/tool_util/unittest_utils/parameters.py
Changes since PR:
- Added
parameter_bundle_for_framework_tool()function (line 27) for tools outside theparameters/directory - Added import of
functional_test_tool_pathfrom unittest_utils module parameter_bundleandParameterBundlestill exist for backward compatibility
test_parameter_specification.py
PR refactored:
- Changed from single-parameter validation to full bundle validation
- Replaced
if/elifchain withassertion_functionsdict dispatch - Changed all assertion helpers to take
ToolParameterBundleModelinstead ofToolParameterT
Current file: test/unit/tool_util/test_parameter_specification.py
Changes since PR:
- Added many more state representations:
relaxed_request_valid/invalid,workflow_step_valid/invalid,workflow_step_linked_valid/invalid,landing_request_valid/invalid,test_case_xml_valid/invalid,test_case_json_valid/invalid,job_internal_valid/invalid,job_runtime_valid/invalid model_assertion_function_factory()introduced for DRY assertion function creation_test_file()gains optionalparameter_bundleparameter
parameter_specification.yml
PR added test specs for:
gx_conditional_select(request_valid/invalid)gx_drill_down_exact(request_valid/invalid)gx_drill_down_exact_with_selection(request_valid/invalid)gx_data_column(request_valid/invalid, request_internal_valid/invalid)gx_selecttest_case_valid/invalid, request_internal_valid/invalidgx_repeat_boolean_min- added{}as valid (empty repeat when min has no required children)gx_repeat_data- added{}as valid (empty repeat)gx_repeat_data_min- added{}as invalid (data required inside repeat)
Current file: test/unit/tool_util/parameter_specification.yml
Changes since PR: Many more specs added for new representations and parameter types:
gx_data_column_optional,gx_data_column_multiple,gx_data_column_multiple_accept_default, etc.gx_drill_down_recurse,gx_drill_down_recurse_multiplegx_conditional_select_dynamic- Various workflow_step, landing_request, test_case_xml, test_case_json, job_internal, job_runtime specs
8. New Test Tools Added
All in test/functional/tools/parameters/:
From this PR:
gx_conditional_select.xml- conditional with select test parameter (not boolean)gx_data_column.xml- basic data_column parametergx_drill_down_exact.xml- drill_down with hierarchy=“exact”gx_drill_down_exact_multiple.xml- drill_down exact + multiple=“true”gx_drill_down_exact_with_selection.xml- drill_down withselected="true"on an optiongx_drill_down_recurse.xml- drill_down with hierarchy=“recurse”gx_drill_down_recurse_multiple.xml- drill_down recurse + multiple=“true”macros.xml- addedsimple_text_outputanddrill_down_static_optionsmacros
Added since PR:
gx_conditional_select_dynamic.xmlgx_data_column_accept_default.xml,gx_data_column_multiple*.xml,gx_data_column_optional*.xml,gx_data_column_with_default*.xmlgx_drill_down_code.xml+gx_drill_down_code.py
Modified by PR:
gx_repeat_boolean_min.xml- fixed tool id, added test case, added length echogx_select.xml- added implicit default test case
9. API Test Changes
PR file: lib/galaxy_test/api/test_tools.py
PR added:
test_select_first_by_default- verifies select parameters pick first option when no selection madetest_drill_down_first_by_default- verifies drill_down parameters without selection are rejected (400) but with selection succeedtest_optional_repeats_with_mins_filled_id- verifies repeats with min can be submitted empty if children are optional
Current state: These tests still exist (e.g., test_drill_down_first_by_default at line 1074 of test_tools.py).
10. Other Changes
basic.py doctest fixes
The PR added >>> from galaxy.util import XML to many doctests in basic.py because XML was removed from the top-level imports (replaced by parser abstraction). This pattern persists in the current codebase.
Tool test file typo fixes
multiple_versions_changes_v01.xml,v02.xml,v01galaxy6.xml: fixedintest->inttestparam name in test cases
Tool Shed schema.ts
Added DataColumnParameterModel and DrillDownParameterModel to the TypeScript schema. This file is auto-generated.
11. File Path Migration Summary
| PR Path | Current Path | Notes |
|---|---|---|
lib/galaxy/tool_util/parameters/models.py | lib/galaxy/tool_util_models/parameters.py | Moved to separate package |
lib/galaxy/tool_util/verify/_types.py | Same path | Not moved |
lib/galaxy/tool_util_models/_types.py | Exists (different from verify/_types.py) | Type utilities for pydantic building |
lib/galaxy/tool_util/parser/interface.py::DrillDownOptionsDict | lib/galaxy/tool_util_models/tool_source.py | Moved, re-imported by interface.py |
lib/galaxy/tool_util/parser/interface.py::DrillDownDynamicFilters | Removed | Filter abstraction removed |
lib/galaxy/tool_util/parser/interface.py::DrillDownDynamicOptions | Same path (simplified) | from_filters() removed |
lib/galaxy/tool_util/parameters/__init__.py | Same path | Re-exports from tool_util_models |
lib/galaxy/tool_util/parameters/factory.py | Same path | Extended significantly |
12. Conditional Select Default Value Handling
The PR introduced a pattern for finding default values of select-based conditional test parameters. In the PR this was inline in factory.py:
elif isinstance(test_parameter, SelectParameterModel):
select_default_value = select_parameter.default_value
if select_default_value is not None:
default_value = select_default_value
In the current codebase, this was extracted to a standalone function cond_test_parameter_default_value() in parameters.py (line 1498) and imported by factory.py.
Unresolved Questions
- The PR introduced
DrillDownDynamicFiltersand filter-based drill down parsing inxml.py(30+ lines of filter logic). This was subsequently completely removed. Was the filter approach abandoned, or was it moved to runtime handling inbasic.py’sget_options()method? - The PR’s
DrillDownDynamicOptions.from_filters()abstract method was removed. How does the currentDrillDownSelectToolParameterhandle filter-based dynamic options? Theget_options()method at line 1693 ofbasic.pystill referencesself.is_dynamicbut where does filter metadata come from? - The PR’s
DataColumnParameterModelwas bare-bones (StrictInt, always not required). The current version is significantly more complex. Was there an intermediate PR that did this expansion, or was it incremental work on the branch? - Several test specs in
parameter_specification.ymlthat the PR added as commented-out (e.g., conditional_select cases where test_parameter is missing) - are any of these now enabled? - The PR’s
gx_drill_down_exact_with_selectionhad- {}commented out inrequest_valid(a case where empty request uses selected default). Is this now valid or still commented out?