Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Another round of parameter model improvements. #18673

Merged
merged 14 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading