Skip to content

Commit

Permalink
Hook up validators models to validator runtime.
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Oct 3, 2024
1 parent 21b4e5c commit 49ec265
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 283 deletions.
3 changes: 2 additions & 1 deletion lib/galaxy/tool_util/parser/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from galaxy.util import Element
from galaxy.util.path import safe_walk
from .parameter_validators import AnyValidatorModel
from .util import _parse_name

if TYPE_CHECKING:
Expand Down Expand Up @@ -509,7 +510,7 @@ def parse_sanitizer_elem(self):
"""
return None

def parse_validator_elems(self):
def parse_validators(self) -> List[AnyValidatorModel]:
"""Return an XML description of sanitizers. This is a stop gap
until we can rework galaxy.tools.parameters.validation to not
explicitly depend on XML.
Expand Down
30 changes: 18 additions & 12 deletions lib/galaxy/tool_util/parser/parameter_validators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os.path
from typing import (
Any,
cast,
Expand All @@ -13,8 +14,8 @@
Field,
)
from typing_extensions import (
get_args,
Annotated,
get_args,
Literal,
)

Expand All @@ -23,6 +24,7 @@
Element,
)


class ValidationArgument:
doc: str
xml_body: bool
Expand All @@ -41,9 +43,7 @@ def __init__(

Negate = Annotated[
bool,
ValidationArgument(
"Negates the result of the validator."
),
ValidationArgument("Negates the result of the validator."),
]
NEGATE_DEFAULT = False
SPLIT_DEFAULT = "\t"
Expand Down Expand Up @@ -74,18 +74,21 @@ class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid")



class ParameterValidatorModel(StrictModel):
type: ValidatorType
message: Annotated[Optional[str], ValidationArgument(
"""The error message displayed on the tool form if validation fails. A placeholder string ``%s`` will be repaced by the ``value``"""
)] = None
message: Annotated[
Optional[str],
ValidationArgument(
"""The error message displayed on the tool form if validation fails. A placeholder string ``%s`` will be repaced by the ``value``"""
),
] = None


class ExpressionParameterValidatorModel(ParameterValidatorModel):
"""Check if a one line python expression given expression evaluates to True.
The expression is given is the content of the validator tag."""

type: Literal["expression"]
negate: Negate = NEGATE_DEFAULT
expression: Annotated[str, ValidationArgument("Python expression to validate.", xml_body=True)]
Expand All @@ -97,9 +100,10 @@ class RegexParameterValidatorModel(ParameterValidatorModel):
``$`` at the end of the expression. The expression is given is the content
of the validator tag. Note that for ``selects`` each option is checked
separately."""

type: Literal["regex"]
negate: Negate = NEGATE_DEFAULT
regex: Annotated[str, ValidationArgument("Regular expression to validate against.", xml_body=True)]
expression: Annotated[str, ValidationArgument("Regular expression to validate against.", xml_body=True)]


class InRangeParameterValidatorModel(ParameterValidatorModel):
Expand All @@ -109,7 +113,7 @@ class InRangeParameterValidatorModel(ParameterValidatorModel):
exclude_min: bool = False
exclude_max: bool = False
negate: Negate = NEGATE_DEFAULT


class LengthParameterValidatorModel(ParameterValidatorModel):
type: Literal["length"]
Expand Down Expand Up @@ -265,7 +269,7 @@ def parse_xml_validator(validator_el: Element) -> AnyValidatorModel:
type="regex",
message=_parse_message(validator_el),
negate=_parse_negate(validator_el),
regex=validator_el.text,
expression=validator_el.text,
)
elif validator_type == "in_range":
return InRangeParameterValidatorModel(
Expand Down Expand Up @@ -383,10 +387,12 @@ def parse_xml_validator(validator_el: Element) -> AnyValidatorModel:
negate=_parse_negate(validator_el),
)
elif validator_type == "dataset_metadata_in_file":
filename = validator_el.get("filename")
assert os.path.exists(filename), f"File {filename} specified by the 'filename' attribute not found"
return DatasetMetadataInFileParameterValidatorModel(
type="dataset_metadata_in_file",
message=_parse_message(validator_el),
filename=validator_el.get("filename"),
filename=filename,
metadata_name=validator_el.get("metadata_name"),
metadata_column=_parse_metadata_column(validator_el),
line_startswith=validator_el.get("line_startswith"),
Expand Down
8 changes: 6 additions & 2 deletions lib/galaxy/tool_util/parser/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
ToolOutputCollection,
ToolOutputCollectionStructure,
)
from .parameter_validators import (
AnyValidatorModel,
parse_xml_validators,
)
from .stdio import (
aggressive_error_checks,
error_on_exit_code,
Expand Down Expand Up @@ -1340,8 +1344,8 @@ def parse_help(self):
def parse_sanitizer_elem(self):
return self.input_elem.find("sanitizer")

def parse_validator_elems(self):
return self.input_elem.findall("validator")
def parse_validators(self) -> List[AnyValidatorModel]:
return parse_xml_validators(self.input_elem)

def parse_dynamic_options(self) -> Optional[XmlDynamicOptions]:
"""Return a XmlDynamicOptions to describe dynamic options if options elem is available."""
Expand Down
53 changes: 53 additions & 0 deletions lib/galaxy/tool_util/unittest_utils/sample_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,56 @@
</macros>
"""
)

VALID_XML_VALIDATORS = [
"""<validator type="empty_dataset" />""",
"""<validator type="empty_dataset" message="foobar" />""",
"""<validator type="empty_dataset" message="foobar" negate="true" />""",
"""<validator type="expression">value == 7</validator>""",
"""<validator type="regex">mycoolexpression</validator>""",
"""<validator type="in_range" min="3.1" max="3.8" />""",
"""<validator type="in_range" min="3.1" />""",
"""<validator type="in_range" min="3.1" max="3.8" exclude_min="false" exclude_max="true" />""",
"""<validator type="length" min="3" />""",
"""<validator type="length" max="7" />""",
"""<validator type="length" min="2" max="7" negate="true" />""",
"""<validator type="empty_field" />""",
"""<validator type="empty_extra_files_path" />""",
"""<validator type="no_options" />""",
"""<validator type="unspecified_build" />""",
"""<validator type="dataset_ok_validator" />""",
"""<validator type="metadata" check="foo, bar" />""",
"""<validator type="metadata" skip="name, dbkey" />""",
"""<validator type="dataset_metadata_equal" metadata_name="foobar" value="moocow" />""",
"""<validator type="dataset_metadata_equal" metadata_name="foobar" value_json="null" />""",
"""<validator type="dataset_metadata_in_range" metadata_name="foobar" min="4.5" max="7.8" />""",
"""<validator type="dataset_metadata_in_range" metadata_name="foobar" min="4.5" max="7.8" include_min="true" />""",
"""<validator type="dataset_metadata_in_data_table" metadata_name="foobar" metadata_column="3" table_name="mycooltable" />""",
"""<validator type="dataset_metadata_not_in_data_table" metadata_name="foobar" metadata_column="3" table_name="mycooltable" />""",
"""<validator type="value_in_data_table" metadata_column="3" table_name="mycooltable" />""",
"""<validator type="value_in_data_table" table_name="mycooltable" />""",
"""<validator type="value_not_in_data_table" metadata_column="3" table_name="mycooltable" />""",
"""<validator type="value_not_in_data_table" table_name="mycooltable" />""",
]

INVALID_XML_VALIDATORS = [
"""<validator type="unknown" />""",
"""<validator type="empty_datasetx" />""",
"""<validator type="expression" />""",
"""<validator type="empty_dataset" message="foobar" negate="NOTABOOLVALUE" />""",
"""<validator type="regex" />""",
"""<validator type="in_range" min="3.1" max="3.8" exclude_min="false" exclude_max="foobar" />""",
"""<validator type="in_range" min="notanumber" max="3.8" />""",
"""<validator type="length" min="notanumber" />""",
"""<validator type="length" max="notanumber" />""",
"""<validator type="length" min="2" max="7" negate="notabool" />""",
"""<validator type="dataset_metadata_equal" />""",
"""<validator type="dataset_metadata_equal" metadaata_name="foobar" />""",
"""<validator type="dataset_metadata_equal" metadaata_name="foobar" value_json="undefined" />""",
"""<validator type="dataset_metadata_in_range" metadata_name="foobar" min="4.5" max="7.8" include_min="notabool" />"""
"""<validator type="dataset_metadata_in_range" min="4.5" max="7.8" />"""
"""<validator type="dataset_metadata_in_data_table" metadata_name="foobar" metadata_column="3" />""",
"""<validator type="dataset_metadata_in_data_table" metadata_column="3" table_name="mycooltable" />""",
"""<validator type="dataset_metadata_not_in_data_table" metadata_name="foobar" metadata_column="3" />""",
"""<validator type="dataset_metadata_not_in_data_table" metadata_column="3" table_name="mycooltable" />""",
]
9 changes: 5 additions & 4 deletions lib/galaxy/tools/parameters/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,7 @@ def __init__(self, tool, input_source, context=None):
self.sanitizer = ToolParameterSanitizer.from_element(sanitizer_elem)
else:
self.sanitizer = None
self.validators = []
for elem in input_source.parse_validator_elems():
self.validators.append(validation.Validator.from_element(self, elem))
self.validators = validation.to_validators(tool.app, input_source.parse_validators())

@property
def visible(self) -> bool:
Expand Down Expand Up @@ -2486,7 +2484,10 @@ def from_json(self, value, trans, other_values=None):
rval = value
elif isinstance(value, MutableMapping) and "src" in value and "id" in value:
if value["src"] == "hdca":
rval = cast(HistoryDatasetCollectionAssociation, src_id_to_item(sa_session=trans.sa_session, value=value, security=trans.security))
rval = cast(
HistoryDatasetCollectionAssociation,
src_id_to_item(sa_session=trans.sa_session, value=value, security=trans.security),
)
elif isinstance(value, list):
if len(value) > 0:
value = value[0]
Expand Down
Loading

0 comments on commit 49ec265

Please sign in to comment.