Merge pull request #14482 from davelopez/22.05_fix_query_parameter_lists

[22.05] Fix Jobs API index to accept query parameters as lists
This commit is contained in:
Marius van den Beek
2022-08-19 13:23:32 +02:00
committed by GitHub
3 changed files with 119 additions and 26 deletions
+41
View File
@@ -18,6 +18,7 @@ from galaxy.schema import (
ValueFilterQueryParams,
)
from galaxy.schema.schema import UpdateDatasetPermissionsPayload
from galaxy.util import listify
SerializationViewQueryParam: Optional[str] = Query(
None,
@@ -142,3 +143,43 @@ def get_query_parameters_from_request_excluding(request: Request, exclude: Set[s
for param_name in exclude:
extra_params.pop(param_name, None)
return extra_params
def query_parameter_as_list(query):
"""Used as FastAPI dependable for query parameters that need to behave as a list of values separated by comma
or as multiple instances of the same parameter.
**Important**: the `query` annotation provided must define the `alias` exactly as the name of the actual parameter name.
Usage example:
```python
ValueQueryParam = Query(
default=None,
alias="value", # Important! this is the parameter name that will be displayed in the API docs
title="My Value",
description="A single value, a comma-separated list of values or a list of values.",
)
@router.get("/api/my_route")
def index(
self,
values: Optional[List[str]] = Depends(query_parameter_as_list(ValueQueryParam)),
):
...
```
This will render in the API docs as a single string query parameter but will make the following requests equivalent:
- `api/my_route?value=val1,val2,val3`
- `api/my_route?value=val1&value=val2&value=val3`
"""
def parse_elements(
elements: Optional[List[str]] = query,
) -> Optional[List[Any]]:
if query.default != Ellipsis and not elements:
return query.default
if elements and len(elements) == 1:
return listify(elements[0])
return elements
return parse_elements
+22 -26
View File
@@ -17,7 +17,10 @@ from typing import (
Union,
)
from fastapi import Query
from fastapi import (
Depends,
Query,
)
from galaxy import (
exceptions,
@@ -39,20 +42,13 @@ from galaxy.managers.jobs import (
from galaxy.schema.fields import EncodedDatabaseIdField
from galaxy.schema.schema import JobIndexSortByEnum
from galaxy.schema.types import OffsetNaiveDatetime
from galaxy.util import listify
from galaxy.web import (
expose_api,
expose_api_anonymous,
require_admin,
)
from galaxy.webapps.base.controller import UsesVisualizationMixin
from galaxy.webapps.galaxy.services.jobs import (
JobIndexPayload,
JobIndexViewEnum,
JobsService,
)
from galaxy.work.context import WorkRequestContext
from . import (
from galaxy.webapps.galaxy.api import (
BaseGalaxyAPIController,
depends,
DependsOnTrans,
@@ -60,16 +56,24 @@ from . import (
Router,
search_query_param,
)
from galaxy.webapps.galaxy.api.common import query_parameter_as_list
from galaxy.webapps.galaxy.services.jobs import (
JobIndexPayload,
JobIndexViewEnum,
JobsService,
)
from galaxy.work.context import WorkRequestContext
log = logging.getLogger(__name__)
router = Router(tags=["jobs"])
StateQueryParam: Optional[str] = Query(
StateQueryParam = Query(
default=None,
alias="state",
title="States",
description="Comma-separated list of states to filter job query on. If unspecified, jobs of any state may be returned.",
description="A list or comma-separated list of states to filter job query on. If unspecified, jobs of any state may be returned.",
)
UserDetailsQueryParam: bool = Query(
@@ -91,15 +95,17 @@ ViewQueryParam: JobIndexViewEnum = Query(
)
ToolIdQueryParam: Optional[str] = Query(
ToolIdQueryParam = Query(
default=None,
alias="tool_id",
title="Tool ID(s)",
description="Limit listing of jobs to those that match one of the included tool_ids. If none, all are returned",
)
ToolIdLikeQueryParam: Optional[str] = Query(
ToolIdLikeQueryParam = Query(
default=None,
alias="tool_id_like",
title="Tool ID Pattern(s)",
description="Limit listing of jobs to those that match one of the included tool ID sql-like patterns. If none, all are returned",
)
@@ -186,12 +192,12 @@ class FastAPIJobs:
def index(
self,
trans: ProvidesUserContext = DependsOnTrans,
state: Optional[str] = StateQueryParam,
states: Optional[List[str]] = Depends(query_parameter_as_list(StateQueryParam)),
user_details: bool = UserDetailsQueryParam,
user_id: Optional[EncodedDatabaseIdField] = UserIdQueryParam,
view: JobIndexViewEnum = ViewQueryParam,
tool_id: Optional[str] = ToolIdQueryParam,
tool_id_like: Optional[str] = ToolIdLikeQueryParam,
tool_ids: Optional[List[str]] = Depends(query_parameter_as_list(ToolIdQueryParam)),
tool_ids_like: Optional[List[str]] = Depends(query_parameter_as_list(ToolIdLikeQueryParam)),
date_range_min: Optional[Union[datetime, date]] = DateRangeMinQueryParam,
date_range_max: Optional[Union[datetime, date]] = DateRangeMaxQueryParam,
history_id: Optional[EncodedDatabaseIdField] = HistoryIdQueryParam,
@@ -202,16 +208,6 @@ class FastAPIJobs:
limit: int = LimitQueryParam,
offset: int = OffsetQueryParam,
) -> List[Dict[str, Any]]:
def optional_list(input: Optional[str]) -> Optional[List[str]]:
if input is None:
return None
else:
return listify(input)
states = optional_list(state)
tool_ids = optional_list(tool_id)
tool_ids_like = optional_list(tool_id_like)
payload = JobIndexPayload(
states=states,
user_details=user_details,
@@ -0,0 +1,56 @@
from typing import List
from fastapi.applications import FastAPI
from fastapi.param_functions import (
Depends,
Query,
)
from fastapi.testclient import TestClient
from galaxy.webapps.galaxy.api.common import query_parameter_as_list
app = FastAPI()
client = TestClient(app)
@app.get("/test/get_value_as_list")
async def get_value_as_list(
values: List[str] = Depends(query_parameter_as_list(Query(alias="value"))),
):
return values
def test_single_value():
query_params = "value=val"
result = _get_result_for_params(query_params)
assert len(result) == 1
assert result[0] == "val"
def test_list_as_comma_separated_values():
query_params = "value=val1,val2,val3"
result = _get_result_for_params(query_params)
assert len(result) == 3
assert result[0] == "val1"
assert result[1] == "val2"
assert result[2] == "val3"
def test_list_as_multiple_query_entries():
query_params = "value=val1&value=val2&value=val3"
result = _get_result_for_params(query_params)
assert len(result) == 3
assert result[0] == "val1"
assert result[1] == "val2"
assert result[2] == "val3"
def _get_result_for_params(query_params: str):
response = client.get(f"/test/get_value_as_list?{query_params}")
assert response.status_code == 200
result = response.json()
return result