mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user