mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-08-30 16:58:03 +08:00
New packages galaxy-tool-util-models, galaxy-tool-shed-schema.
The galaxy-tool-shed-schema package is basically pydantic models for the whole tool shed API. It is used by both the backend code for the new tool shed and the testing code that runs on both the old and new tool shed. The creation of galaxy-tool-util-models package is something I've wanted to do for a while - it should solve some TODOs I've left in the base. I think beyond enabling galaxy-tool-shed-schema package - the other nice thing is that galaxy-schema code can now rely on various tool stuff without requiring dependency on any of the parser or runtime code. This should also fix things for the new record types in #19377. I've introduced a type that can be used tools and is included schema for collection creation. The galaxy-tool-util-models packages can now be dependended on by galaxy-schema and galaxy-tool-util and these two packages do not need to depend on each other - either direction of that dependency would make me uncomfortable. xref https://github.com/galaxyproject/galaxy/actions/runs/13704893929/job/38327692654?pr=19377
This commit is contained in:
@@ -11,7 +11,7 @@ from beaker.cache import CacheManager
|
||||
from beaker.util import parse_cache_config_options
|
||||
|
||||
from galaxy.structured_app import BasicSharedApp
|
||||
from galaxy.tool_util.parser.interface import Citation
|
||||
from galaxy.tool_util_models.tool_source import Citation
|
||||
from galaxy.util import (
|
||||
DEFAULT_SOCKET_TIMEOUT,
|
||||
requests,
|
||||
|
||||
@@ -12,7 +12,7 @@ from packaging.version import Version
|
||||
|
||||
from galaxy.tool_util.lint import Linter
|
||||
from galaxy.tool_util.parameters import validate_test_cases_for_tool_source
|
||||
from galaxy.tool_util.verify.assertion_models import assertion_list
|
||||
from galaxy.tool_util_models.assertions import assertion_list
|
||||
from galaxy.util import asbool
|
||||
from ._util import is_datasource
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
from typing import (
|
||||
Type,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
from galaxy.tool_util_models import ParsedTool
|
||||
from .parameters import input_models_for_tool_source
|
||||
from .parser.interface import (
|
||||
ToolSource,
|
||||
)
|
||||
from .parser.output_objects import from_tool_source
|
||||
|
||||
|
||||
def parse_tool(tool_source: ToolSource) -> ParsedTool:
|
||||
return parse_tool_custom(tool_source, ParsedTool)
|
||||
|
||||
|
||||
P = TypeVar("P", bound=ParsedTool)
|
||||
|
||||
|
||||
def parse_tool_custom(tool_source: ToolSource, model_type: Type[P]) -> P:
|
||||
id = tool_source.parse_id()
|
||||
version = tool_source.parse_version()
|
||||
name = tool_source.parse_name()
|
||||
description = tool_source.parse_description()
|
||||
inputs = input_models_for_tool_source(tool_source).parameters
|
||||
outputs = from_tool_source(tool_source)
|
||||
citations = tool_source.parse_citations()
|
||||
license = tool_source.parse_license()
|
||||
profile = tool_source.parse_profile()
|
||||
edam_operations = tool_source.parse_edam_operations()
|
||||
edam_topics = tool_source.parse_edam_topics()
|
||||
xrefs = tool_source.parse_xrefs()
|
||||
help = tool_source.parse_help()
|
||||
|
||||
return model_type(
|
||||
id=id,
|
||||
version=version,
|
||||
name=name,
|
||||
description=description,
|
||||
profile=profile,
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
license=license,
|
||||
citations=citations,
|
||||
edam_operations=edam_operations,
|
||||
edam_topics=edam_topics,
|
||||
xrefs=xrefs,
|
||||
help=help,
|
||||
)
|
||||
@@ -9,10 +9,8 @@ from typing import (
|
||||
)
|
||||
|
||||
from galaxy.tool_util.biotools import BiotoolsMetadataSource
|
||||
from galaxy.tool_util.parser import (
|
||||
ToolSource,
|
||||
XrefDict,
|
||||
)
|
||||
from galaxy.tool_util.parser import ToolSource
|
||||
from galaxy.tool_util_models.tool_source import XrefDict
|
||||
from galaxy.util.resources import resource_string
|
||||
|
||||
|
||||
|
||||
@@ -1,26 +1,4 @@
|
||||
from .case import (
|
||||
test_case_state,
|
||||
validate_test_cases_for_tool_source,
|
||||
)
|
||||
from .convert import (
|
||||
decode,
|
||||
dereference,
|
||||
encode,
|
||||
encode_test,
|
||||
fill_static_defaults,
|
||||
landing_decode,
|
||||
landing_encode,
|
||||
)
|
||||
from .factory import (
|
||||
from_input_source,
|
||||
input_models_for_pages,
|
||||
input_models_for_tool_source,
|
||||
input_models_from_json,
|
||||
ParameterDefinitionError,
|
||||
tool_parameter_bundle_from_json,
|
||||
)
|
||||
from .json import to_json_schema_string
|
||||
from .models import (
|
||||
from galaxy.tool_util_models.parameters import (
|
||||
BooleanParameterModel,
|
||||
ColorParameterModel,
|
||||
ConditionalParameterModel,
|
||||
@@ -53,6 +31,30 @@ from .models import (
|
||||
ToolParameterBundleModel,
|
||||
ToolParameterModel,
|
||||
ToolParameterT,
|
||||
)
|
||||
from .case import (
|
||||
test_case_state,
|
||||
validate_test_cases_for_tool_source,
|
||||
)
|
||||
from .convert import (
|
||||
decode,
|
||||
dereference,
|
||||
encode,
|
||||
encode_test,
|
||||
fill_static_defaults,
|
||||
landing_decode,
|
||||
landing_encode,
|
||||
)
|
||||
from .factory import (
|
||||
from_input_source,
|
||||
input_models_for_pages,
|
||||
input_models_for_tool_source,
|
||||
input_models_from_json,
|
||||
ParameterDefinitionError,
|
||||
tool_parameter_bundle_from_json,
|
||||
)
|
||||
from .json import to_json_schema_string
|
||||
from .model_validation import (
|
||||
validate_against_model,
|
||||
validate_internal_job,
|
||||
validate_internal_landing_request,
|
||||
|
||||
@@ -21,9 +21,7 @@ from galaxy.tool_util.parser.interface import (
|
||||
XmlTestCollectionDefDict,
|
||||
)
|
||||
from galaxy.tool_util.parser.util import multiple_select_value_split
|
||||
from galaxy.util import asbool
|
||||
from .factory import input_models_for_tool_source
|
||||
from .models import (
|
||||
from galaxy.tool_util_models.parameters import (
|
||||
BooleanParameterModel,
|
||||
ConditionalParameterModel,
|
||||
ConditionalWhen,
|
||||
@@ -38,6 +36,8 @@ from .models import (
|
||||
SectionParameterModel,
|
||||
ToolParameterT,
|
||||
)
|
||||
from galaxy.util import asbool
|
||||
from .factory import input_models_for_tool_source
|
||||
from .state import TestCaseToolState
|
||||
from .visitor import (
|
||||
flat_state_path,
|
||||
|
||||
@@ -11,11 +11,7 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from galaxy.tool_util.parser.interface import (
|
||||
JsonTestCollectionDefDict,
|
||||
JsonTestDatasetDefDict,
|
||||
)
|
||||
from .models import (
|
||||
from galaxy.tool_util_models.parameters import (
|
||||
ConditionalParameterModel,
|
||||
ConditionalWhen,
|
||||
DataCollectionRequest,
|
||||
@@ -29,6 +25,10 @@ from .models import (
|
||||
ToolParameterBundle,
|
||||
ToolParameterT,
|
||||
)
|
||||
from galaxy.tool_util_models.tool_source import (
|
||||
JsonTestCollectionDefDict,
|
||||
JsonTestDatasetDefDict,
|
||||
)
|
||||
from .state import (
|
||||
JobInternalToolState,
|
||||
LandingRequestInternalToolState,
|
||||
|
||||
@@ -14,16 +14,13 @@ from galaxy.tool_util.parser.interface import (
|
||||
PagesSource,
|
||||
ToolSource,
|
||||
)
|
||||
from galaxy.tool_util.parser.parameter_validators import (
|
||||
static_validators,
|
||||
)
|
||||
from galaxy.tool_util.parser.parameter_validators import static_validators
|
||||
from galaxy.tool_util.parser.util import (
|
||||
multiple_select_value_split,
|
||||
parse_profile_version,
|
||||
text_input_is_optional,
|
||||
)
|
||||
from galaxy.util import string_as_bool
|
||||
from .models import (
|
||||
from galaxy.tool_util_models.parameters import (
|
||||
BaseUrlParameterModel,
|
||||
BooleanParameterModel,
|
||||
ColorParameterModel,
|
||||
@@ -61,6 +58,7 @@ from .models import (
|
||||
ToolParameterBundleModel,
|
||||
ToolParameterT,
|
||||
)
|
||||
from galaxy.util import string_as_bool
|
||||
|
||||
|
||||
class ParameterDefinitionError(Exception):
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
Optional,
|
||||
Type,
|
||||
)
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ValidationError,
|
||||
)
|
||||
from typing_extensions import (
|
||||
Protocol,
|
||||
)
|
||||
|
||||
from galaxy.exceptions import RequestParameterInvalidException
|
||||
from galaxy.tool_util_models.parameters import (
|
||||
create_field_model,
|
||||
DEFAULT_MODEL_NAME,
|
||||
RawStateDict,
|
||||
StateRepresentationT,
|
||||
ToolParameterBundle,
|
||||
)
|
||||
|
||||
|
||||
def validate_against_model(pydantic_model: Type[BaseModel], parameter_state: Dict[str, Any]) -> None:
|
||||
try:
|
||||
pydantic_model(**parameter_state)
|
||||
except ValidationError as e:
|
||||
# TODO: Improve this or maybe add a handler for this in the FastAPI exception
|
||||
# handler.
|
||||
raise RequestParameterInvalidException(str(e))
|
||||
|
||||
|
||||
class ValidationFunctionT(Protocol):
|
||||
|
||||
def __call__(self, tool: ToolParameterBundle, request: RawStateDict, name: Optional[str] = None) -> None: ...
|
||||
|
||||
|
||||
def validate_model_type_factory(state_representation: StateRepresentationT) -> ValidationFunctionT:
|
||||
|
||||
def validate_request(tool: ToolParameterBundle, request: Dict[str, Any], name: Optional[str] = None) -> None:
|
||||
name = name or DEFAULT_MODEL_NAME
|
||||
pydantic_model = create_field_model(tool.parameters, name=name, state_representation=state_representation)
|
||||
validate_against_model(pydantic_model, request)
|
||||
|
||||
return validate_request
|
||||
|
||||
|
||||
validate_request = validate_model_type_factory("request")
|
||||
validate_internal_request = validate_model_type_factory("request_internal")
|
||||
validate_internal_request_dereferenced = validate_model_type_factory("request_internal_dereferenced")
|
||||
validate_landing_request = validate_model_type_factory("landing_request")
|
||||
validate_internal_landing_request = validate_model_type_factory("landing_request_internal")
|
||||
validate_internal_job = validate_model_type_factory("job_internal")
|
||||
validate_test_case = validate_model_type_factory("test_case_xml")
|
||||
validate_workflow_step = validate_model_type_factory("workflow_step")
|
||||
validate_workflow_step_linked = validate_model_type_factory("workflow_step_linked")
|
||||
@@ -14,7 +14,7 @@ from typing import (
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Literal
|
||||
|
||||
from .models import (
|
||||
from galaxy.tool_util_models.parameters import (
|
||||
create_job_internal_model,
|
||||
create_landing_request_internal_model,
|
||||
create_landing_request_model,
|
||||
@@ -28,6 +28,8 @@ from .models import (
|
||||
ToolParameterBundle,
|
||||
ToolParameterBundleModel,
|
||||
ToolParameterT,
|
||||
)
|
||||
from .model_validation import (
|
||||
validate_against_model,
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import (
|
||||
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from .models import (
|
||||
from galaxy.tool_util_models.parameters import (
|
||||
ConditionalParameterModel,
|
||||
ConditionalWhen,
|
||||
simple_input_models,
|
||||
|
||||
@@ -8,7 +8,6 @@ from .factory import (
|
||||
from .interface import (
|
||||
RequiredFiles,
|
||||
ToolSource,
|
||||
XrefDict,
|
||||
)
|
||||
from .output_objects import ToolOutputCollectionPart
|
||||
|
||||
@@ -19,5 +18,4 @@ __all__ = (
|
||||
"RequiredFiles",
|
||||
"ToolOutputCollectionPart",
|
||||
"ToolSource",
|
||||
"XrefDict",
|
||||
)
|
||||
|
||||
@@ -10,8 +10,8 @@ import packaging.version
|
||||
|
||||
from galaxy.tool_util.cwl.parser import tool_proxy
|
||||
from galaxy.tool_util.deps import requirements
|
||||
from galaxy.tool_util_models.tool_source import HelpContent
|
||||
from .interface import (
|
||||
HelpContent,
|
||||
PageSource,
|
||||
PagesSource,
|
||||
ToolSource,
|
||||
|
||||
@@ -5,7 +5,6 @@ from abc import (
|
||||
ABCMeta,
|
||||
abstractmethod,
|
||||
)
|
||||
from enum import Enum
|
||||
from os.path import join
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -19,16 +18,30 @@ from typing import (
|
||||
)
|
||||
|
||||
import packaging.version
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import (
|
||||
Literal,
|
||||
NotRequired,
|
||||
TypedDict,
|
||||
)
|
||||
|
||||
from galaxy.tool_util_models.parameter_validators import (
|
||||
AnyValidatorModel,
|
||||
)
|
||||
from galaxy.tool_util_models.tool_source import (
|
||||
BaseJsonTestCollectionDefCollectionElementDict,
|
||||
Citation,
|
||||
DrillDownOptionsDict,
|
||||
HelpContent,
|
||||
JsonTestCollectionDefCollectionElementDict,
|
||||
JsonTestCollectionDefDatasetElementDict,
|
||||
JsonTestCollectionDefDict,
|
||||
JsonTestCollectionDefElementDict,
|
||||
JsonTestDatasetDefDict,
|
||||
OutputCompareType,
|
||||
XrefDict,
|
||||
)
|
||||
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:
|
||||
@@ -57,15 +70,6 @@ AssertionList = Optional[List[AssertionDict]]
|
||||
XmlInt = Union[str, int]
|
||||
|
||||
|
||||
class OutputCompareType(str, Enum):
|
||||
diff = "diff"
|
||||
re_match = "re_match"
|
||||
sim_size = "sim_size"
|
||||
re_match_multiline = "re_match_multiline"
|
||||
contains = "contains"
|
||||
image_diff = "image_diff"
|
||||
|
||||
|
||||
class ToolSourceTestOutputAttributes(TypedDict):
|
||||
object: NotRequired[Optional[Any]]
|
||||
compare: OutputCompareType
|
||||
@@ -129,21 +133,6 @@ class ToolSourceTests(TypedDict):
|
||||
tests: List[ToolSourceTest]
|
||||
|
||||
|
||||
class XrefDict(TypedDict):
|
||||
value: str
|
||||
reftype: str
|
||||
|
||||
|
||||
class Citation(BaseModel):
|
||||
type: str
|
||||
content: str
|
||||
|
||||
|
||||
class HelpContent(BaseModel):
|
||||
format: Literal["restructuredtext", "plain_text", "markdown"]
|
||||
content: str
|
||||
|
||||
|
||||
class ToolSource(metaclass=ABCMeta):
|
||||
"""This interface represents an abstract source to parse tool
|
||||
information from.
|
||||
@@ -612,65 +601,6 @@ class XmlTestCollectionDefDict(TypedDict):
|
||||
name: str
|
||||
|
||||
|
||||
JsonTestDatasetDefDict = TypedDict(
|
||||
"JsonTestDatasetDefDict",
|
||||
{
|
||||
"class": Literal["File"],
|
||||
"path": NotRequired[Optional[str]],
|
||||
"location": NotRequired[Optional[str]],
|
||||
"name": NotRequired[Optional[str]],
|
||||
"dbkey": NotRequired[Optional[str]],
|
||||
"filetype": NotRequired[Optional[str]],
|
||||
"composite_data": NotRequired[Optional[List[str]]],
|
||||
"tags": NotRequired[Optional[List[str]]],
|
||||
},
|
||||
)
|
||||
|
||||
JsonTestCollectionDefElementDict = Union[
|
||||
"JsonTestCollectionDefDatasetElementDict", "JsonTestCollectionDefCollectionElementDict"
|
||||
]
|
||||
JsonTestCollectionDefDatasetElementDict = TypedDict(
|
||||
"JsonTestCollectionDefDatasetElementDict",
|
||||
{
|
||||
"identifier": str,
|
||||
"class": Literal["File"],
|
||||
"path": NotRequired[Optional[str]],
|
||||
"location": NotRequired[Optional[str]],
|
||||
"name": NotRequired[Optional[str]],
|
||||
"dbkey": NotRequired[Optional[str]],
|
||||
"filetype": NotRequired[Optional[str]],
|
||||
"composite_data": NotRequired[Optional[List[str]]],
|
||||
"tags": NotRequired[Optional[List[str]]],
|
||||
},
|
||||
)
|
||||
BaseJsonTestCollectionDefCollectionElementDict = TypedDict(
|
||||
"BaseJsonTestCollectionDefCollectionElementDict",
|
||||
{
|
||||
"class": Literal["Collection"],
|
||||
"collection_type": str,
|
||||
"elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]],
|
||||
},
|
||||
)
|
||||
JsonTestCollectionDefCollectionElementDict = TypedDict(
|
||||
"JsonTestCollectionDefCollectionElementDict",
|
||||
{
|
||||
"identifier": str,
|
||||
"class": Literal["Collection"],
|
||||
"collection_type": str,
|
||||
"elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]],
|
||||
},
|
||||
)
|
||||
JsonTestCollectionDefDict = TypedDict(
|
||||
"JsonTestCollectionDefDict",
|
||||
{
|
||||
"class": Literal["Collection"],
|
||||
"collection_type": str,
|
||||
"elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]],
|
||||
"name": NotRequired[Optional[str]],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def xml_data_input_to_json(xml_input: ToolSourceTestInput) -> Optional["JsonTestDatasetDefDict"]:
|
||||
attributes = xml_input["attributes"]
|
||||
value = xml_input["value"]
|
||||
@@ -913,10 +843,3 @@ class TestCollectionOutputDef:
|
||||
|
||||
def to_dict(self):
|
||||
return dict(name=self.name, attributes=self.attrib, element_tests=self.element_tests, element_count=self.count)
|
||||
|
||||
|
||||
class DrillDownOptionsDict(TypedDict):
|
||||
name: Optional[str]
|
||||
value: str
|
||||
options: List["DrillDownOptionsDict"]
|
||||
selected: bool
|
||||
|
||||
@@ -8,8 +8,7 @@ from typing import (
|
||||
Optional,
|
||||
)
|
||||
|
||||
from galaxy.util import asbool
|
||||
from .output_models import (
|
||||
from galaxy.tool_util_models.tool_outputs import (
|
||||
DatasetCollectionDescriptionT,
|
||||
DiscoverViaT,
|
||||
FilePatternDatasetCollectionDescription as FilePatternDatasetCollectionDescriptionModel,
|
||||
@@ -17,6 +16,7 @@ from .output_models import (
|
||||
SortKeyT,
|
||||
ToolProvidedMetadataDatasetCollection as ToolProvidedMetadataDatasetCollectionModel,
|
||||
)
|
||||
from galaxy.util import asbool
|
||||
from .util import is_dict
|
||||
|
||||
DEFAULT_EXTRA_FILENAME_PATTERN = (
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import (
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Type,
|
||||
TYPE_CHECKING,
|
||||
Union,
|
||||
@@ -11,6 +12,16 @@ from typing import (
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from galaxy.tool_util_models.tool_outputs import (
|
||||
ToolOutputBoolean as ToolOutputBooleanModel,
|
||||
ToolOutputCollection as ToolOutputCollectionModel,
|
||||
ToolOutputCollectionStructure as ToolOutputCollectionStructureModel,
|
||||
ToolOutputDataset as ToolOutputDataModel,
|
||||
ToolOutputFloat as ToolOutputFloatModel,
|
||||
ToolOutputInteger as ToolOutputIntegerModel,
|
||||
ToolOutputT as ToolOutputModel,
|
||||
ToolOutputText as ToolOutputTextModel,
|
||||
)
|
||||
from galaxy.util import Element
|
||||
from galaxy.util.dictifiable import Dictifiable
|
||||
from .output_actions import (
|
||||
@@ -21,16 +32,9 @@ from .output_collection_def import (
|
||||
dataset_collector_descriptions_from_output_dict,
|
||||
DatasetCollectionDescription,
|
||||
)
|
||||
from .output_models import (
|
||||
ToolOutputBoolean as ToolOutputBooleanModel,
|
||||
ToolOutputCollection as ToolOutputCollectionModel,
|
||||
ToolOutputCollectionStructure as ToolOutputCollectionStructureModel,
|
||||
ToolOutputDataset as ToolOutputDataModel,
|
||||
ToolOutputFloat as ToolOutputFloatModel,
|
||||
ToolOutputInteger as ToolOutputIntegerModel,
|
||||
ToolOutputT as ToolOutputModel,
|
||||
ToolOutputText as ToolOutputTextModel,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from galaxy.tool_util.parser import ToolSource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeIs # Supported only under Python >=3.8
|
||||
@@ -521,3 +525,13 @@ class ToolOutputCollectionPart:
|
||||
def split_output_name(name):
|
||||
assert ToolOutputCollectionPart.is_named_collection_part_name(name)
|
||||
return name.split("|__part__|")
|
||||
|
||||
|
||||
def from_tool_source(tool_source: "ToolSource") -> Sequence[ToolOutputModel]:
|
||||
tool_outputs, tool_output_collections = tool_source.parse_outputs(None)
|
||||
outputs = []
|
||||
for tool_output in tool_outputs.values():
|
||||
outputs.append(tool_output.to_model())
|
||||
# for tool_output_collection in tool_output_collections.values():
|
||||
# outputs.append(tool_output_collection.to_model())
|
||||
return outputs
|
||||
|
||||
@@ -9,467 +9,39 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
model_validator,
|
||||
PrivateAttr,
|
||||
TypeAdapter,
|
||||
)
|
||||
from typing_extensions import (
|
||||
Annotated,
|
||||
get_args,
|
||||
Literal,
|
||||
Protocol,
|
||||
Self,
|
||||
)
|
||||
from typing_extensions import get_args
|
||||
|
||||
from galaxy.tool_util_models.parameter_validators import (
|
||||
AnyValidatorModel,
|
||||
DatasetMetadataEqualParameterValidatorModel,
|
||||
DatasetMetadataInDataTableParameterValidatorModel,
|
||||
DatasetMetadataInFileParameterValidatorModel,
|
||||
DatasetMetadataInRangeParameterValidatorModel,
|
||||
DatasetMetadataNotInDataTableParameterValidatorModel,
|
||||
DatasetOkValidatorParameterValidatorModel,
|
||||
DiscriminatedAnyValidatorModel,
|
||||
EmptyDatasetParameterValidatorModel,
|
||||
EmptyExtraFilesPathParameterValidatorModel,
|
||||
EmptyFieldParameterValidatorModel,
|
||||
ExpressionParameterValidatorModel,
|
||||
InRangeParameterValidatorModel,
|
||||
LengthParameterValidatorModel,
|
||||
MetadataParameterValidatorModel,
|
||||
NoOptionsParameterValidatorModel,
|
||||
ParameterValidatorModel,
|
||||
RegexParameterValidatorModel,
|
||||
SPLIT_DEFAULT,
|
||||
StaticValidatorModel,
|
||||
UnspecifiedBuildParameterValidatorModel,
|
||||
ValidatorType,
|
||||
ValueInDataTableParameterValidatorModel,
|
||||
ValueNotInDataTableParameterValidatorModel,
|
||||
)
|
||||
from galaxy.util import (
|
||||
asbool,
|
||||
Element,
|
||||
)
|
||||
|
||||
try:
|
||||
import regex
|
||||
except ImportError:
|
||||
import re as regex
|
||||
|
||||
|
||||
class ValidationArgument:
|
||||
doc: Optional[str]
|
||||
xml_body: bool
|
||||
xml_allow_json_load: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
doc: Optional[str],
|
||||
xml_body: bool = False,
|
||||
xml_allow_json_load: bool = False,
|
||||
):
|
||||
self.doc = doc
|
||||
self.xml_body = xml_body
|
||||
self.xml_allow_json_load = xml_allow_json_load
|
||||
|
||||
|
||||
Negate = Annotated[
|
||||
bool,
|
||||
ValidationArgument("Negates the result of the validator."),
|
||||
]
|
||||
NEGATE_DEFAULT = False
|
||||
SPLIT_DEFAULT = "\t"
|
||||
DEFAULT_VALIDATOR_MESSAGE = "Parameter validation error."
|
||||
|
||||
ValidatorType = Literal[
|
||||
"expression",
|
||||
"regex",
|
||||
"in_range",
|
||||
"length",
|
||||
"metadata",
|
||||
"dataset_metadata_equal",
|
||||
"unspecified_build",
|
||||
"no_options",
|
||||
"empty_field",
|
||||
"empty_dataset",
|
||||
"empty_extra_files_path",
|
||||
"dataset_metadata_in_data_table",
|
||||
"dataset_metadata_not_in_data_table",
|
||||
"dataset_metadata_in_range",
|
||||
"value_in_data_table",
|
||||
"value_not_in_data_table",
|
||||
"dataset_ok_validator",
|
||||
"dataset_metadata_in_file",
|
||||
]
|
||||
|
||||
|
||||
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
|
||||
# track validators setup by other input parameters and not validation explicitly
|
||||
implicit: bool = False
|
||||
_static: bool = PrivateAttr(False)
|
||||
_deprecated: bool = PrivateAttr(False)
|
||||
# validators must be explicitly set as 'safe' to operate as user-defined workflow parameters or to be used
|
||||
# within future user-defined tool parameters
|
||||
_safe: bool = PrivateAttr(False)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def set_default_message(self) -> Self:
|
||||
if self.message is None:
|
||||
self.message = self.default_message
|
||||
return self
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return DEFAULT_VALIDATOR_MESSAGE
|
||||
|
||||
|
||||
class StaticValidatorModel(ParameterValidatorModel):
|
||||
_static: bool = PrivateAttr(True)
|
||||
|
||||
def statically_validate(self, v: Any) -> None: ...
|
||||
|
||||
|
||||
class ExpressionParameterValidatorModel(StaticValidatorModel):
|
||||
"""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"] = "expression"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
expression: Annotated[str, ValidationArgument("Python expression to validate.", xml_body=True)]
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
ExpressionParameterValidatorModel.expression_validation(self.expression, value, self)
|
||||
|
||||
@staticmethod
|
||||
def ensure_compiled(expression: Union[str, Any]) -> Any:
|
||||
if isinstance(expression, str):
|
||||
return compile(expression, "<string>", "eval")
|
||||
else:
|
||||
return expression
|
||||
|
||||
@staticmethod
|
||||
def expression_validation(
|
||||
expression: str, value: Any, validator: "ValidatorDescription", compiled_expression: Optional[Any] = None
|
||||
):
|
||||
if compiled_expression is None:
|
||||
compiled_expression = ExpressionParameterValidatorModel.ensure_compiled(expression)
|
||||
message = None
|
||||
try:
|
||||
evalresult = eval(compiled_expression, dict(value=value))
|
||||
except Exception:
|
||||
message = f"Validator '{expression}' could not be evaluated on '{value}'"
|
||||
evalresult = False
|
||||
|
||||
raise_error_if_valiation_fails(bool(evalresult), validator, message=message, value_to_show=value)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value '%s' does not evaluate to {'True' if not self.negate else 'False'} for '{self.expression}'"
|
||||
|
||||
|
||||
class RegexParameterValidatorModel(StaticValidatorModel):
|
||||
"""Check if a regular expression **matches** the value, i.e. appears
|
||||
at the beginning of the value. To enforce a match of the complete value use
|
||||
``$`` 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"] = "regex"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
expression: Annotated[str, ValidationArgument("Regular expression to validate against.", xml_body=True)]
|
||||
_safe: bool = PrivateAttr(True)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value '%s' does {'not ' if not self.negate else ''}match regular expression '{self.expression.replace('%', '%%')}'"
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
if value and not isinstance(value, str):
|
||||
raise ValueError(f"Wrong type found value {value}")
|
||||
RegexParameterValidatorModel.regex_validation(self.expression, value, self)
|
||||
|
||||
@staticmethod
|
||||
def regex_validation(expression: str, value: Any, validator: "ValidatorDescription"):
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
for val in value:
|
||||
match = regex.match(expression, val or "")
|
||||
raise_error_if_valiation_fails(match is not None, validator, value_to_show=val)
|
||||
|
||||
|
||||
class InRangeParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["in_range"] = "in_range"
|
||||
min: Optional[Union[float, int]] = None
|
||||
max: Optional[Union[float, int]] = None
|
||||
exclude_min: bool = False
|
||||
exclude_max: bool = False
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
_safe: bool = PrivateAttr(True)
|
||||
|
||||
def statically_validate(self, value: Any):
|
||||
if isinstance(value, (int, float)):
|
||||
validates = True
|
||||
if self.min is not None and value == self.min and self.exclude_min:
|
||||
validates = False
|
||||
elif self.min is not None and value < self.min:
|
||||
validates = False
|
||||
elif self.max is not None and value == self.max and self.exclude_max:
|
||||
validates = False
|
||||
if self.max is not None and value > self.max:
|
||||
validates = False
|
||||
raise_error_if_valiation_fails(validates, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
op1 = "<="
|
||||
op2 = "<="
|
||||
if self.exclude_min:
|
||||
op1 = "<"
|
||||
if self.exclude_max:
|
||||
op2 = "<"
|
||||
min_str = str(self.min) if self.min is not None else "-infinity"
|
||||
max_str = str(self.max) if self.max is not None else "+infinity"
|
||||
range_description_str = f"({min_str} {op1} value {op2} {max_str})"
|
||||
return f"Value ('%s') must {'not ' if self.negate else ''}fulfill {range_description_str}"
|
||||
|
||||
|
||||
class LengthParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["length"] = "length"
|
||||
min: Optional[int] = None
|
||||
max: Optional[int] = None
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
_safe: bool = PrivateAttr(True)
|
||||
|
||||
def statically_validate(self, value: Any):
|
||||
if isinstance(value, str):
|
||||
length = len(value)
|
||||
validates = True
|
||||
if self.min is not None and length < self.min:
|
||||
validates = False
|
||||
if self.max is not None and length > self.max:
|
||||
validates = False
|
||||
raise_error_if_valiation_fails(validates, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Must {'not ' if self.negate else ''}have length of at least {self.min} and at most {self.max}"
|
||||
|
||||
|
||||
class MetadataParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["metadata"] = "metadata"
|
||||
check: Optional[List[str]] = None
|
||||
skip: Optional[List[str]] = None
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
check = self.check
|
||||
skip = self.skip
|
||||
message = DEFAULT_VALIDATOR_MESSAGE
|
||||
if not self.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 '{",".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"""
|
||||
return message
|
||||
|
||||
|
||||
class DatasetMetadataEqualParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_equal"] = "dataset_metadata_equal"
|
||||
metadata_name: str
|
||||
value: Annotated[Any, ValidationArgument("Value to test against", xml_allow_json_load=True)]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
if not self.negate:
|
||||
message = f"Metadata value for '{self.metadata_name}' must be '{self.value}', but it is '%s'."
|
||||
else:
|
||||
message = f"Metadata value for '{self.metadata_name}' must not be '{self.value}' but it is."
|
||||
return message
|
||||
|
||||
|
||||
class UnspecifiedBuildParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["unspecified_build"] = "unspecified_build"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"{'Unspecified' if not self.negate else 'Specified'} genome build, click the pencil icon in the history item to {'set' if not self.negate else 'remove'} the genome build"
|
||||
|
||||
|
||||
class NoOptionsParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["no_options"] = "no_options"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@staticmethod
|
||||
def no_options_validate(value: Any, validator: "ValidatorDescription"):
|
||||
raise_error_if_valiation_fails(value is not None, validator)
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
NoOptionsParameterValidatorModel.no_options_validate(value, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"{'No options' if not self.negate else 'Options'} available for selection"
|
||||
|
||||
|
||||
class EmptyFieldParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["empty_field"] = "empty_field"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@staticmethod
|
||||
def empty_validate(value: Any, validator: "ValidatorDescription"):
|
||||
raise_error_if_valiation_fails((value not in ("", None)), validator)
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
EmptyFieldParameterValidatorModel.empty_validate(value, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
if not self.negate:
|
||||
message = "Field requires a value"
|
||||
else:
|
||||
message = "Field must not set a value"
|
||||
return message
|
||||
|
||||
|
||||
class EmptyDatasetParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["empty_dataset"] = "empty_dataset"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"The selected dataset is {'non-' if self.negate else ''}empty, this tool expects {'non-' if not self.negate else ''}empty files."
|
||||
|
||||
|
||||
class EmptyExtraFilesPathParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["empty_extra_files_path"] = "empty_extra_files_path"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
negate = self.negate
|
||||
return 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."
|
||||
|
||||
|
||||
class DatasetMetadataInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_in_data_table"] = "dataset_metadata_in_data_table"
|
||||
table_name: str
|
||||
metadata_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value for metadata {self.metadata_name} was not found in {self.table_name}."
|
||||
|
||||
|
||||
class DatasetMetadataNotInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_not_in_data_table"] = "dataset_metadata_not_in_data_table"
|
||||
table_name: str
|
||||
metadata_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value for metadata {self.metadata_name} was not found in {self.table_name}."
|
||||
|
||||
|
||||
class DatasetMetadataInRangeParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_in_range"] = "dataset_metadata_in_range"
|
||||
metadata_name: str
|
||||
min: Optional[Union[float, int]] = None
|
||||
max: Optional[Union[float, int]] = None
|
||||
exclude_min: bool = False
|
||||
exclude_max: bool = False
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
op1 = "<="
|
||||
op2 = "<="
|
||||
if self.exclude_min:
|
||||
op1 = "<"
|
||||
if self.exclude_max:
|
||||
op2 = "<"
|
||||
range_description_str = f"({self.min} {op1} value {op2} {self.max})"
|
||||
return f"Value ('%s') must {'not ' if self.negate else ''}fulfill {range_description_str}"
|
||||
|
||||
|
||||
class ValueInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["value_in_data_table"] = "value_in_data_table"
|
||||
table_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return "Value for metadata not found."
|
||||
|
||||
|
||||
class ValueNotInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["value_not_in_data_table"] = "value_not_in_data_table"
|
||||
table_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value was not found in {self.table_name}."
|
||||
|
||||
|
||||
class DatasetOkValidatorParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_ok_validator"] = "dataset_ok_validator"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
if not self.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 message
|
||||
|
||||
|
||||
class DatasetMetadataInFileParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_in_file"] = "dataset_metadata_in_file"
|
||||
filename: str
|
||||
metadata_name: str
|
||||
metadata_column: Union[int, str]
|
||||
line_startswith: Optional[str] = None
|
||||
split: str = SPLIT_DEFAULT
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
_deprecated: bool = PrivateAttr(True)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value for metadata {self.metadata_name} was not found in {self.filename}."
|
||||
|
||||
|
||||
AnyValidatorModel = Annotated[
|
||||
Union[
|
||||
ExpressionParameterValidatorModel,
|
||||
RegexParameterValidatorModel,
|
||||
InRangeParameterValidatorModel,
|
||||
LengthParameterValidatorModel,
|
||||
MetadataParameterValidatorModel,
|
||||
DatasetMetadataEqualParameterValidatorModel,
|
||||
UnspecifiedBuildParameterValidatorModel,
|
||||
NoOptionsParameterValidatorModel,
|
||||
EmptyFieldParameterValidatorModel,
|
||||
EmptyDatasetParameterValidatorModel,
|
||||
EmptyExtraFilesPathParameterValidatorModel,
|
||||
DatasetMetadataInDataTableParameterValidatorModel,
|
||||
DatasetMetadataNotInDataTableParameterValidatorModel,
|
||||
DatasetMetadataInRangeParameterValidatorModel,
|
||||
ValueInDataTableParameterValidatorModel,
|
||||
ValueNotInDataTableParameterValidatorModel,
|
||||
DatasetOkValidatorParameterValidatorModel,
|
||||
DatasetMetadataInFileParameterValidatorModel,
|
||||
],
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
DiscriminatedAnyValidatorModel = TypeAdapter(AnyValidatorModel) # type:ignore[var-annotated]
|
||||
|
||||
|
||||
def parse_dict_validators(validator_dicts: List[Dict[str, Any]], trusted: bool) -> List[AnyValidatorModel]:
|
||||
validator_models = []
|
||||
@@ -651,34 +223,6 @@ def parse_xml_validator(validator_el: Element) -> AnyValidatorModel:
|
||||
raise ValueError(f"Unhandled 'type' attribute in validator {validator_type}")
|
||||
|
||||
|
||||
class ValidatorDescription(Protocol):
|
||||
|
||||
@property
|
||||
def negate(self) -> bool: ...
|
||||
|
||||
@property
|
||||
def message(self) -> Optional[str]: ...
|
||||
|
||||
|
||||
def raise_error_if_valiation_fails(
|
||||
value: bool, validator: ValidatorDescription, message: Optional[str] = None, value_to_show: Optional[str] = None
|
||||
):
|
||||
if not isinstance(value, bool):
|
||||
raise AssertionError("Validator logic problem - computed validation value must be boolean")
|
||||
if message is None:
|
||||
message = validator.message
|
||||
if not message:
|
||||
message = DEFAULT_VALIDATOR_MESSAGE
|
||||
assert message is not None
|
||||
if value_to_show and "%s" in message:
|
||||
message = message % value_to_show
|
||||
negate = validator.negate
|
||||
if (not negate and value) or (negate and not value):
|
||||
return
|
||||
else:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def _parse_message(xml_el: Element) -> Optional[str]:
|
||||
message = xml_el.get("message")
|
||||
return message
|
||||
|
||||
@@ -28,6 +28,14 @@ from galaxy.tool_util.parser.util import (
|
||||
DEFAULT_PIN_LABELS,
|
||||
DEFAULT_SORT,
|
||||
)
|
||||
from galaxy.tool_util_models.parameter_validators import AnyValidatorModel
|
||||
from galaxy.tool_util_models.tool_source import (
|
||||
Citation,
|
||||
DrillDownOptionsDict,
|
||||
HelpContent,
|
||||
OutputCompareType,
|
||||
XrefDict,
|
||||
)
|
||||
from galaxy.util import (
|
||||
Element,
|
||||
ElementTree,
|
||||
@@ -40,13 +48,9 @@ from galaxy.util import (
|
||||
)
|
||||
from .interface import (
|
||||
AssertionList,
|
||||
Citation,
|
||||
DrillDownDynamicOptions,
|
||||
DrillDownOptionsDict,
|
||||
DynamicOptions,
|
||||
HelpContent,
|
||||
InputSource,
|
||||
OutputCompareType,
|
||||
PageSource,
|
||||
PagesSource,
|
||||
RequiredFiles,
|
||||
@@ -63,7 +67,6 @@ from .interface import (
|
||||
ToolSourceTestOutputs,
|
||||
ToolSourceTests,
|
||||
XmlTestCollectionDefDict,
|
||||
XrefDict,
|
||||
)
|
||||
from .output_actions import (
|
||||
ToolOutputActionApp,
|
||||
@@ -77,10 +80,7 @@ from .output_objects import (
|
||||
ToolOutputCollection,
|
||||
ToolOutputCollectionStructure,
|
||||
)
|
||||
from .parameter_validators import (
|
||||
AnyValidatorModel,
|
||||
parse_xml_validators,
|
||||
)
|
||||
from .parameter_validators import parse_xml_validators
|
||||
from .stdio import (
|
||||
aggressive_error_checks,
|
||||
error_on_exit_code,
|
||||
|
||||
@@ -16,17 +16,20 @@ from galaxy.tool_util.parser.util import (
|
||||
DEFAULT_DELTA_FRAC,
|
||||
DEFAULT_SORT,
|
||||
)
|
||||
from galaxy.tool_util_models.parameter_validators import AnyValidatorModel
|
||||
from galaxy.tool_util_models.tool_source import (
|
||||
HelpContent,
|
||||
XrefDict,
|
||||
)
|
||||
from .interface import (
|
||||
AssertionDict,
|
||||
AssertionList,
|
||||
HelpContent,
|
||||
InputSource,
|
||||
PageSource,
|
||||
PagesSource,
|
||||
ToolSource,
|
||||
ToolSourceTest,
|
||||
ToolSourceTests,
|
||||
XrefDict,
|
||||
)
|
||||
from .output_actions import ToolOutputActionApp
|
||||
from .output_collection_def import dataset_collector_descriptions_from_output_dict
|
||||
@@ -35,10 +38,7 @@ from .output_objects import (
|
||||
ToolOutputCollection,
|
||||
ToolOutputCollectionStructure,
|
||||
)
|
||||
from .parameter_validators import (
|
||||
AnyValidatorModel,
|
||||
parse_dict_validators,
|
||||
)
|
||||
from .parameter_validators import parse_dict_validators
|
||||
from .stdio import error_on_exit_code
|
||||
from .util import is_dict
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import sys
|
||||
|
||||
import yaml
|
||||
|
||||
from galaxy.tool_util.models import Tests
|
||||
from galaxy.tool_util_models import Tests
|
||||
|
||||
DESCRIPTION = """
|
||||
A small utility to verify the Planemo test format.
|
||||
|
||||
@@ -30,12 +30,12 @@ from galaxy.tool_util.parser.util import (
|
||||
parse_tool_version_with_defaults,
|
||||
)
|
||||
from galaxy.tool_util.parser.xml import __parse_assert_list_from_elem
|
||||
from galaxy.tool_util.verify.assertion_models import relaxed_assertion_list
|
||||
from galaxy.tool_util.verify.interactor import (
|
||||
InvalidToolTestDict,
|
||||
ToolTestDescription,
|
||||
ValidToolTestDict,
|
||||
)
|
||||
from galaxy.tool_util_models.assertions import relaxed_assertion_list
|
||||
from galaxy.util import (
|
||||
string_as_bool,
|
||||
string_as_bool_or_none,
|
||||
|
||||
@@ -9,8 +9,6 @@ from typing import (
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
@@ -29,22 +27,19 @@ from typing_extensions import (
|
||||
TypedDict,
|
||||
)
|
||||
|
||||
from .assertions import assertions
|
||||
from .parameters import (
|
||||
input_models_for_tool_source,
|
||||
ToolParameterT,
|
||||
)
|
||||
from .parser.interface import (
|
||||
from .tool_outputs import (
|
||||
ToolOutput,
|
||||
)
|
||||
from .tool_source import (
|
||||
Citation,
|
||||
HelpContent,
|
||||
OutputCompareType,
|
||||
ToolSource,
|
||||
XrefDict,
|
||||
)
|
||||
from .parser.output_models import (
|
||||
from_tool_source,
|
||||
ToolOutput,
|
||||
)
|
||||
from .verify.assertion_models import assertions
|
||||
|
||||
|
||||
class ParsedTool(BaseModel):
|
||||
@@ -63,45 +58,6 @@ class ParsedTool(BaseModel):
|
||||
help: Optional[HelpContent]
|
||||
|
||||
|
||||
def parse_tool(tool_source: ToolSource) -> ParsedTool:
|
||||
return parse_tool_custom(tool_source, ParsedTool)
|
||||
|
||||
|
||||
P = TypeVar("P", bound=ParsedTool)
|
||||
|
||||
|
||||
def parse_tool_custom(tool_source: ToolSource, model_type: Type[P]) -> P:
|
||||
id = tool_source.parse_id()
|
||||
version = tool_source.parse_version()
|
||||
name = tool_source.parse_name()
|
||||
description = tool_source.parse_description()
|
||||
inputs = input_models_for_tool_source(tool_source).parameters
|
||||
outputs = from_tool_source(tool_source)
|
||||
citations = tool_source.parse_citations()
|
||||
license = tool_source.parse_license()
|
||||
profile = tool_source.parse_profile()
|
||||
edam_operations = tool_source.parse_edam_operations()
|
||||
edam_topics = tool_source.parse_edam_topics()
|
||||
xrefs = tool_source.parse_xrefs()
|
||||
help = tool_source.parse_help()
|
||||
|
||||
return model_type(
|
||||
id=id,
|
||||
version=version,
|
||||
name=name,
|
||||
description=description,
|
||||
profile=profile,
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
license=license,
|
||||
citations=citations,
|
||||
edam_operations=edam_operations,
|
||||
edam_topics=edam_topics,
|
||||
xrefs=xrefs,
|
||||
help=help,
|
||||
)
|
||||
|
||||
|
||||
class StrictModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(
|
||||
@@ -0,0 +1,517 @@
|
||||
from typing import (
|
||||
Any,
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
model_validator,
|
||||
PrivateAttr,
|
||||
TypeAdapter,
|
||||
)
|
||||
from typing_extensions import (
|
||||
Annotated,
|
||||
Literal,
|
||||
Protocol,
|
||||
Self,
|
||||
)
|
||||
|
||||
try:
|
||||
import regex
|
||||
except ImportError:
|
||||
import re as regex
|
||||
|
||||
|
||||
class ValidationArgument:
|
||||
doc: Optional[str]
|
||||
xml_body: bool
|
||||
xml_allow_json_load: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
doc: Optional[str],
|
||||
xml_body: bool = False,
|
||||
xml_allow_json_load: bool = False,
|
||||
):
|
||||
self.doc = doc
|
||||
self.xml_body = xml_body
|
||||
self.xml_allow_json_load = xml_allow_json_load
|
||||
|
||||
|
||||
Negate = Annotated[
|
||||
bool,
|
||||
ValidationArgument("Negates the result of the validator."),
|
||||
]
|
||||
NEGATE_DEFAULT = False
|
||||
SPLIT_DEFAULT = "\t"
|
||||
DEFAULT_VALIDATOR_MESSAGE = "Parameter validation error."
|
||||
|
||||
ValidatorType = Literal[
|
||||
"expression",
|
||||
"regex",
|
||||
"in_range",
|
||||
"length",
|
||||
"metadata",
|
||||
"dataset_metadata_equal",
|
||||
"unspecified_build",
|
||||
"no_options",
|
||||
"empty_field",
|
||||
"empty_dataset",
|
||||
"empty_extra_files_path",
|
||||
"dataset_metadata_in_data_table",
|
||||
"dataset_metadata_not_in_data_table",
|
||||
"dataset_metadata_in_range",
|
||||
"value_in_data_table",
|
||||
"value_not_in_data_table",
|
||||
"dataset_ok_validator",
|
||||
"dataset_metadata_in_file",
|
||||
]
|
||||
|
||||
|
||||
class ValidatorDescription(Protocol):
|
||||
|
||||
@property
|
||||
def negate(self) -> bool: ...
|
||||
|
||||
@property
|
||||
def message(self) -> Optional[str]: ...
|
||||
|
||||
|
||||
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
|
||||
# track validators setup by other input parameters and not validation explicitly
|
||||
implicit: bool = False
|
||||
_static: bool = PrivateAttr(False)
|
||||
_deprecated: bool = PrivateAttr(False)
|
||||
# validators must be explicitly set as 'safe' to operate as user-defined workflow parameters or to be used
|
||||
# within future user-defined tool parameters
|
||||
_safe: bool = PrivateAttr(False)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def set_default_message(self) -> Self:
|
||||
if self.message is None:
|
||||
self.message = self.default_message
|
||||
return self
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return DEFAULT_VALIDATOR_MESSAGE
|
||||
|
||||
|
||||
class StaticValidatorModel(ParameterValidatorModel):
|
||||
_static: bool = PrivateAttr(True)
|
||||
|
||||
def statically_validate(self, v: Any) -> None: ...
|
||||
|
||||
|
||||
class ExpressionParameterValidatorModel(StaticValidatorModel):
|
||||
"""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"] = "expression"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
expression: Annotated[str, ValidationArgument("Python expression to validate.", xml_body=True)]
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
ExpressionParameterValidatorModel.expression_validation(self.expression, value, self)
|
||||
|
||||
@staticmethod
|
||||
def ensure_compiled(expression: Union[str, Any]) -> Any:
|
||||
if isinstance(expression, str):
|
||||
return compile(expression, "<string>", "eval")
|
||||
else:
|
||||
return expression
|
||||
|
||||
@staticmethod
|
||||
def expression_validation(
|
||||
expression: str, value: Any, validator: "ValidatorDescription", compiled_expression: Optional[Any] = None
|
||||
):
|
||||
if compiled_expression is None:
|
||||
compiled_expression = ExpressionParameterValidatorModel.ensure_compiled(expression)
|
||||
message = None
|
||||
try:
|
||||
evalresult = eval(compiled_expression, dict(value=value))
|
||||
except Exception:
|
||||
message = f"Validator '{expression}' could not be evaluated on '{value}'"
|
||||
evalresult = False
|
||||
|
||||
raise_error_if_validation_fails(bool(evalresult), validator, message=message, value_to_show=value)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value '%s' does not evaluate to {'True' if not self.negate else 'False'} for '{self.expression}'"
|
||||
|
||||
|
||||
class RegexParameterValidatorModel(StaticValidatorModel):
|
||||
"""Check if a regular expression **matches** the value, i.e. appears
|
||||
at the beginning of the value. To enforce a match of the complete value use
|
||||
``$`` 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"] = "regex"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
expression: Annotated[str, ValidationArgument("Regular expression to validate against.", xml_body=True)]
|
||||
_safe: bool = PrivateAttr(True)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value '%s' does {'not ' if not self.negate else ''}match regular expression '{self.expression.replace('%', '%%')}'"
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
if value and not isinstance(value, str):
|
||||
raise ValueError(f"Wrong type found value {value}")
|
||||
RegexParameterValidatorModel.regex_validation(self.expression, value, self)
|
||||
|
||||
@staticmethod
|
||||
def regex_validation(expression: str, value: Any, validator: "ValidatorDescription"):
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
for val in value:
|
||||
match = regex.match(expression, val or "")
|
||||
raise_error_if_validation_fails(match is not None, validator, value_to_show=val)
|
||||
|
||||
|
||||
class InRangeParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["in_range"] = "in_range"
|
||||
min: Optional[Union[float, int]] = None
|
||||
max: Optional[Union[float, int]] = None
|
||||
exclude_min: bool = False
|
||||
exclude_max: bool = False
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
_safe: bool = PrivateAttr(True)
|
||||
|
||||
def statically_validate(self, value: Any):
|
||||
if isinstance(value, (int, float)):
|
||||
validates = True
|
||||
if self.min is not None and value == self.min and self.exclude_min:
|
||||
validates = False
|
||||
elif self.min is not None and value < self.min:
|
||||
validates = False
|
||||
elif self.max is not None and value == self.max and self.exclude_max:
|
||||
validates = False
|
||||
if self.max is not None and value > self.max:
|
||||
validates = False
|
||||
raise_error_if_validation_fails(validates, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
op1 = "<="
|
||||
op2 = "<="
|
||||
if self.exclude_min:
|
||||
op1 = "<"
|
||||
if self.exclude_max:
|
||||
op2 = "<"
|
||||
min_str = str(self.min) if self.min is not None else "-infinity"
|
||||
max_str = str(self.max) if self.max is not None else "+infinity"
|
||||
range_description_str = f"({min_str} {op1} value {op2} {max_str})"
|
||||
return f"Value ('%s') must {'not ' if self.negate else ''}fulfill {range_description_str}"
|
||||
|
||||
|
||||
class LengthParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["length"] = "length"
|
||||
min: Optional[int] = None
|
||||
max: Optional[int] = None
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
_safe: bool = PrivateAttr(True)
|
||||
|
||||
def statically_validate(self, value: Any):
|
||||
if isinstance(value, str):
|
||||
length = len(value)
|
||||
validates = True
|
||||
if self.min is not None and length < self.min:
|
||||
validates = False
|
||||
if self.max is not None and length > self.max:
|
||||
validates = False
|
||||
raise_error_if_validation_fails(validates, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Must {'not ' if self.negate else ''}have length of at least {self.min} and at most {self.max}"
|
||||
|
||||
|
||||
class MetadataParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["metadata"] = "metadata"
|
||||
check: Optional[List[str]] = None
|
||||
skip: Optional[List[str]] = None
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
check = self.check
|
||||
skip = self.skip
|
||||
message = DEFAULT_VALIDATOR_MESSAGE
|
||||
if not self.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 '{",".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"""
|
||||
return message
|
||||
|
||||
|
||||
class DatasetMetadataEqualParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_equal"] = "dataset_metadata_equal"
|
||||
metadata_name: str
|
||||
value: Annotated[Any, ValidationArgument("Value to test against", xml_allow_json_load=True)]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
if not self.negate:
|
||||
message = f"Metadata value for '{self.metadata_name}' must be '{self.value}', but it is '%s'."
|
||||
else:
|
||||
message = f"Metadata value for '{self.metadata_name}' must not be '{self.value}' but it is."
|
||||
return message
|
||||
|
||||
|
||||
class UnspecifiedBuildParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["unspecified_build"] = "unspecified_build"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"{'Unspecified' if not self.negate else 'Specified'} genome build, click the pencil icon in the history item to {'set' if not self.negate else 'remove'} the genome build"
|
||||
|
||||
|
||||
class NoOptionsParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["no_options"] = "no_options"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@staticmethod
|
||||
def no_options_validate(value: Any, validator: "ValidatorDescription"):
|
||||
raise_error_if_validation_fails(value is not None, validator)
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
NoOptionsParameterValidatorModel.no_options_validate(value, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"{'No options' if not self.negate else 'Options'} available for selection"
|
||||
|
||||
|
||||
class EmptyFieldParameterValidatorModel(StaticValidatorModel):
|
||||
type: Literal["empty_field"] = "empty_field"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@staticmethod
|
||||
def empty_validate(value: Any, validator: "ValidatorDescription"):
|
||||
raise_error_if_validation_fails((value not in ("", None)), validator)
|
||||
|
||||
def statically_validate(self, value: Any) -> None:
|
||||
EmptyFieldParameterValidatorModel.empty_validate(value, self)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
if not self.negate:
|
||||
message = "Field requires a value"
|
||||
else:
|
||||
message = "Field must not set a value"
|
||||
return message
|
||||
|
||||
|
||||
class EmptyDatasetParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["empty_dataset"] = "empty_dataset"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"The selected dataset is {'non-' if self.negate else ''}empty, this tool expects {'non-' if not self.negate else ''}empty files."
|
||||
|
||||
|
||||
class EmptyExtraFilesPathParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["empty_extra_files_path"] = "empty_extra_files_path"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
negate = self.negate
|
||||
return 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."
|
||||
|
||||
|
||||
class DatasetMetadataInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_in_data_table"] = "dataset_metadata_in_data_table"
|
||||
table_name: str
|
||||
metadata_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value for metadata {self.metadata_name} was not found in {self.table_name}."
|
||||
|
||||
|
||||
class DatasetMetadataNotInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_not_in_data_table"] = "dataset_metadata_not_in_data_table"
|
||||
table_name: str
|
||||
metadata_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value for metadata {self.metadata_name} was not found in {self.table_name}."
|
||||
|
||||
|
||||
class DatasetMetadataInRangeParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_in_range"] = "dataset_metadata_in_range"
|
||||
metadata_name: str
|
||||
min: Optional[Union[float, int]] = None
|
||||
max: Optional[Union[float, int]] = None
|
||||
exclude_min: bool = False
|
||||
exclude_max: bool = False
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
op1 = "<="
|
||||
op2 = "<="
|
||||
if self.exclude_min:
|
||||
op1 = "<"
|
||||
if self.exclude_max:
|
||||
op2 = "<"
|
||||
range_description_str = f"({self.min} {op1} value {op2} {self.max})"
|
||||
return f"Value ('%s') must {'not ' if self.negate else ''}fulfill {range_description_str}"
|
||||
|
||||
|
||||
class ValueInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["value_in_data_table"] = "value_in_data_table"
|
||||
table_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return "Value for metadata not found."
|
||||
|
||||
|
||||
class ValueNotInDataTableParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["value_not_in_data_table"] = "value_not_in_data_table"
|
||||
table_name: str
|
||||
metadata_column: Union[int, str]
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value was not found in {self.table_name}."
|
||||
|
||||
|
||||
class DatasetOkValidatorParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_ok_validator"] = "dataset_ok_validator"
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
if not self.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 message
|
||||
|
||||
|
||||
class DatasetMetadataInFileParameterValidatorModel(ParameterValidatorModel):
|
||||
type: Literal["dataset_metadata_in_file"] = "dataset_metadata_in_file"
|
||||
filename: str
|
||||
metadata_name: str
|
||||
metadata_column: Union[int, str]
|
||||
line_startswith: Optional[str] = None
|
||||
split: str = SPLIT_DEFAULT
|
||||
negate: Negate = NEGATE_DEFAULT
|
||||
_deprecated: bool = PrivateAttr(True)
|
||||
|
||||
@property
|
||||
def default_message(self) -> str:
|
||||
return f"Value for metadata {self.metadata_name} was not found in {self.filename}."
|
||||
|
||||
|
||||
AnyValidatorModel = Annotated[
|
||||
Union[
|
||||
ExpressionParameterValidatorModel,
|
||||
RegexParameterValidatorModel,
|
||||
InRangeParameterValidatorModel,
|
||||
LengthParameterValidatorModel,
|
||||
MetadataParameterValidatorModel,
|
||||
DatasetMetadataEqualParameterValidatorModel,
|
||||
UnspecifiedBuildParameterValidatorModel,
|
||||
NoOptionsParameterValidatorModel,
|
||||
EmptyFieldParameterValidatorModel,
|
||||
EmptyDatasetParameterValidatorModel,
|
||||
EmptyExtraFilesPathParameterValidatorModel,
|
||||
DatasetMetadataInDataTableParameterValidatorModel,
|
||||
DatasetMetadataNotInDataTableParameterValidatorModel,
|
||||
DatasetMetadataInRangeParameterValidatorModel,
|
||||
ValueInDataTableParameterValidatorModel,
|
||||
ValueNotInDataTableParameterValidatorModel,
|
||||
DatasetOkValidatorParameterValidatorModel,
|
||||
DatasetMetadataInFileParameterValidatorModel,
|
||||
],
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
DiscriminatedAnyValidatorModel = TypeAdapter(AnyValidatorModel) # type:ignore[var-annotated]
|
||||
|
||||
|
||||
def raise_error_if_validation_fails(
|
||||
value: bool, validator: ValidatorDescription, message: Optional[str] = None, value_to_show: Optional[str] = None
|
||||
):
|
||||
if not isinstance(value, bool):
|
||||
raise AssertionError("Validator logic problem - computed validation value must be boolean")
|
||||
if message is None:
|
||||
message = validator.message
|
||||
if not message:
|
||||
message = DEFAULT_VALIDATOR_MESSAGE
|
||||
assert message is not None
|
||||
if value_to_show and "%s" in message:
|
||||
message = message % value_to_show
|
||||
negate = validator.negate
|
||||
if (not negate and value) or (negate and not value):
|
||||
return
|
||||
else:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AnyValidatorModel",
|
||||
"DiscriminatedAnyValidatorModel",
|
||||
"ValidatorType",
|
||||
"ParameterValidatorModel",
|
||||
"SPLIT_DEFAULT",
|
||||
"StaticValidatorModel",
|
||||
"ExpressionParameterValidatorModel",
|
||||
"RegexParameterValidatorModel",
|
||||
"InRangeParameterValidatorModel",
|
||||
"LengthParameterValidatorModel",
|
||||
"MetadataParameterValidatorModel",
|
||||
"DatasetMetadataEqualParameterValidatorModel",
|
||||
"UnspecifiedBuildParameterValidatorModel",
|
||||
"NoOptionsParameterValidatorModel",
|
||||
"EmptyFieldParameterValidatorModel",
|
||||
"EmptyDatasetParameterValidatorModel",
|
||||
"EmptyExtraFilesPathParameterValidatorModel",
|
||||
"DatasetMetadataInDataTableParameterValidatorModel",
|
||||
"DatasetMetadataNotInDataTableParameterValidatorModel",
|
||||
"DatasetMetadataInRangeParameterValidatorModel",
|
||||
"ValueInDataTableParameterValidatorModel",
|
||||
"ValueNotInDataTableParameterValidatorModel",
|
||||
"DatasetOkValidatorParameterValidatorModel",
|
||||
"DatasetMetadataInFileParameterValidatorModel",
|
||||
)
|
||||
+14
-51
@@ -33,7 +33,6 @@ from pydantic import (
|
||||
StrictInt,
|
||||
StrictStr,
|
||||
Tag,
|
||||
ValidationError,
|
||||
)
|
||||
from typing_extensions import (
|
||||
Annotated,
|
||||
@@ -41,21 +40,6 @@ from typing_extensions import (
|
||||
Protocol,
|
||||
)
|
||||
|
||||
from galaxy.exceptions import RequestParameterInvalidException
|
||||
from galaxy.tool_util.parser.interface import (
|
||||
DrillDownOptionsDict,
|
||||
JsonTestCollectionDefDict,
|
||||
JsonTestDatasetDefDict,
|
||||
)
|
||||
from galaxy.tool_util.parser.parameter_validators import (
|
||||
EmptyFieldParameterValidatorModel,
|
||||
ExpressionParameterValidatorModel,
|
||||
InRangeParameterValidatorModel,
|
||||
LengthParameterValidatorModel,
|
||||
NoOptionsParameterValidatorModel,
|
||||
RegexParameterValidatorModel,
|
||||
StaticValidatorModel,
|
||||
)
|
||||
from ._types import (
|
||||
cast_as_type,
|
||||
expand_annotation,
|
||||
@@ -65,6 +49,20 @@ from ._types import (
|
||||
optional_if_needed,
|
||||
union_type,
|
||||
)
|
||||
from .parameter_validators import (
|
||||
EmptyFieldParameterValidatorModel,
|
||||
ExpressionParameterValidatorModel,
|
||||
InRangeParameterValidatorModel,
|
||||
LengthParameterValidatorModel,
|
||||
NoOptionsParameterValidatorModel,
|
||||
RegexParameterValidatorModel,
|
||||
StaticValidatorModel,
|
||||
)
|
||||
from .tool_source import (
|
||||
DrillDownOptionsDict,
|
||||
JsonTestCollectionDefDict,
|
||||
JsonTestDatasetDefDict,
|
||||
)
|
||||
|
||||
# TODO:
|
||||
# - implement data_ref on rules and implement some cross model validation
|
||||
@@ -1543,38 +1541,3 @@ def create_field_model(
|
||||
|
||||
def _is_landing_request(state_representation: StateRepresentationT):
|
||||
return state_representation in ["landing_request", "landing_request_internal"]
|
||||
|
||||
|
||||
def validate_against_model(pydantic_model: Type[BaseModel], parameter_state: Dict[str, Any]) -> None:
|
||||
try:
|
||||
pydantic_model(**parameter_state)
|
||||
except ValidationError as e:
|
||||
# TODO: Improve this or maybe add a handler for this in the FastAPI exception
|
||||
# handler.
|
||||
raise RequestParameterInvalidException(str(e))
|
||||
|
||||
|
||||
class ValidationFunctionT(Protocol):
|
||||
|
||||
def __call__(self, tool: ToolParameterBundle, request: RawStateDict, name: Optional[str] = None) -> None: ...
|
||||
|
||||
|
||||
def validate_model_type_factory(state_representation: StateRepresentationT) -> ValidationFunctionT:
|
||||
|
||||
def validate_request(tool: ToolParameterBundle, request: Dict[str, Any], name: Optional[str] = None) -> None:
|
||||
name = name or DEFAULT_MODEL_NAME
|
||||
pydantic_model = create_field_model(tool.parameters, name=name, state_representation=state_representation)
|
||||
validate_against_model(pydantic_model, request)
|
||||
|
||||
return validate_request
|
||||
|
||||
|
||||
validate_request = validate_model_type_factory("request")
|
||||
validate_internal_request = validate_model_type_factory("request_internal")
|
||||
validate_internal_request_dereferenced = validate_model_type_factory("request_internal_dereferenced")
|
||||
validate_landing_request = validate_model_type_factory("landing_request")
|
||||
validate_internal_landing_request = validate_model_type_factory("landing_request_internal")
|
||||
validate_internal_job = validate_model_type_factory("job_internal")
|
||||
validate_test_case = validate_model_type_factory("test_case_xml")
|
||||
validate_workflow_step = validate_model_type_factory("workflow_step")
|
||||
validate_workflow_step_linked = validate_model_type_factory("workflow_step_linked")
|
||||
-13
@@ -8,7 +8,6 @@ code where actual tool objects aren't created.
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Union,
|
||||
)
|
||||
|
||||
@@ -21,8 +20,6 @@ from typing_extensions import (
|
||||
Literal,
|
||||
)
|
||||
|
||||
from .interface import ToolSource
|
||||
|
||||
|
||||
class ToolOutputBaseModel(BaseModel):
|
||||
name: str
|
||||
@@ -104,13 +101,3 @@ ToolOutputT = Union[
|
||||
ToolOutputDataset, ToolOutputCollection, ToolOutputText, ToolOutputInteger, ToolOutputFloat, ToolOutputBoolean
|
||||
]
|
||||
ToolOutput = Annotated[ToolOutputT, Field(discriminator="type")]
|
||||
|
||||
|
||||
def from_tool_source(tool_source: ToolSource) -> Sequence[ToolOutput]:
|
||||
tool_outputs, tool_output_collections = tool_source.parse_outputs(None)
|
||||
outputs = []
|
||||
for tool_output in tool_outputs.values():
|
||||
outputs.append(tool_output.to_model())
|
||||
# for tool_output_collection in tool_output_collections.values():
|
||||
# outputs.append(tool_output_collection.to_model())
|
||||
return outputs
|
||||
@@ -0,0 +1,107 @@
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import (
|
||||
Literal,
|
||||
NotRequired,
|
||||
TypedDict,
|
||||
)
|
||||
|
||||
|
||||
class XrefDict(TypedDict):
|
||||
value: str
|
||||
reftype: str
|
||||
|
||||
|
||||
class Citation(BaseModel):
|
||||
type: str
|
||||
content: str
|
||||
|
||||
|
||||
class HelpContent(BaseModel):
|
||||
format: Literal["restructuredtext", "plain_text", "markdown"]
|
||||
content: str
|
||||
|
||||
|
||||
class OutputCompareType(str, Enum):
|
||||
diff = "diff"
|
||||
re_match = "re_match"
|
||||
sim_size = "sim_size"
|
||||
re_match_multiline = "re_match_multiline"
|
||||
contains = "contains"
|
||||
image_diff = "image_diff"
|
||||
|
||||
|
||||
class DrillDownOptionsDict(TypedDict):
|
||||
name: Optional[str]
|
||||
value: str
|
||||
options: List["DrillDownOptionsDict"]
|
||||
selected: bool
|
||||
|
||||
|
||||
JsonTestDatasetDefDict = TypedDict(
|
||||
"JsonTestDatasetDefDict",
|
||||
{
|
||||
"class": Literal["File"],
|
||||
"path": NotRequired[Optional[str]],
|
||||
"location": NotRequired[Optional[str]],
|
||||
"name": NotRequired[Optional[str]],
|
||||
"dbkey": NotRequired[Optional[str]],
|
||||
"filetype": NotRequired[Optional[str]],
|
||||
"composite_data": NotRequired[Optional[List[str]]],
|
||||
"tags": NotRequired[Optional[List[str]]],
|
||||
},
|
||||
)
|
||||
|
||||
JsonTestCollectionDefElementDict = Union[
|
||||
"JsonTestCollectionDefDatasetElementDict", "JsonTestCollectionDefCollectionElementDict"
|
||||
]
|
||||
|
||||
JsonTestCollectionDefDatasetElementDict = TypedDict(
|
||||
"JsonTestCollectionDefDatasetElementDict",
|
||||
{
|
||||
"identifier": str,
|
||||
"class": Literal["File"],
|
||||
"path": NotRequired[Optional[str]],
|
||||
"location": NotRequired[Optional[str]],
|
||||
"name": NotRequired[Optional[str]],
|
||||
"dbkey": NotRequired[Optional[str]],
|
||||
"filetype": NotRequired[Optional[str]],
|
||||
"composite_data": NotRequired[Optional[List[str]]],
|
||||
"tags": NotRequired[Optional[List[str]]],
|
||||
},
|
||||
)
|
||||
|
||||
BaseJsonTestCollectionDefCollectionElementDict = TypedDict(
|
||||
"BaseJsonTestCollectionDefCollectionElementDict",
|
||||
{
|
||||
"class": Literal["Collection"],
|
||||
"collection_type": str,
|
||||
"elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]],
|
||||
},
|
||||
)
|
||||
|
||||
JsonTestCollectionDefCollectionElementDict = TypedDict(
|
||||
"JsonTestCollectionDefCollectionElementDict",
|
||||
{
|
||||
"identifier": str,
|
||||
"class": Literal["Collection"],
|
||||
"collection_type": str,
|
||||
"elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]],
|
||||
},
|
||||
)
|
||||
|
||||
JsonTestCollectionDefDict = TypedDict(
|
||||
"JsonTestCollectionDefDict",
|
||||
{
|
||||
"class": Literal["Collection"],
|
||||
"collection_type": str,
|
||||
"elements": NotRequired[Optional[List[JsonTestCollectionDefElementDict]]],
|
||||
"name": NotRequired[Optional[str]],
|
||||
},
|
||||
)
|
||||
@@ -84,7 +84,6 @@ from galaxy.tool_util.parser import (
|
||||
ToolOutputCollectionPart,
|
||||
)
|
||||
from galaxy.tool_util.parser.interface import (
|
||||
HelpContent,
|
||||
InputSource,
|
||||
PageSource,
|
||||
ToolSource,
|
||||
@@ -113,6 +112,7 @@ from galaxy.tool_util.version import (
|
||||
LegacyVersion,
|
||||
parse_version,
|
||||
)
|
||||
from galaxy.tool_util_models.tool_source import HelpContent
|
||||
from galaxy.tools import expressions
|
||||
from galaxy.tools.actions import (
|
||||
DefaultToolAction,
|
||||
|
||||
@@ -45,7 +45,6 @@ from galaxy.model.dataset_collections import builder
|
||||
from galaxy.schema.fetch_data import FilesPayload
|
||||
from galaxy.tool_util.parameters.factory import get_color_value
|
||||
from galaxy.tool_util.parser import get_input_source as ensure_input_source
|
||||
from galaxy.tool_util.parser.interface import DrillDownOptionsDict
|
||||
from galaxy.tool_util.parser.util import (
|
||||
boolean_is_checked,
|
||||
boolean_true_and_false_values,
|
||||
@@ -53,6 +52,7 @@ from galaxy.tool_util.parser.util import (
|
||||
ParameterParseException,
|
||||
text_input_is_optional,
|
||||
)
|
||||
from galaxy.tool_util_models.tool_source import DrillDownOptionsDict
|
||||
from galaxy.tools.parameters.options import ParameterOption
|
||||
from galaxy.tools.parameters.workflow_utils import (
|
||||
NO_REPLACEMENT,
|
||||
|
||||
@@ -18,13 +18,15 @@ from galaxy import (
|
||||
util,
|
||||
)
|
||||
from galaxy.tool_util.parser.parameter_validators import (
|
||||
parse_xml_validators as parse_xml_validators_models,
|
||||
)
|
||||
from galaxy.tool_util_models.parameter_validators import (
|
||||
AnyValidatorModel,
|
||||
EmptyFieldParameterValidatorModel,
|
||||
ExpressionParameterValidatorModel,
|
||||
InRangeParameterValidatorModel,
|
||||
MetadataParameterValidatorModel,
|
||||
parse_xml_validators as parse_xml_validators_models,
|
||||
raise_error_if_valiation_fails,
|
||||
raise_error_if_validation_fails,
|
||||
RegexParameterValidatorModel,
|
||||
)
|
||||
|
||||
@@ -62,7 +64,7 @@ class Validator(abc.ABC):
|
||||
|
||||
return None if positive validation, otherwise a ValueError is raised
|
||||
"""
|
||||
raise_error_if_valiation_fails(value, self, message=message, value_to_show=value_to_show)
|
||||
raise_error_if_validation_fails(value, self, message=message, value_to_show=value_to_show)
|
||||
|
||||
|
||||
class RegexValidator(Validator):
|
||||
|
||||
@@ -8,12 +8,6 @@ import requests
|
||||
import yaml
|
||||
from gxformat2.yaml import ordered_load
|
||||
|
||||
from galaxy.tool_util.models import (
|
||||
OutputChecks,
|
||||
OutputsDict,
|
||||
TestDicts,
|
||||
TestJobDict,
|
||||
)
|
||||
from galaxy.tool_util.parser.interface import TestCollectionOutputDef
|
||||
from galaxy.tool_util.verify import verify_file_contents_against_dict
|
||||
from galaxy.tool_util.verify.interactor import (
|
||||
@@ -21,6 +15,12 @@ from galaxy.tool_util.verify.interactor import (
|
||||
get_metadata_to_test,
|
||||
verify_collection,
|
||||
)
|
||||
from galaxy.tool_util_models import (
|
||||
OutputChecks,
|
||||
OutputsDict,
|
||||
TestDicts,
|
||||
TestJobDict,
|
||||
)
|
||||
from galaxy.util import asbool
|
||||
from galaxy_test.api._framework import ApiTestCase
|
||||
from galaxy_test.base.populators import (
|
||||
|
||||
@@ -21,10 +21,7 @@ from galaxy.tool_shed.util.hg_util import (
|
||||
clone_repository,
|
||||
get_changectx_for_changeset,
|
||||
)
|
||||
from galaxy.tool_util.models import (
|
||||
parse_tool_custom,
|
||||
ParsedTool,
|
||||
)
|
||||
from galaxy.tool_util.model_factory import parse_tool_custom
|
||||
from galaxy.tool_util.parser import (
|
||||
get_tool_source,
|
||||
ToolSource,
|
||||
@@ -38,22 +35,13 @@ from tool_shed.context import (
|
||||
from tool_shed.util.common_util import generate_clone_url_for
|
||||
from tool_shed.webapp.model import RepositoryMetadata
|
||||
from tool_shed.webapp.search.tool_search import ToolSearch
|
||||
from tool_shed_client.schema import RepositoryRevisionMetadata
|
||||
from tool_shed_client.schema import ShedParsedTool
|
||||
from .repositories import get_repository_revision_metadata_model
|
||||
from .trs import trs_tool_id_to_repository_metadata
|
||||
|
||||
STOCK_TOOL_SOURCES: Optional[Dict[str, Dict[str, ToolSource]]] = None
|
||||
|
||||
|
||||
class ShedParsedTool(ParsedTool):
|
||||
repository_revision: Optional[RepositoryRevisionMetadata] = None
|
||||
|
||||
|
||||
def parse_tool(tool_source: ToolSource) -> ShedParsedTool:
|
||||
parsed_tool = parse_tool_custom(tool_source, ShedParsedTool)
|
||||
return parsed_tool
|
||||
|
||||
|
||||
def search(trans: SessionRequestContext, q: str, page: int = 1, page_size: int = 10) -> dict:
|
||||
"""
|
||||
Perform the search over TS tools index.
|
||||
@@ -131,7 +119,7 @@ def parsed_tool_model_for(
|
||||
tool_source, repository_metadata = tool_source_for(
|
||||
trans, trs_tool_id, tool_version, repository_clone_url=repository_clone_url
|
||||
)
|
||||
parsed_tool = parse_tool(tool_source)
|
||||
parsed_tool = parse_tool_custom(tool_source, ShedParsedTool)
|
||||
if repository_metadata:
|
||||
revision_model = get_repository_revision_metadata_model(
|
||||
trans.app, repository_metadata.repository, repository_metadata, recursive=False
|
||||
|
||||
@@ -15,7 +15,6 @@ from tool_shed.context import SessionRequestContext
|
||||
from tool_shed.managers.tools import (
|
||||
parsed_tool_model_cached_for,
|
||||
search,
|
||||
ShedParsedTool,
|
||||
)
|
||||
from tool_shed.managers.trs import (
|
||||
get_tool,
|
||||
@@ -24,7 +23,10 @@ from tool_shed.managers.trs import (
|
||||
)
|
||||
from tool_shed.structured_app import ToolShedApp
|
||||
from tool_shed.util.shed_index import build_index
|
||||
from tool_shed_client.schema import BuildSearchIndexResponse
|
||||
from tool_shed_client.schema import (
|
||||
BuildSearchIndexResponse,
|
||||
ShedParsedTool,
|
||||
)
|
||||
from tool_shed_client.schema.trs import (
|
||||
Tool,
|
||||
ToolClass,
|
||||
|
||||
@@ -19,6 +19,8 @@ from typing_extensions import (
|
||||
TypedDict,
|
||||
)
|
||||
|
||||
from galaxy.tool_util_models import ParsedTool
|
||||
|
||||
|
||||
class Repository(BaseModel):
|
||||
# element/collection view on the backend have same keys/impl
|
||||
@@ -540,3 +542,7 @@ class Version(BaseModel):
|
||||
version_major: str
|
||||
version: str
|
||||
api_version: str = "v1"
|
||||
|
||||
|
||||
class ShedParsedTool(ParsedTool):
|
||||
repository_revision: Optional[RepositoryRevisionMetadata] = None
|
||||
|
||||
@@ -40,6 +40,7 @@ install_requires =
|
||||
galaxy-job-metrics
|
||||
galaxy-objectstore
|
||||
galaxy-tool-util[cwl,edam]
|
||||
galaxy-tool-shed-schema
|
||||
galaxy-tours
|
||||
galaxy-util[image_util]
|
||||
galaxy-web-framework
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
util
|
||||
tool_util_models
|
||||
|
||||
tool_shed_schema
|
||||
|
||||
config
|
||||
files
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
util
|
||||
tool_util_models
|
||||
job_metrics
|
||||
objectstore
|
||||
tool_util
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
History
|
||||
-------
|
||||
|
||||
.. to_doc
|
||||
|
||||
---------
|
||||
25.0.dev0
|
||||
---------
|
||||
|
||||
* Initial creation of package during 25.0 development cycle.
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../LICENSE.txt
|
||||
+1
@@ -0,0 +1 @@
|
||||
../package.Makefile
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../mypy.ini
|
||||
@@ -0,0 +1 @@
|
||||
../package-pyproject.toml
|
||||
+1
@@ -0,0 +1 @@
|
||||
../build_scripts
|
||||
@@ -0,0 +1,47 @@
|
||||
[metadata]
|
||||
author = Galaxy Project and Community
|
||||
author_email = galaxy-committers@lists.galaxyproject.org
|
||||
classifiers =
|
||||
Development Status :: 5 - Production/Stable
|
||||
Environment :: Console
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: Academic Free License (AFL)
|
||||
Natural Language :: English
|
||||
Operating System :: POSIX
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: 3.12
|
||||
Programming Language :: Python :: 3.13
|
||||
Topic :: Software Development
|
||||
Topic :: Software Development :: Code Generators
|
||||
Topic :: Software Development :: Testing
|
||||
description = Galaxy tool and tool dependency utilities
|
||||
keywords =
|
||||
Galaxy
|
||||
license = AFL
|
||||
license_files =
|
||||
LICENSE
|
||||
long_description = file: README.rst, HISTORY.rst
|
||||
long_description_content_type = text/x-rst
|
||||
name = galaxy-tool-shed-schema
|
||||
url = https://github.com/galaxyproject/galaxy
|
||||
version = 25.0.dev0
|
||||
|
||||
[options]
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
galaxy-tool-util-models
|
||||
pydantic>=2,!=2.6.0,!=2.6.1
|
||||
packages = find:
|
||||
python_requires = >=3.7
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
tests*
|
||||
@@ -0,0 +1 @@
|
||||
pytest
|
||||
@@ -0,0 +1 @@
|
||||
../../../test/unit/tool_shed_schema
|
||||
@@ -0,0 +1 @@
|
||||
../../lib/tool_shed_client/
|
||||
@@ -34,6 +34,7 @@ version = 25.0.dev0
|
||||
[options]
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
galaxy-tool-util-models
|
||||
galaxy-util[image_util]>=22.1
|
||||
conda-package-streaming
|
||||
lxml!=4.2.2
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
History
|
||||
-------
|
||||
|
||||
.. to_doc
|
||||
|
||||
---------
|
||||
25.0.dev0
|
||||
---------
|
||||
|
||||
* Initial creation of package during 25.0 development cycle.
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../LICENSE.txt
|
||||
@@ -0,0 +1 @@
|
||||
include *.rst *.txt LICENSE */py.typed
|
||||
+1
@@ -0,0 +1 @@
|
||||
../package.Makefile
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
.. image:: https://badge.fury.io/py/galaxy-tool-util-models.svg
|
||||
:target: https://pypi.org/project/galaxy-tool-util-models/
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Pydantic models to support Galaxy_ tool utilities.
|
||||
|
||||
* Code: https://github.com/galaxyproject/galaxy/tree/dev/packages/tool_util_models
|
||||
|
||||
.. _Galaxy: http://galaxyproject.org/
|
||||
@@ -0,0 +1 @@
|
||||
../package-dev-requirements.txt
|
||||
@@ -0,0 +1 @@
|
||||
../../package.__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
../../../lib/galaxy/tool_util_models
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../mypy.ini
|
||||
@@ -0,0 +1 @@
|
||||
../package-pyproject.toml
|
||||
+1
@@ -0,0 +1 @@
|
||||
../build_scripts
|
||||
@@ -0,0 +1,46 @@
|
||||
[metadata]
|
||||
author = Galaxy Project and Community
|
||||
author_email = galaxy-committers@lists.galaxyproject.org
|
||||
classifiers =
|
||||
Development Status :: 5 - Production/Stable
|
||||
Environment :: Console
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: Academic Free License (AFL)
|
||||
Natural Language :: English
|
||||
Operating System :: POSIX
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: 3.12
|
||||
Programming Language :: Python :: 3.13
|
||||
Topic :: Software Development
|
||||
Topic :: Software Development :: Code Generators
|
||||
Topic :: Software Development :: Testing
|
||||
description = Galaxy tool and tool dependency utilities
|
||||
keywords =
|
||||
Galaxy
|
||||
license = AFL
|
||||
license_files =
|
||||
LICENSE
|
||||
long_description = file: README.rst, HISTORY.rst
|
||||
long_description_content_type = text/x-rst
|
||||
name = galaxy-tool-util-models
|
||||
url = https://github.com/galaxyproject/galaxy
|
||||
version = 25.0.dev0
|
||||
|
||||
[options]
|
||||
include_package_data = True
|
||||
install_requires =
|
||||
pydantic>=2,!=2.6.0,!=2.6.1
|
||||
packages = find:
|
||||
python_requires = >=3.7
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
tests*
|
||||
@@ -0,0 +1 @@
|
||||
pytest
|
||||
@@ -0,0 +1 @@
|
||||
../../../test/unit/tool_util_models
|
||||
@@ -0,0 +1,12 @@
|
||||
from tool_shed_client.schema import Category
|
||||
|
||||
|
||||
def test_create_model():
|
||||
category = Category(
|
||||
id="1234567",
|
||||
name="my category",
|
||||
description="the description",
|
||||
deleted=False,
|
||||
repositories=3,
|
||||
)
|
||||
assert category.name == "my category"
|
||||
@@ -5,9 +5,9 @@ import lxml.etree as ET
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from galaxy.tool_util.verify.assertion_models import assertion_list
|
||||
from galaxy.tool_util.verify.codegen import galaxy_xsd_path
|
||||
from galaxy.tool_util.verify.parse import assertion_xml_els_to_models
|
||||
from galaxy.tool_util_models.assertions import assertion_list
|
||||
from galaxy.util.commands import shell
|
||||
from galaxy.util.unittest_utils import skip_unless_executable
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from galaxy.tool_util.parser.factory import get_tool_source
|
||||
from galaxy.tool_util.parser.output_models import from_tool_source
|
||||
from galaxy.tool_util.parser.output_objects import from_tool_source
|
||||
from galaxy.tool_util.unittest_utils import functional_test_tool_path
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from typing import (
|
||||
|
||||
import pytest
|
||||
|
||||
from galaxy.tool_util.models import parse_tool
|
||||
from galaxy.tool_util.model_factory import parse_tool
|
||||
from galaxy.tool_util.parameters import (
|
||||
DataCollectionRequest,
|
||||
DataRequestHda,
|
||||
@@ -25,13 +25,15 @@ from galaxy.tool_util.parameters.case import (
|
||||
)
|
||||
from galaxy.tool_util.parser.factory import get_tool_source
|
||||
from galaxy.tool_util.parser.interface import (
|
||||
JsonTestCollectionDefDict,
|
||||
JsonTestDatasetDefDict,
|
||||
ToolSource,
|
||||
ToolSourceTest,
|
||||
)
|
||||
from galaxy.tool_util.unittest_utils import functional_test_tool_directory
|
||||
from galaxy.tool_util.verify.parse import parse_tool_test_descriptions
|
||||
from galaxy.tool_util_models.tool_source import (
|
||||
JsonTestCollectionDefDict,
|
||||
JsonTestDatasetDefDict,
|
||||
)
|
||||
from .util import dict_verify_each
|
||||
|
||||
# legacy tools allows specifying parameter and repeat parameters without
|
||||
|
||||
@@ -11,13 +11,13 @@ from typing import (
|
||||
)
|
||||
|
||||
from galaxy.tool_util.parser.factory import get_tool_source
|
||||
from galaxy.tool_util.parser.output_models import (
|
||||
from_tool_source,
|
||||
from galaxy.tool_util.parser.output_objects import from_tool_source
|
||||
from galaxy.tool_util.unittest_utils import functional_test_tool_path
|
||||
from galaxy.tool_util_models.tool_outputs import (
|
||||
ToolOutput,
|
||||
ToolOutputCollection,
|
||||
ToolOutputDataset,
|
||||
)
|
||||
from galaxy.tool_util.unittest_utils import functional_test_tool_path
|
||||
from galaxy.util import galaxy_directory
|
||||
from galaxy.util.unittest import TestCase
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import List
|
||||
|
||||
import yaml
|
||||
|
||||
from galaxy.tool_util.models import Tests
|
||||
from galaxy.tool_util_models import Tests
|
||||
from galaxy.util import galaxy_directory
|
||||
from galaxy.util.unittest_utils import skip_unless_environ
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from galaxy.tool_util_models import Tests
|
||||
|
||||
simple_json_test = """
|
||||
[{ "doc": "Test simple use of __ZIP_COLLECTION__ in a workflow.",
|
||||
"job": {
|
||||
"test_input_1": "samp1 10.0 samp2 20.0 ",
|
||||
"test_input_2": "samp1 20.0 samp2 40.0 "
|
||||
},
|
||||
"outputs": {
|
||||
"out": {
|
||||
"asserts": [
|
||||
{"that": "has_text",
|
||||
"text": "samp1 10.0 samp2 20.0 samp1 20.0 samp2 40.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
"""
|
||||
|
||||
|
||||
def test_simple_validate():
|
||||
Tests.model_validate_json(simple_json_test)
|
||||
Reference in New Issue
Block a user