Skip to content

Commit

Permalink
More json schema API options...
Browse files Browse the repository at this point in the history
  • Loading branch information
jmchilton committed Dec 3, 2024
1 parent 4459622 commit 8d7ff7c
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 10 deletions.
2 changes: 2 additions & 0 deletions lib/galaxy/tool_util/parameters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
ValidationFunctionT,
)
from .state import (
HasToolParameters,
JobInternalToolState,
LandingRequestInternalToolState,
LandingRequestToolState,
Expand Down Expand Up @@ -140,6 +141,7 @@
"ToolState",
"TestCaseToolState",
"ToolParameterT",
"HasToolParameters",
"to_json_schema_string",
"test_case_state",
"validate_test_cases_for_tool_source",
Expand Down
13 changes: 13 additions & 0 deletions lib/galaxy/webapps/galaxy/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@
from galaxy.schema.fields import DecodedDatabaseIdField
from galaxy.security.idencoding import IdEncodingHelper
from galaxy.structured_app import StructuredApp
from galaxy.tool_util.parameters import (
HasToolParameters,
to_json_schema_string,
ToolState,
)
from galaxy.web.framework.decorators import require_admin_message
from galaxy.webapps.base.controller import BaseAPIController
from galaxy.webapps.galaxy.api.cbv import cbv
Expand Down Expand Up @@ -611,6 +616,14 @@ async def _as_form(**data):
return cls


def json_schema_response_for_tool_state_model(
state_type: Type[ToolState], has_parameters: HasToolParameters
) -> Response:
pydantic_model = state_type.parameter_model_for(has_parameters)
json_str = to_json_schema_string(pydantic_model)
return Response(content=json_str, media_type="application/json")


async def try_get_request_body_as_json(request: Request) -> Optional[Any]:
"""Returns the request body as a JSON object if the content type is JSON."""
if "application/json" in request.headers.get("content-type", ""):
Expand Down
64 changes: 63 additions & 1 deletion lib/galaxy/webapps/galaxy/api/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Path,
Query,
Request,
Response,
UploadFile,
)
from pydantic import UUID4
Expand Down Expand Up @@ -47,7 +48,12 @@
ToolLandingRequest,
ToolRequestModel,
)
from galaxy.tool_util.parameters import ToolParameterT
from galaxy.tool_util.parameters import (
LandingRequestToolState,
RequestToolState,
TestCaseToolState,
ToolParameterT,
)
from galaxy.tool_util.verify import ToolTestDescriptionDict
from galaxy.tools.evaluation import global_tool_errors
from galaxy.util.zipstream import ZipstreamWrapper
Expand All @@ -67,6 +73,7 @@
BaseGalaxyAPIController,
depends,
DependsOnTrans,
json_schema_response_for_tool_state_model,
LandingUuidPathParam,
Router,
)
Expand Down Expand Up @@ -209,6 +216,61 @@ def tool_inputs(
tool_run_ref = ToolRunReference(tool_id=tool_id, tool_version=tool_version, tool_uuid=None)
return self.service.inputs(trans, tool_run_ref)

@router.get(
"/api/tools/{tool_id}/parameter_request_schema",
operation_id="tools__parameter_request_schema",
summary="Return a JSON schema description of the tool's inputs for the tool request API that will be added to Galaxy at some point",
description="The tool request schema includes validation of map/reduce concepts that can be consumed by the tool execution API and not just the request for a single execution.",
)
def tool_state_request(
self,
tool_id: str = ToolIDPathParam,
tool_version: Optional[str] = ToolVersionQueryParam,
trans: ProvidesHistoryContext = DependsOnTrans,
) -> Response:
tool_run_ref = ToolRunReference(tool_id=tool_id, tool_version=tool_version, tool_uuid=None)
inputs = self.service.inputs(trans, tool_run_ref)
return json_schema_response_for_tool_state_model(
RequestToolState,
inputs,
)

@router.get(
"/api/tools/{tool_id}/parameter_landing_request_schema",
operation_id="tools__parameter_landing_request_schema",
summary="Return a JSON schema description of the tool's inputs for the tool landing request API.",
)
def tool_state_landing_request(
self,
tool_id: str = ToolIDPathParam,
tool_version: Optional[str] = ToolVersionQueryParam,
trans: ProvidesHistoryContext = DependsOnTrans,
) -> Response:
tool_run_ref = ToolRunReference(tool_id=tool_id, tool_version=tool_version, tool_uuid=None)
inputs = self.service.inputs(trans, tool_run_ref)
return json_schema_response_for_tool_state_model(
LandingRequestToolState,
inputs,
)

@router.get(
"/api/tools/{tool_id}/parameter_test_case_xml_schema",
operation_id="tools__parameter_test_case_xml_schema",
summary="Return a JSON schema description of the tool's inputs for test case construction.",
)
def tool_state_test_case_xml(
self,
tool_id: str = ToolIDPathParam,
tool_version: Optional[str] = ToolVersionQueryParam,
trans: ProvidesHistoryContext = DependsOnTrans,
) -> Response:
tool_run_ref = ToolRunReference(tool_id=tool_id, tool_version=tool_version, tool_uuid=None)
inputs = self.service.inputs(trans, tool_run_ref)
return json_schema_response_for_tool_state_model(
TestCaseToolState,
inputs,
)


class ToolsController(BaseGalaxyAPIController, UsesVisualizationMixin):
"""
Expand Down
27 changes: 27 additions & 0 deletions lib/galaxy_test/api/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from uuid import uuid4

import pytest
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from requests import (
get,
put,
Expand Down Expand Up @@ -247,6 +249,31 @@ def test_legacy_biotools_xref_injection(self):
assert xref["reftype"] == "bio.tools"
assert xref["value"] == "bwa"

@skip_without_tool("gx_int")
def test_tool_schemas(self):
tool_id = "gx_int"

def get_jsonschema(state_type: str):
schema_url = self._api_url(f"tools/{tool_id}/parameter_{state_type}_schema")
schema_response = get(schema_url)
schema_response.raise_for_status()
return schema_response.json()

request_schema = get_jsonschema("request")
validate(instance={"parameter": 5}, schema=request_schema)
with pytest.raises(ValidationError):
validate(instance={"parameter": "Foobar"}, schema=request_schema)

test_case_schema = get_jsonschema("test_case_xml")
validate(instance={"parameter": 5}, schema=test_case_schema)
with pytest.raises(ValidationError):
validate(instance={"parameter": "Foobar"}, schema=test_case_schema)

landing_schema = get_jsonschema("landing_request")
validate(instance={"parameter": 5}, schema=landing_schema)
with pytest.raises(ValidationError):
validate(instance={"parameter": "Foobar"}, schema=landing_schema)

@skip_without_tool("test_data_source")
@skip_if_github_down
def test_data_source_ok_request(self):
Expand Down
43 changes: 34 additions & 9 deletions lib/tool_shed/webapp/api2/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

from galaxy.tool_util.models import ParsedTool
from galaxy.tool_util.parameters import (
LandingRequestToolState,
RequestToolState,
to_json_schema_string,
TestCaseToolState,
)
from galaxy.webapps.galaxy.api import json_schema_response_for_tool_state_model
from tool_shed.context import SessionRequestContext
from tool_shed.managers.tools import (
parsed_tool_model_cached_for,
Expand Down Expand Up @@ -57,11 +59,6 @@
)


def json_schema_response(pydantic_model) -> Response:
json_str = to_json_schema_string(pydantic_model)
return Response(content=json_str, media_type="application/json")


@router.cbv
class FastAPITools:
app: ToolShedApp = depends(ToolShedApp)
Expand Down Expand Up @@ -158,15 +155,43 @@ def show_tool(

@router.get(
"/api/tools/{tool_id}/versions/{tool_version}/parameter_request_schema",
operation_id="tools__parameter_request_model",
operation_id="tools__parameter_request_schema",
summary="Return a JSON schema description of the tool's inputs for the tool request API that will be added to Galaxy at some point",
description="The tool request schema includes validation of map/reduce concepts that can be consumed by the tool execution API and not just the request for a single execution.",
)
def tool_state(
def tool_state_request(
self,
trans: SessionRequestContext = DependsOnTrans,
tool_id: str = TOOL_ID_PATH_PARAM,
tool_version: str = TOOL_VERSION_PATH_PARAM,
) -> Response:
parsed_tool = parsed_tool_model_cached_for(trans, tool_id, tool_version)
return json_schema_response_for_tool_state_model(RequestToolState, parsed_tool.inputs)

@router.get(
"/api/tools/{tool_id}/versions/{tool_version}/parameter_landing_request_schema",
operation_id="tools__parameter_landing_request_schema",
summary="Return a JSON schema description of the tool's inputs for the tool landing request API.",
)
def tool_state_landing_request(
self,
trans: SessionRequestContext = DependsOnTrans,
tool_id: str = TOOL_ID_PATH_PARAM,
tool_version: str = TOOL_VERSION_PATH_PARAM,
) -> Response:
parsed_tool = parsed_tool_model_cached_for(trans, tool_id, tool_version)
return json_schema_response_for_tool_state_model(LandingRequestToolState, parsed_tool.inputs)

@router.get(
"/api/tools/{tool_id}/versions/{tool_version}/parameter_test_case_xml_schema",
operation_id="tools__parameter_test_case_xml_schema",
summary="Return a JSON schema description of the tool's inputs for test case construction.",
)
def tool_state_test_case_xml(
self,
trans: SessionRequestContext = DependsOnTrans,
tool_id: str = TOOL_ID_PATH_PARAM,
tool_version: str = TOOL_VERSION_PATH_PARAM,
) -> Response:
parsed_tool = parsed_tool_model_cached_for(trans, tool_id, tool_version)
return json_schema_response(RequestToolState.parameter_model_for(parsed_tool.inputs))
return json_schema_response_for_tool_state_model(TestCaseToolState, parsed_tool.inputs)

0 comments on commit 8d7ff7c

Please sign in to comment.