Skip to content

Commit

Permalink
models for tool landing request state...
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Oct 8, 2024
1 parent 3086131 commit bcf9fab
Show file tree
Hide file tree
Showing 9 changed files with 494 additions and 18 deletions.
12 changes: 12 additions & 0 deletions lib/galaxy/tool_util/parameters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
encode,
encode_test,
fill_static_defaults,
landing_decode,
landing_encode,
)
from .factory import (
from_input_source,
Expand Down Expand Up @@ -50,8 +52,10 @@
ToolParameterT,
validate_against_model,
validate_internal_job,
validate_internal_landing_request,
validate_internal_request,
validate_internal_request_dereferenced,
validate_landing_request,
validate_request,
validate_test_case,
validate_workflow_step,
Expand All @@ -60,6 +64,8 @@
)
from .state import (
JobInternalToolState,
LandingRequestInternalToolState,
LandingRequestToolState,
RequestInternalDereferencedToolState,
RequestInternalToolState,
RequestToolState,
Expand Down Expand Up @@ -119,8 +125,10 @@
"ValidationFunctionT",
"validate_against_model",
"validate_internal_job",
"validate_internal_landing_request",
"validate_internal_request",
"validate_internal_request_dereferenced",
"validate_landing_request",
"validate_request",
"validate_test_case",
"validate_workflow_step",
Expand All @@ -134,6 +142,8 @@
"RequestToolState",
"RequestInternalToolState",
"RequestInternalDereferencedToolState",
"LandingRequestToolState",
"LandingRequestInternalToolState",
"flat_state_path",
"keys_starting_with",
"visit_input_values",
Expand All @@ -143,6 +153,8 @@
"encode",
"encode_test",
"fill_static_defaults",
"landing_decode",
"landing_encode",
"dereference",
"WorkflowStepToolState",
"WorkflowStepLinkedToolState",
Expand Down
44 changes: 40 additions & 4 deletions lib/galaxy/tool_util/parameters/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
)
from .state import (
JobInternalToolState,
LandingRequestInternalToolState,
LandingRequestToolState,
RequestInternalDereferencedToolState,
RequestInternalToolState,
RequestToolState,
Expand Down Expand Up @@ -67,7 +69,7 @@
def decode(
external_state: RequestToolState, input_models: ToolParameterBundle, decode_id: Callable[[str], int]
) -> RequestInternalToolState:
"""Prepare an external representation of tool state (request) for storing in the database (request_internal)."""
"""Prepare an internal representation of tool state (request_internal) for storing in the database."""

external_state.validate(input_models)
decode_callback = _decode_callback_for(decode_id)
Expand All @@ -83,21 +85,55 @@ def decode(


def encode(
external_state: RequestInternalToolState, input_models: ToolParameterBundle, encode_id: EncodeFunctionT
internal_state: RequestInternalToolState, input_models: ToolParameterBundle, encode_id: EncodeFunctionT
) -> RequestToolState:
"""Prepare an external representation of tool state (request) for storing in the database (request_internal)."""
"""Prepare an external representation of tool state (request) from persisted state in the database (request_internal)."""

encode_callback = _encode_callback_for(encode_id)
request_state_dict = visit_input_values(
input_models,
external_state,
internal_state,
encode_callback,
)
request_state = RequestToolState(request_state_dict)
request_state.validate(input_models)
return request_state


def landing_decode(
external_state: LandingRequestToolState, input_models: ToolParameterBundle, decode_id: Callable[[str], int]
) -> LandingRequestInternalToolState:
"""Prepare an external representation of tool state (request) for storing in the database (request_internal)."""

external_state.validate(input_models)
decode_callback = _decode_callback_for(decode_id)
internal_state_dict = visit_input_values(
input_models,
external_state,
decode_callback,
)

internal_request_state = LandingRequestInternalToolState(internal_state_dict)
internal_request_state.validate(input_models)
return internal_request_state


def landing_encode(
internal_state: LandingRequestInternalToolState, input_models: ToolParameterBundle, encode_id: EncodeFunctionT
) -> LandingRequestToolState:
"""Prepare an external representation of tool state (request) for storing in the database (request_internal)."""

encode_callback = _encode_callback_for(encode_id)
request_state_dict = visit_input_values(
input_models,
internal_state,
encode_callback,
)
request_state = LandingRequestToolState(request_state_dict)
request_state.validate(input_models)
return request_state


def dereference(
internal_state: RequestInternalToolState, input_models: ToolParameterBundle, dereference: DereferenceCallable
) -> RequestInternalDereferencedToolState:
Expand Down
46 changes: 43 additions & 3 deletions lib/galaxy/tool_util/parameters/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
"request",
"request_internal",
"request_internal_dereferenced",
"landing_request",
"landing_request_internal",
"job_internal",
"test_case_xml",
"workflow_step",
Expand Down Expand Up @@ -219,6 +221,8 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam
requires_value = self.request_requires_value
if state_representation == "job_internal":
requires_value = True
elif _is_landing_request(state_representation):
requires_value = False
return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value)

@property
Expand All @@ -240,7 +244,12 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam
py_type = self.py_type
if state_representation == "workflow_step_linked":
py_type = allow_connected_value(py_type)
return dynamic_model_information_from_py_type(self, py_type)
requires_value = self.request_requires_value
if state_representation == "job_internal":
requires_value = True
elif _is_landing_request(state_representation):
requires_value = False
return dynamic_model_information_from_py_type(self, py_type, requires_value=requires_value)

@property
def request_requires_value(self) -> bool:
Expand Down Expand Up @@ -405,10 +414,19 @@ def py_type_test_case(self) -> Type:
def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation:
if state_representation == "request":
return allow_batching(dynamic_model_information_from_py_type(self, self.py_type), BatchDataInstance)
if state_representation == "landing_request":
return allow_batching(
dynamic_model_information_from_py_type(self, self.py_type, requires_value=False), BatchDataInstance
)
elif state_representation == "request_internal":
return allow_batching(
dynamic_model_information_from_py_type(self, self.py_type_internal), BatchDataInstanceInternal
)
elif state_representation == "landing_request_internal":
return allow_batching(
dynamic_model_information_from_py_type(self, self.py_type_internal, requires_value=False),
BatchDataInstanceInternal,
)
elif state_representation == "request_internal_dereferenced":
return allow_batching(
dynamic_model_information_from_py_type(self, self.py_type_internal_dereferenced),
Expand Down Expand Up @@ -455,6 +473,12 @@ def py_type_internal(self) -> Type:
def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation:
if state_representation == "request":
return allow_batching(dynamic_model_information_from_py_type(self, self.py_type))
elif state_representation == "landing_request":
return allow_batching(dynamic_model_information_from_py_type(self, self.py_type, requires_value=False))
elif state_representation == "landing_request_internal":
return allow_batching(
dynamic_model_information_from_py_type(self, self.py_type_internal, requires_value=False)
)
elif state_representation in ["request_internal", "request_internal_dereferenced"]:
return allow_batching(dynamic_model_information_from_py_type(self, self.py_type_internal))
elif state_representation == "job_internal":
Expand Down Expand Up @@ -600,7 +624,10 @@ def py_type(self) -> Type:
return AnyUrl

def pydantic_template(self, state_representation: StateRepresentationT) -> DynamicModelInformation:
return dynamic_model_information_from_py_type(self, self.py_type)
requires_value = self.request_requires_value
if _is_landing_request(state_representation):
requires_value = False
return dynamic_model_information_from_py_type(self, self.py_type, requires_value=requires_value)

@property
def request_requires_value(self) -> bool:
Expand Down Expand Up @@ -1080,9 +1107,14 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam
instance_class: Type[BaseModel] = create_field_model(
self.parameters, f"Repeat_{self.name}", state_representation
)
min_length = self.min
max_length = self.max
requires_value = self.request_requires_value
if state_representation == "job_internal":
requires_value = True
elif _is_landing_request(state_representation):
requires_value = False
min_length = 0 # in a landing request - parameters can be partially filled

initialize_repeat: Any
if requires_value:
Expand All @@ -1091,7 +1123,7 @@ def pydantic_template(self, state_representation: StateRepresentationT) -> Dynam
initialize_repeat = None

class RepeatType(RootModel):
root: List[instance_class] = Field(initialize_repeat, min_length=self.min, max_length=self.max) # type: ignore[valid-type]
root: List[instance_class] = Field(initialize_repeat, min_length=min_length, max_length=max_length) # type: ignore[valid-type]

return DynamicModelInformation(
self.name,
Expand Down Expand Up @@ -1382,6 +1414,8 @@ def create_method(tool: ToolParameterBundle, name: str = DEFAULT_MODEL_NAME) ->
create_request_model = create_model_factory("request")
create_request_internal_model = create_model_factory("request_internal")
create_request_internal_dereferenced_model = create_model_factory("request_internal_dereferenced")
create_landing_request_model = create_model_factory("landing_request")
create_landing_request_internal_model = create_model_factory("landing_request_internal")
create_job_internal_model = create_model_factory("job_internal")
create_test_case_model = create_model_factory("test_case_xml")
create_workflow_step_model = create_model_factory("workflow_step")
Expand Down Expand Up @@ -1413,6 +1447,10 @@ def create_field_model(
return pydantic_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)
Expand Down Expand Up @@ -1441,6 +1479,8 @@ def validate_request(tool: ToolParameterBundle, request: Dict[str, Any], name: s
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")
Expand Down
18 changes: 18 additions & 0 deletions lib/galaxy/tool_util/parameters/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

from .models import (
create_job_internal_model,
create_landing_request_internal_model,
create_landing_request_model,
create_request_internal_dereferenced_model,
create_request_internal_model,
create_request_model,
Expand Down Expand Up @@ -84,6 +86,22 @@ def _parameter_model_for(cls, parameters: ToolParameterBundle) -> Type[BaseModel
return create_request_internal_model(parameters)


class LandingRequestToolState(ToolState):
state_representation: Literal["landing_request"] = "landing_request"

@classmethod
def _parameter_model_for(cls, parameters: ToolParameterBundle) -> Type[BaseModel]:
return create_landing_request_model(parameters)


class LandingRequestInternalToolState(ToolState):
state_representation: Literal["landing_request_internal"] = "landing_request_internal"

@classmethod
def _parameter_model_for(cls, parameters: ToolParameterBundle) -> Type[BaseModel]:
return create_landing_request_internal_model(parameters)


class RequestInternalDereferencedToolState(ToolState):
state_representation: Literal["request_internal_dereferenced"] = "request_internal_dereferenced"

Expand Down
13 changes: 12 additions & 1 deletion lib/galaxy/tools/parameters/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ def __init__(self, tool, input_source, context=None):
self.hidden = input_source.get_bool("hidden", False)
self.refresh_on_change = input_source.get_bool("refresh_on_change", False)
self.optional = input_source.parse_optional()
self.optionality_inferred = False
self.is_dynamic = False
self.label = input_source.parse_label()
self.help = input_source.parse_help()
Expand Down Expand Up @@ -352,6 +351,7 @@ def parse_name(input_source):
class SimpleTextToolParameter(ToolParameter):
def __init__(self, tool, input_source):
input_source = ensure_input_source(input_source)
self.optionality_inferred = False
super().__init__(tool, input_source)
optional = input_source.get("optional", None)
if optional is not None:
Expand Down Expand Up @@ -405,6 +405,7 @@ class TextToolParameter(SimpleTextToolParameter):
def __init__(self, tool, input_source):
input_source = ensure_input_source(input_source)
super().__init__(tool, input_source)
self.profile = tool.profile
self.datalist = []
for title, value, _ in input_source.parse_static_options():
self.datalist.append({"label": title, "value": value})
Expand All @@ -420,6 +421,16 @@ def validate(self, value, trans=None):
):
return super().validate(value, trans)

@property
def wrapper_default() -> Optional[str]:
"""Handle change in default handling pre and post 23.0 profiles."""
profile = self.profile
legacy_behavior = (profile is None or Version(str(profile)) < Version("23.0"))
default_value = None
if self.optional and self.optionality_inferred and legacy_behavior:
default_value = ""
return default_value

def to_dict(self, trans, other_values=None):
d = super().to_dict(trans)
other_values = other_values or {}
Expand Down
16 changes: 6 additions & 10 deletions lib/galaxy/tools/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
Union,
)

from packaging.version import Version
from typing_extensions import TypeAlias

from galaxy.model import (
Expand All @@ -33,7 +32,10 @@
from galaxy.model.metadata import FileParameter
from galaxy.model.none_like import NoneDataset
from galaxy.security.object_wrapper import wrap_with_safe_string
from galaxy.tools.parameters.basic import BooleanToolParameter
from galaxy.tools.parameters.basic import (
BooleanToolParameter,
TextToolParameter,
)
from galaxy.tools.parameters.wrapped_json import (
data_collection_input_to_staging_path_and_source_path,
data_input_to_staging_path_and_source_path,
Expand Down Expand Up @@ -126,15 +128,9 @@ def __init__(
profile: Optional[float] = None,
) -> None:
self.input = input
if (
value is None
and input.type == "text"
and input.optional
and input.optionality_inferred
and (profile is None or Version(str(profile)) < Version("23.0"))
):
if value is None and input.type == "text":
# Tools with old profile versions may treat an optional text parameter as `""`
value = ""
value = cast(TextToolParameter, input).wrapper_default()
self.value = value
self._other_values: Dict[str, str] = other_values or {}

Expand Down
Loading

0 comments on commit bcf9fab

Please sign in to comment.