diff --git a/lib/galaxy/tool_util/parser/interface.py b/lib/galaxy/tool_util/parser/interface.py
index a52e39e0200b..aeebd6142dbf 100644
--- a/lib/galaxy/tool_util/parser/interface.py
+++ b/lib/galaxy/tool_util/parser/interface.py
@@ -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:
@@ -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.
diff --git a/lib/galaxy/tool_util/parser/parameter_validators.py b/lib/galaxy/tool_util/parser/parameter_validators.py
index 5aa140542c16..2d94c932e500 100644
--- a/lib/galaxy/tool_util/parser/parameter_validators.py
+++ b/lib/galaxy/tool_util/parser/parameter_validators.py
@@ -13,8 +13,8 @@
Field,
)
from typing_extensions import (
- get_args,
Annotated,
+ get_args,
Literal,
)
@@ -23,6 +23,7 @@
Element,
)
+
class ValidationArgument:
doc: str
xml_body: bool
@@ -41,9 +42,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"
@@ -74,18 +73,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)]
@@ -97,9 +99,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):
@@ -109,7 +112,7 @@ class InRangeParameterValidatorModel(ParameterValidatorModel):
exclude_min: bool = False
exclude_max: bool = False
negate: Negate = NEGATE_DEFAULT
-
+
class LengthParameterValidatorModel(ParameterValidatorModel):
type: Literal["length"]
@@ -265,7 +268,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(
diff --git a/lib/galaxy/tool_util/parser/xml.py b/lib/galaxy/tool_util/parser/xml.py
index c5f39568447b..b93eaa730d23 100644
--- a/lib/galaxy/tool_util/parser/xml.py
+++ b/lib/galaxy/tool_util/parser/xml.py
@@ -72,6 +72,10 @@
ToolOutputCollection,
ToolOutputCollectionStructure,
)
+from .parameter_validators import (
+ AnyValidatorModel,
+ parse_xml_validators,
+)
from .stdio import (
aggressive_error_checks,
error_on_exit_code,
@@ -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."""
diff --git a/lib/galaxy/tool_util/unittest_utils/sample_data.py b/lib/galaxy/tool_util/unittest_utils/sample_data.py
index d4b6ddb6f027..e9b19283401a 100644
--- a/lib/galaxy/tool_util/unittest_utils/sample_data.py
+++ b/lib/galaxy/tool_util/unittest_utils/sample_data.py
@@ -17,3 +17,56 @@
"""
)
+
+VALID_XML_VALIDATORS = [
+ """""",
+ """""",
+ """""",
+ """value == 7""",
+ """mycoolexpression""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+]
+
+INVALID_XML_VALIDATORS = [
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """""",
+ """"""
+ """"""
+ """""",
+ """""",
+ """""",
+ """""",
+]
diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py
index e79a6c2a1028..27aaa8d97fae 100644
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -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:
@@ -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]
diff --git a/lib/galaxy/tools/parameters/validation.py b/lib/galaxy/tools/parameters/validation.py
index 6334fd95f8b8..ebe253d13da9 100644
--- a/lib/galaxy/tools/parameters/validation.py
+++ b/lib/galaxy/tools/parameters/validation.py
@@ -6,6 +6,10 @@
import json
import logging
import os.path
+from typing import (
+ List,
+ Optional,
+)
import regex
@@ -13,6 +17,10 @@
model,
util,
)
+from galaxy.tool_util.parser.parameter_validators import (
+ AnyValidatorModel,
+ parse_xml_validators as parse_xml_validators_models,
+)
log = logging.getLogger(__name__)
@@ -24,27 +32,7 @@ class Validator(abc.ABC):
requires_dataset_metadata = False
- @classmethod
- def from_element(cls, param, elem):
- """
- Initialize the appropriate Validator class
-
- example call `validation.Validator.from_element(ToolParameter_object, Validator_object)`
-
- needs to be implemented in the subclasses and should return the
- corresponding Validator object by a call to `cls( ... )` which calls the
- `__init__` method of the corresponding validator
-
- param cls the Validator class
- param param the element to be evaluated (which contains the validator)
- param elem the validator element
- return an object of a Validator subclass that corresponds to the type attribute of the validator element
- """
- _type = elem.get("type")
- assert _type is not None, "Required 'type' attribute missing from validator"
- return validator_types[_type].from_element(param, elem)
-
- def __init__(self, message, negate=False):
+ def __init__(self, message: Optional[str], negate: bool = False):
self.message = message
self.negate = util.asbool(negate)
super().__init__()
@@ -84,13 +72,9 @@ class RegexValidator(Validator):
Validator that evaluates a regular expression
"""
- @classmethod
- def from_element(cls, param, elem):
- return cls(elem.get("message"), elem.text, elem.get("negate", "false"))
-
- def __init__(self, message, expression, negate):
+ def __init__(self, message: Optional[str], expression: str, negate: bool):
if message is None:
- message = f"Value '%s' does {'not ' if negate == 'false' else ''}match regular expression '{expression.replace('%', '%%')}'"
+ message = f"Value '%s' does {'not ' if not negate else ''}match regular expression '{expression.replace('%', '%%')}'"
super().__init__(message, negate)
# Compile later. RE objects used to not be thread safe. Not sure about
# the sre module.
@@ -109,13 +93,9 @@ class ExpressionValidator(Validator):
Validator that evaluates a python expression using the value
"""
- @classmethod
- def from_element(cls, param, elem):
- return cls(elem.get("message"), elem.text, elem.get("negate", "false"))
-
def __init__(self, message, expression, negate):
if message is None:
- message = f"Value '%s' does not evaluate to {'True' if negate == 'false' else 'False'} for '{expression}'"
+ message = f"Value '%s' does not evaluate to {'True' if not negate else 'False'} for '{expression}'"
super().__init__(message, negate)
self.expression = expression
# Save compiled expression, code objects are thread safe (right?)
@@ -134,18 +114,15 @@ class InRangeValidator(ExpressionValidator):
Validator that ensures a number is in a specified range
"""
- @classmethod
- def from_element(cls, param, elem):
- return cls(
- elem.get("message"),
- elem.get("min"),
- elem.get("max"),
- elem.get("exclude_min", "false"),
- elem.get("exclude_max", "false"),
- elem.get("negate", "false"),
- )
-
- def __init__(self, message, range_min, range_max, exclude_min=False, exclude_max=False, negate=False):
+ def __init__(
+ self,
+ message: Optional[str] = None,
+ min: Optional[float] = None,
+ max: Optional[float] = None,
+ exclude_min: bool = False,
+ exclude_max: bool = False,
+ negate: bool = False,
+ ):
"""
When the optional exclude_min and exclude_max attributes are set
to true, the range excludes the end points (i.e., min < value < max),
@@ -153,10 +130,10 @@ def __init__(self, message, range_min, range_max, exclude_min=False, exclude_max
(1.e., min <= value <= max). Combinations of exclude_min and exclude_max
values are allowed.
"""
- self.min = range_min if range_min is not None else "-inf"
- self.exclude_min = util.asbool(exclude_min)
- self.max = range_max if range_max is not None else "inf"
- self.exclude_max = util.asbool(exclude_max)
+ self.min = str(min) if min is not None else "-inf"
+ self.exclude_min = exclude_min
+ self.max = str(max) if max is not None else "inf"
+ self.exclude_max = exclude_max
assert float(self.min) <= float(self.max), "min must be less than or equal to max"
# Remove unneeded 0s and decimal from floats to make message pretty.
op1 = "<="
@@ -167,7 +144,7 @@ def __init__(self, message, range_min, range_max, exclude_min=False, exclude_max
op2 = "<"
expression = f"float('{self.min}') {op1} float(value) {op2} float('{self.max}')"
if message is None:
- message = f"Value ('%s') must {'not ' if negate == 'true' else ''}fulfill {expression}"
+ message = f"Value ('%s') must {'not ' if negate else ''}fulfill {expression}"
super().__init__(message, expression, negate)
@@ -176,14 +153,10 @@ class LengthValidator(InRangeValidator):
Validator that ensures the length of the provided string (value) is in a specific range
"""
- @classmethod
- def from_element(cls, param, elem):
- return cls(elem.get("message"), elem.get("min"), elem.get("max"), elem.get("negate", "false"))
-
- def __init__(self, message, length_min, length_max, negate):
+ def __init__(self, message: Optional[str], min: float, max: float, negate: bool):
if message is None:
- message = f"Must {'not ' if negate == 'true' else ''}have length of at least {length_min} and at most {length_max}"
- super().__init__(message, range_min=length_min, range_max=length_max, negate=negate)
+ message = f"Must {'not ' if negate else ''}have length of at least {min} and at most {max}"
+ super().__init__(message, min=min, max=max, negate=negate)
def validate(self, value, trans=None):
if value is None:
@@ -196,16 +169,13 @@ class DatasetOkValidator(Validator):
Validator that checks if a dataset is in an 'ok' state
"""
- @classmethod
- def from_element(cls, param, elem):
- negate = elem.get("negate", "false")
- message = elem.get("message")
+ def __init__(self, message: Optional[str], negate: bool = False):
if message is None:
- if negate == "false":
+ if not negate:
message = "The selected dataset is still being generated, select another dataset or wait until it is completed"
else:
message = "The selected dataset must not be in state OK"
- return cls(message, negate)
+ return super().__init__(message, negate=negate)
def validate(self, value, trans=None):
if value:
@@ -217,13 +187,10 @@ class DatasetEmptyValidator(Validator):
Validator that checks if a dataset has a positive file size.
"""
- @classmethod
- def from_element(cls, param, elem):
- message = elem.get("message")
- negate = elem.get("negate", "false")
- if not message:
- message = f"The selected dataset is {'non-' if negate == 'true' else ''}empty, this tool expects {'non-' if negate == 'false' else ''}empty files."
- return cls(message, negate)
+ def __init__(self, message: Optional[str], negate: bool = False):
+ if message is None:
+ message = f"The selected dataset is {'non-' if negate else ''}empty, this tool expects {'non-' if not negate else ''}empty files."
+ return super().__init__(message, negate=negate)
def validate(self, value, trans=None):
if value:
@@ -235,13 +202,10 @@ class DatasetExtraFilesPathEmptyValidator(Validator):
Validator that checks if a dataset's extra_files_path exists and is not empty.
"""
- @classmethod
- def from_element(cls, param, elem):
- message = elem.get("message")
- negate = elem.get("negate", "false")
- if not message:
- message = f"The selected dataset's extra_files_path directory is {'non-' if negate == 'true' else ''}empty or does {'not ' if negate == 'false' else ''}exist, this tool expects {'non-' if negate == 'false' else ''}empty extra_files_path directories associated with the selected input."
- return cls(message, negate)
+ def __init__(self, message: Optional[str], negate: bool = False):
+ if message is None:
+ message = f"The selected dataset's extra_files_path directory is {'non-' if negate else ''}empty or does {'not ' if not negate else ''}exist, this tool expects {'non-' if not negate else ''}empty extra_files_path directories associated with the selected input."
+ return super().__init__(message, negate=negate)
def validate(self, value, trans=None):
if value:
@@ -255,25 +219,26 @@ class MetadataValidator(Validator):
requires_dataset_metadata = True
- @classmethod
- def from_element(cls, param, elem):
- message = elem.get("message")
- return cls(
- message=message, check=elem.get("check", ""), skip=elem.get("skip", ""), negate=elem.get("negate", "false")
- )
-
- def __init__(self, message=None, check="", skip="", negate="false"):
+ def __init__(
+ self,
+ message: Optional[str] = None,
+ check: Optional[List[str]] = None,
+ skip: Optional[List[str]] = None,
+ negate: bool = False,
+ ):
+ print(check)
+ print(skip)
if not message:
- if not util.asbool(negate):
+ if not negate:
message = "Metadata '%s' missing, click the pencil icon in the history item to edit / save the metadata attributes"
else:
- if check != "":
- message = f"At least one of the checked metadata '{check}' is set, click the pencil icon in the history item to edit / save the metadata attributes"
- elif skip != "":
- message = f"At least one of the non skipped metadata '{skip}' is set, click the pencil icon in the history item to edit / save the metadata attributes"
+ if check:
+ message = f"At least one of the checked metadata '{",".join(check)}' is set, click the pencil icon in the history item to edit / save the metadata attributes"
+ elif skip:
+ message = f"At least one of the non skipped metadata '{",".join(skip)}' is set, click the pencil icon in the history item to edit / save the metadata attributes"
super().__init__(message, negate)
- self.check = check.split(",") if check else None
- self.skip = skip.split(",") if skip else None
+ self.check = check
+ self.skip = skip
def validate(self, value, trans=None):
if value:
@@ -302,16 +267,6 @@ def __init__(self, metadata_name=None, value=None, message=None, negate="false")
self.metadata_name = metadata_name
self.value = value
- @classmethod
- def from_element(cls, param, elem):
- value = elem.get("value", None) or json.loads(elem.get("value_json", "null"))
- return cls(
- metadata_name=elem.get("metadata_name", None),
- value=value,
- message=elem.get("message", None),
- negate=elem.get("negate", "false"),
- )
-
def validate(self, value, trans=None):
if value:
metadata_value = getattr(value.metadata, self.metadata_name)
@@ -325,13 +280,10 @@ class UnspecifiedBuildValidator(Validator):
requires_dataset_metadata = True
- @classmethod
- def from_element(cls, param, elem):
- message = elem.get("message")
- negate = elem.get("negate", "false")
- if not message:
- message = f"{'Unspecified' if negate == 'false' else 'Specified'} genome build, click the pencil icon in the history item to {'set' if negate == 'false' else 'remove'} the genome build"
- return cls(message, negate)
+ def __init__(self, message: Optional[str], negate: bool = False):
+ if message is None:
+ message = f"{'Unspecified' if not negate else 'Specified'} genome build, click the pencil icon in the history item to {'set' if not negate else 'remove'} the genome build"
+ return super().__init__(message, negate=negate)
def validate(self, value, trans=None):
# if value is None, we cannot validate
@@ -348,13 +300,10 @@ class NoOptionsValidator(Validator):
Validator that checks for empty select list
"""
- @classmethod
- def from_element(cls, param, elem):
- message = elem.get("message")
- negate = elem.get("negate", "false")
- if not message:
- message = f"{'No options' if negate == 'false' else 'Options'} available for selection"
- return cls(message, negate)
+ def __init__(self, message: Optional[str], negate: bool = False):
+ if message is None:
+ message = f"{'No options' if not negate else 'Options'} available for selection"
+ return super().__init__(message, negate=negate)
def validate(self, value, trans=None):
super().validate(value is not None)
@@ -365,16 +314,13 @@ class EmptyTextfieldValidator(Validator):
Validator that checks for empty text field
"""
- @classmethod
- def from_element(cls, param, elem):
- message = elem.get("message")
- negate = elem.get("negate", "false")
- if not message:
- if negate == "false":
- message = elem.get("message", "Field requires a value")
+ def __init__(self, message: Optional[str], negate: bool = False):
+ if message is None:
+ if not negate:
+ message = "Field requires a value"
else:
- message = elem.get("message", "Field must not set a value")
- return cls(message, negate)
+ message = "Field must not set a value"
+ return super().__init__(message, negate=negate)
def validate(self, value, trans=None):
super().validate(value != "")
@@ -391,34 +337,18 @@ class MetadataInFileColumnValidator(Validator):
requires_dataset_metadata = True
- @classmethod
- def from_element(cls, param, elem):
- filename = elem.get("filename")
- assert filename, f"Required 'filename' attribute missing from {elem.get('type')} validator."
- filename = f"{param.tool.app.config.tool_data_path}/{filename.strip()}"
- assert os.path.exists(filename), f"File {filename} specified by the 'filename' attribute not found"
- metadata_name = elem.get("metadata_name")
- assert metadata_name, f"Required 'metadata_name' attribute missing from {elem.get('type')} validator."
- metadata_name = metadata_name.strip()
- metadata_column = int(elem.get("metadata_column", 0))
- split = elem.get("split", "\t")
- message = elem.get("message", f"Value for metadata {metadata_name} was not found in {filename}.")
- line_startswith = elem.get("line_startswith")
- if line_startswith:
- line_startswith = line_startswith.strip()
- negate = elem.get("negate", "false")
- return cls(filename, metadata_name, metadata_column, message, line_startswith, split, negate)
-
def __init__(
self,
- filename,
- metadata_name,
- metadata_column,
- message="Value for metadata not found.",
- line_startswith=None,
- split="\t",
- negate="false",
+ filename: str,
+ metadata_name: str,
+ metadata_column: int,
+ message: Optional[str] = None,
+ line_startswith: Optional[str] = None,
+ split: str = "\t",
+ negate: bool = False,
):
+ if message is None:
+ message = "Value for metadata not found."
super().__init__(message, negate)
self.metadata_name = metadata_name
self.valid_values = set()
@@ -445,28 +375,16 @@ class ValueInDataTableColumnValidator(Validator):
note: this is covered in a framework test (validation_value_in_datatable)
"""
- @classmethod
- def from_element(cls, param, elem):
- table_name = elem.get("table_name")
- assert table_name, f"Required 'table_name' attribute missing from {elem.get('type')} validator."
- tool_data_table = param.tool.app.tool_data_tables[table_name]
- column = elem.get("metadata_column", 0)
- try:
- column = int(column)
- except ValueError:
- pass
- message = elem.get("message", f"Value was not found in {table_name}.")
- negate = elem.get("negate", "false")
- return cls(tool_data_table, column, message, negate)
-
- def __init__(self, tool_data_table, column, message="Value not found.", negate="false"):
+ def __init__(
+ self, tool_data_table, metadata_column: str, message: Optional[str] = "Value not found.", negate: bool = False
+ ):
super().__init__(message, negate)
self.valid_values = []
self._data_table_content_version = None
self._tool_data_table = tool_data_table
- if isinstance(column, str):
- column = tool_data_table.columns[column]
- self._column = column
+ if isinstance(metadata_column, str):
+ metadata_column = tool_data_table.columns[metadata_column]
+ self._column = metadata_column
self._load_values()
def _load_values(self):
@@ -517,26 +435,13 @@ class MetadataInDataTableColumnValidator(ValueInDataTableColumnValidator):
requires_dataset_metadata = True
- @classmethod
- def from_element(cls, param, elem):
- table_name = elem.get("table_name")
- assert table_name, f"Required 'table_name' attribute missing from {elem.get('type')} validator."
- tool_data_table = param.tool.app.tool_data_tables[table_name]
- metadata_name = elem.get("metadata_name")
- assert metadata_name, f"Required 'metadata_name' attribute missing from {elem.get('type')} validator."
- metadata_name = metadata_name.strip()
- # TODO rename to column?
- metadata_column = elem.get("metadata_column", 0)
- try:
- metadata_column = int(metadata_column)
- except ValueError:
- pass
- message = elem.get("message", f"Value for metadata {metadata_name} was not found in {table_name}.")
- negate = elem.get("negate", "false")
- return cls(tool_data_table, metadata_name, metadata_column, message, negate)
-
def __init__(
- self, tool_data_table, metadata_name, metadata_column, message="Value for metadata not found.", negate="false"
+ self,
+ tool_data_table,
+ metadata_name: str,
+ metadata_column: int,
+ message: Optional[str] = "Value for metadata not found.",
+ negate: bool = False,
):
super().__init__(tool_data_table, metadata_column, message, negate)
self.metadata_name = metadata_name
@@ -558,7 +463,12 @@ class MetadataNotInDataTableColumnValidator(MetadataInDataTableColumnValidator):
requires_dataset_metadata = True
def __init__(
- self, tool_data_table, metadata_name, metadata_column, message="Value for metadata not found.", negate="false"
+ self,
+ tool_data_table,
+ metadata_name: str,
+ metadata_column: int,
+ message: Optional[str] = "Value for metadata not found.",
+ negate: bool = False,
):
super().__init__(tool_data_table, metadata_name, metadata_column, message, negate)
@@ -580,26 +490,18 @@ class MetadataInRangeValidator(InRangeValidator):
requires_dataset_metadata = True
- @classmethod
- def from_element(cls, param, elem):
- metadata_name = elem.get("metadata_name")
- assert metadata_name, f"Required 'metadata_name' attribute missing from {elem.get('type')} validator."
- metadata_name = metadata_name.strip()
- ret = cls(
- metadata_name,
- elem.get("message"),
- elem.get("min"),
- elem.get("max"),
- elem.get("exclude_min", "false"),
- elem.get("exclude_max", "false"),
- elem.get("negate", "false"),
- )
- ret.message = "Metadata: " + ret.message
- return ret
-
- def __init__(self, metadata_name, message, range_min, range_max, exclude_min, exclude_max, negate):
+ def __init__(
+ self,
+ metadata_name: str,
+ message: Optional[str] = None,
+ min: Optional[float] = None,
+ max: Optional[float] = None,
+ exclude_min: bool = False,
+ exclude_max: bool = False,
+ negate: bool = False,
+ ):
self.metadata_name = metadata_name
- super().__init__(message, range_min, range_max, exclude_min, exclude_max, negate)
+ super().__init__(message, min, max, exclude_min, exclude_max, negate)
def validate(self, value, trans=None):
if value:
@@ -638,3 +540,24 @@ def validate(self, value, trans=None):
deprecated_validator_types = dict(dataset_metadata_in_file=MetadataInFileColumnValidator)
validator_types.update(deprecated_validator_types)
+
+
+def parse_xml_validators(app, xml_el: util.Element) -> List[Validator]:
+ return to_validators(app, parse_xml_validators_models(xml_el))
+
+
+def to_validators(app, validator_models: List[AnyValidatorModel]) -> Validator:
+ validators = []
+ for validator_model in validator_models:
+ validators.append(_to_validator(app, validator_model))
+ return validators
+
+
+def _to_validator(app, validator_model: AnyValidatorModel) -> Validator:
+ as_dict = validator_model.model_dump()
+ validator_type = as_dict.pop("type")
+ if "table_name" in as_dict:
+ table_name = as_dict.pop("table_name")
+ tool_data_table = app.tool_data_tables[table_name]
+ as_dict["tool_data_table"] = tool_data_table
+ return validator_types[validator_type](**as_dict)
diff --git a/test/unit/app/tools/test_validation_parsing.py b/test/unit/app/tools/test_validation_parsing.py
new file mode 100644
index 000000000000..40848c2cb95e
--- /dev/null
+++ b/test/unit/app/tools/test_validation_parsing.py
@@ -0,0 +1,39 @@
+from galaxy.tool_util.unittest_utils.sample_data import (
+ INVALID_XML_VALIDATORS,
+ VALID_XML_VALIDATORS,
+)
+from galaxy.tools.parameters.validation import parse_xml_validators
+from galaxy.util import XML
+
+
+class MockApp:
+
+ @property
+ def tool_data_tables(self):
+ return {"mycooltable": MockTable()}
+
+
+class MockTable:
+
+ def get_version_fields(self):
+ return (1, [])
+
+
+def test_xml_validation_valid():
+ for xml_validator in VALID_XML_VALIDATORS:
+ _validate_xml_str(xml_validator)
+
+
+def test_xml_validation_invalid():
+ for xml_validator in INVALID_XML_VALIDATORS:
+ exc: Optional[Exception] = None
+ try:
+ _validate_xml_str(xml_validator)
+ except ValueError as e:
+ exc = e
+ assert exc is not None, f"{xml_validator} - validated when it wasn't expected to"
+
+
+def _validate_xml_str(xml_str: str):
+ xml_el = XML(f"{xml_str}")
+ parse_xml_validators(MockApp(), xml_el)
diff --git a/test/unit/tool_util/test_parameter_validator_models.py b/test/unit/tool_util/test_parameter_validator_models.py
index 3802a4235d11..bceba4bdf7e2 100644
--- a/test/unit/tool_util/test_parameter_validator_models.py
+++ b/test/unit/tool_util/test_parameter_validator_models.py
@@ -1,68 +1,18 @@
from galaxy.tool_util.parser.parameter_validators import parse_xml_validators
+from galaxy.tool_util.unittest_utils.sample_data import (
+ INVALID_XML_VALIDATORS,
+ VALID_XML_VALIDATORS,
+)
from galaxy.util import XML
-valid_xml_validators = [
- """""",
- """""",
- """""",
- """value == 7""",
- """mycoolexpression""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
-]
-
-invalid_xml_validators = [
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """""",
- """"""
- """"""
- """""",
- """""",
- """""",
- """""",
-]
-
-
def test_xml_validation_valid():
- for xml_validator in valid_xml_validators:
+ for xml_validator in VALID_XML_VALIDATORS:
_validate_xml_str(xml_validator)
def test_xml_validation_invalid():
- for xml_validator in invalid_xml_validators:
+ for xml_validator in INVALID_XML_VALIDATORS:
exc: Optional[Exception] = None
try:
_validate_xml_str(xml_validator)