Skip to content

Commit

Permalink
Merge pull request galaxyproject#18673 from jmchilton/parameter_models_2
Browse files Browse the repository at this point in the history
Another round of parameter model improvements.
  • Loading branch information
jmchilton authored Aug 9, 2024
2 parents 03a7fda + 39cb7f2 commit 287b889
Show file tree
Hide file tree
Showing 25 changed files with 532 additions and 25 deletions.
33 changes: 33 additions & 0 deletions lib/galaxy/tool_util/parameters/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)
from galaxy.util import string_as_bool
from .models import (
BaseUrlParameterModel,
BooleanParameterModel,
ColorParameterModel,
ConditionalParameterModel,
Expand All @@ -31,8 +32,11 @@
DataCollectionParameterModel,
DataColumnParameterModel,
DataParameterModel,
DirectoryUriParameterModel,
DrillDownParameterModel,
FloatParameterModel,
GenomeBuildParameterModel,
GroupTagParameterModel,
HiddenParameterModel,
IntegerParameterModel,
LabelValue,
Expand Down Expand Up @@ -67,6 +71,9 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT:
int_value = int(value)
elif optional:
int_value = None
elif value == "" or value is None:
# A truly required parameter: https://github.com/galaxyproject/galaxy/pull/16966/files
int_value = None
else:
raise ParameterDefinitionError()
return IntegerParameterModel(name=input_source.parse_name(), optional=optional, value=int_value)
Expand Down Expand Up @@ -101,9 +108,11 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT:
)
elif param_type == "hidden":
optional = input_source.parse_optional()
value = input_source.get("value")
return HiddenParameterModel(
name=input_source.parse_name(),
optional=optional,
value=value,
)
elif param_type == "color":
optional = input_source.parse_optional()
Expand Down Expand Up @@ -164,6 +173,26 @@ def _from_input_source_galaxy(input_source: InputSource) -> ToolParameterT:
return DataColumnParameterModel(
name=input_source.parse_name(),
)
elif param_type == "group_tag":
return GroupTagParameterModel(
name=input_source.parse_name(),
)
elif param_type == "baseurl":
return BaseUrlParameterModel(
name=input_source.parse_name(),
)
elif param_type == "genomebuild":
optional = input_source.parse_optional()
multiple = input_source.get_bool("multiple", False)
return GenomeBuildParameterModel(
name=input_source.parse_name(),
optional=optional,
multiple=multiple,
)
elif param_type == "directory_uri":
return DirectoryUriParameterModel(
name=input_source.parse_name(),
)
else:
raise Exception(f"Unknown Galaxy parameter type {param_type}")
elif input_type == "conditional":
Expand Down Expand Up @@ -308,6 +337,10 @@ def input_models_for_pages(pages: PagesSource) -> List[ToolParameterT]:
def input_models_for_page(page_source: PageSource) -> List[ToolParameterT]:
input_models = []
for input_source in page_source.parse_input_sources():
input_type = input_source.parse_input_type()
if input_type == "display":
# not a real input... just skip this. Should this be handled in the parser layer better?
continue
tool_parameter_model = from_input_source(input_source)
input_models.append(tool_parameter_model)
return input_models
Expand Down
80 changes: 71 additions & 9 deletions lib/galaxy/tool_util/parameters/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
)

from pydantic import (
AnyUrl,
BaseModel,
ConfigDict,
create_model,
Discriminator,
Field,
field_validator,
HttpUrl,
RootModel,
StrictBool,
StrictFloat,
Expand All @@ -48,10 +50,7 @@

# TODO:
# - implement job vs request...
# - drill down
# - implement data_ref on rules and implement some cross model validation
# - Optional conditionals... work through that?
# - Sections - fight that battle again...

# + request: Return info needed to build request pydantic model at runtime.
# + request_internal: This is a pydantic model to validate what Galaxy expects to find in the database,
Expand Down Expand Up @@ -185,7 +184,7 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam

@property
def request_requires_value(self) -> bool:
return False
return not self.optional and self.value is None


class FloatParameterModel(BaseGalaxyToolParameterModelDefinition):
Expand Down Expand Up @@ -347,6 +346,7 @@ def request_requires_value(self) -> bool:

class HiddenParameterModel(BaseGalaxyToolParameterModelDefinition):
parameter_type: Literal["gx_hidden"] = "gx_hidden"
value: Optional[str]

@property
def py_type(self) -> Type:
Expand All @@ -357,7 +357,7 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam

@property
def request_requires_value(self) -> bool:
return not self.optional
return not self.optional and self.value is None


def ensure_color_valid(value: Optional[Any]):
Expand Down Expand Up @@ -423,8 +423,14 @@ def request_requires_value(self) -> bool:


class DirectoryUriParameterModel(BaseGalaxyToolParameterModelDefinition):
parameter_type: Literal["gx_directory_uri"]
value: Optional[str]
parameter_type: Literal["gx_directory_uri"] = "gx_directory_uri"

@property
def py_type(self) -> Type:
return AnyUrl

def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation:
return dynamic_model_information_from_py_type(self, self.py_type)

@property
def request_requires_value(self) -> bool:
Expand Down Expand Up @@ -464,8 +470,11 @@ class SelectParameterModel(BaseGalaxyToolParameterModelDefinition):
@property
def py_type(self) -> Type:
if self.options is not None:
literal_options: List[Type] = [cast_as_type(Literal[o.value]) for o in self.options]
py_type = union_type(literal_options)
if len(self.options) > 0:
literal_options: List[Type] = [cast_as_type(Literal[o.value]) for o in self.options]
py_type = union_type(literal_options)
else:
py_type = type(None)
else:
py_type = StrictStr
if self.multiple:
Expand Down Expand Up @@ -499,6 +508,26 @@ def request_requires_value(self) -> bool:
return self.multiple and not self.optional


class GenomeBuildParameterModel(BaseGalaxyToolParameterModelDefinition):
parameter_type: Literal["gx_genomebuild"] = "gx_genomebuild"
multiple: bool

@property
def py_type(self) -> Type:
py_type: Type = StrictStr
if self.multiple:
py_type = list_type(py_type)
return optional_if_needed(py_type, self.optional)

def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation:
return dynamic_model_information_from_py_type(self, self.py_type)

@property
def request_requires_value(self) -> bool:
# assumes it uses behavior of select parameters - an API test to reference for this would be nice
return self.multiple and not self.optional


DrillDownHierarchyT = Literal["recurse", "exact"]


Expand Down Expand Up @@ -587,6 +616,36 @@ def request_requires_value(self) -> bool:
return False


class GroupTagParameterModel(BaseGalaxyToolParameterModelDefinition):
parameter_type: Literal["gx_group_tag"] = "gx_group_tag"

@property
def py_type(self) -> Type:
return StrictStr

def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation:
return dynamic_model_information_from_py_type(self, self.py_type)

@property
def request_requires_value(self) -> bool:
return True


class BaseUrlParameterModel(BaseGalaxyToolParameterModelDefinition):
parameter_type: Literal["gx_baseurl"] = "gx_baseurl"

@property
def py_type(self) -> Type:
return HttpUrl

def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation:
return dynamic_model_information_from_py_type(self, self.py_type)

@property
def request_requires_value(self) -> bool:
return True


DiscriminatorType = Union[bool, str]


Expand Down Expand Up @@ -920,6 +979,9 @@ def request_requires_value(self) -> bool:
DirectoryUriParameterModel,
RulesParameterModel,
DrillDownParameterModel,
GroupTagParameterModel,
BaseUrlParameterModel,
GenomeBuildParameterModel,
ColorParameterModel,
ConditionalParameterModel,
RepeatParameterModel,
Expand Down
7 changes: 7 additions & 0 deletions lib/galaxy/tool_util/unittest_utils/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ToolSource,
)
from galaxy.util import galaxy_directory
from . import functional_test_tool_path


class ParameterBundle(ToolParameterBundle):
Expand All @@ -23,6 +24,12 @@ def parameter_bundle(parameter: ToolParameterT) -> ParameterBundle:
return ParameterBundle(parameter)


def parameter_bundle_for_framework_tool(filename: str) -> ToolParameterBundleModel:
path = functional_test_tool_path(filename)
tool_source = get_tool_source(path, macro_paths=[])
return input_models_for_tool_source(tool_source)


def parameter_bundle_for_file(filename: str) -> ToolParameterBundleModel:
tool_source = parameter_tool_source(filename)
return input_models_for_tool_source(tool_source)
Expand Down
5 changes: 5 additions & 0 deletions lib/galaxy/tool_util/verify/interactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,10 +505,14 @@ def stage_data_async(
tool_version: Optional[str] = None,
) -> Callable[[], None]:
fname = test_data["fname"]
tags = test_data.get("tags")
tool_input = {
"file_type": test_data["ftype"],
"dbkey": test_data["dbkey"],
}
if tags:
tool_input["tags"] = tags

metadata = test_data.get("metadata", {})
if not hasattr(metadata, "items"):
raise Exception(f"Invalid metadata description found for input [{fname}] - [{metadata}]")
Expand Down Expand Up @@ -1865,6 +1869,7 @@ def test_data_iter(required_files):
ftype=extra.get("ftype", DEFAULT_FTYPE),
dbkey=extra.get("dbkey", DEFAULT_DBKEY),
location=extra.get("location", None),
tags=extra.get("tags", []),
)
edit_attributes = extra.get("edit_attributes", [])

Expand Down
8 changes: 7 additions & 1 deletion lib/galaxy/tools/actions/upload_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,13 @@ def new_upload(
)
else:
upload_target_dataset_instance = __new_history_upload(trans, uploaded_dataset, history=history, state=state)

tags_raw = getattr(uploaded_dataset, "tags", None)
if tags_raw:
new_tags = tag_handler.parse_tags_list(tags_raw.split(","))
for tag in new_tags:
tag_handler.apply_item_tag(
user=trans.user, item=upload_target_dataset_instance, name=tag[0], value=tag[1], flush=True
)
if tag_list:
tag_handler.add_tags_from_list(trans.user, upload_target_dataset_instance, tag_list, flush=False)

Expand Down
32 changes: 32 additions & 0 deletions lib/galaxy_test/api/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,38 @@ def test_select_first_by_default(self):
self._assert_status_code_is(response, 400)
assert "an invalid option" in response.text

@skip_without_tool("gx_select_multiple")
@skip_without_tool("gx_select_multiple_optional")
def test_select_multiple_null_handling(self):
with self.dataset_populator.test_history(require_new=False) as history_id:
inputs: Dict[str, Any] = {}
response = self._run("gx_select_multiple", history_id, inputs, assert_ok=True)
output = response["outputs"][0]
output1_content = self.dataset_populator.get_history_dataset_content(history_id, dataset=output)
assert output1_content.strip() == "None"

inputs = {}
response = self._run("gx_select_multiple_optional", history_id, inputs, assert_ok=True)
output = response["outputs"][0]
output1_content = self.dataset_populator.get_history_dataset_content(history_id, dataset=output)
assert output1_content.strip() == "None"

inputs = {
"parameter": None,
}
response = self._run("gx_select_multiple", history_id, inputs, assert_ok=True)
output = response["outputs"][0]
output1_content = self.dataset_populator.get_history_dataset_content(history_id, dataset=output)
assert output1_content.strip() == "None"

inputs = {
"parameter": None,
}
response = self._run("gx_select_multiple_optional", history_id, inputs, assert_ok=True)
output = response["outputs"][0]
output1_content = self.dataset_populator.get_history_dataset_content(history_id, dataset=output)
assert output1_content.strip() == "None"

@skip_without_tool("gx_drill_down_exact")
@skip_without_tool("gx_drill_down_exact_multiple")
@skip_without_tool("gx_drill_down_recurse")
Expand Down
Loading

0 comments on commit 287b889

Please sign in to comment.