diff --git a/sapporo-wes-spec-2.0.0.yml b/sapporo-wes-spec-2.0.0.yml index 75a77f2..a590041 100644 --- a/sapporo-wes-spec-2.0.0.yml +++ b/sapporo-wes-spec-2.0.0.yml @@ -947,9 +947,9 @@ info: ## Executive Summary - he Workflow Execution Service (WES) API provides a standard way for users to submit - workflow requests to workflow execution systems and monitor their execution. This - API lets users run a single workflow (currently [**CWL**](https://www.commonwl.org/) + The Workflow Execution Service (WES) API provides a standard way for users to + submit workflow requests to workflow execution systems and monitor their execution. + This API lets users run a single workflow (currently [**CWL**](https://www.commonwl.org/) or [**WDL**](http://www.openwdl.org/) formatted workflows, with other types potentially supported in the future) on multiple different platforms, clouds, and environments. @@ -1094,7 +1094,7 @@ paths: may not represent the latest state. - If you want to get the latest state of run, use `GET /runs/{run_id}` or - `GET /runs/{run_id}/status`.' + `GET /runs/{run_id}/status` or use `latest=true` query parameter.' operationId: list_runs_runs_get parameters: - description: OPTIONAL The preferred number of workflow runs to return in a @@ -1156,6 +1156,33 @@ paths: description: '**sapporo-wes-2.0.0 extension:**: Filter the runs based on the state (e.g., "COMPLETE", "RUNNING", etc.).' title: State + - description: '**sapporo-wes-2.0.0 extension:**: A list of run IDs to retrieve + specific runs.' + in: query + name: run_ids + required: false + schema: + anyOf: + - items: + type: string + type: array + - type: 'null' + description: '**sapporo-wes-2.0.0 extension:**: A list of run IDs to retrieve + specific runs.' + title: Run Ids + - description: '**sapporo-wes-2.0.0 extension:**: If True, return the latest + state of runs instead of the snapshot.' + in: query + name: latest + required: false + schema: + anyOf: + - type: boolean + - type: 'null' + default: false + description: '**sapporo-wes-2.0.0 extension:**: If True, return the latest + state of runs instead of the snapshot.' + title: Latest responses: '200': content: diff --git a/sapporo/database.py b/sapporo/database.py index e93b431..76ab7ae 100644 --- a/sapporo/database.py +++ b/sapporo/database.py @@ -150,6 +150,7 @@ def list_runs_db( page_token: Optional[str] = None, sort_order: Literal["asc", "desc"] = "desc", state: Optional[State] = None, + run_ids: Optional[List[str]] = None, username: Optional[str] = None, ) -> Tuple[List[Run], Optional[str]]: query = select(Run) @@ -165,6 +166,9 @@ def list_runs_db( else: query = query.order_by(Run.start_time.desc(), Run.run_id.desc()) # type: ignore # pylint: disable=E1101 + if run_ids is not None: + query = query.where(Run.run_id.in_(run_ids)) # type: ignore # pylint: disable=E1101 + if page_token is not None: token_data = _decode_page_token(page_token) start_time = datetime.fromisoformat(token_data["start_time"]) diff --git a/sapporo/routers.py b/sapporo/routers.py index 7934fa2..9d8de9a 100644 --- a/sapporo/routers.py +++ b/sapporo/routers.py @@ -51,7 +51,7 @@ async def get_service_info() -> ServiceInfo: **sapporo-wes-2.0.0 extension:** - This endpoint returns a snapshot that is aggregated every 30 minutes. It may not represent the latest state. -- If you want to get the latest state of run, use `GET /runs/{run_id}` or `GET /runs/{run_id}/status`. +- If you want to get the latest state of run, use `GET /runs/{run_id}` or `GET /runs/{run_id}/status` or use `latest=true` query parameter. """, response_model=RunListResponse, ) @@ -72,12 +72,24 @@ async def list_runs( None, description='**sapporo-wes-2.0.0 extension:**: Filter the runs based on the state (e.g., "COMPLETE", "RUNNING", etc.).', ), + run_ids: Optional[List[str]] = Query( + None, + description='**sapporo-wes-2.0.0 extension:**: A list of run IDs to retrieve specific runs.', + ), + latest: Optional[bool] = Query( + False, + description='**sapporo-wes-2.0.0 extension:**: If True, return the latest state of runs instead of the snapshot.', + ), token: Optional[str] = auth_depends_factory(), ) -> RunListResponse: username = token and extract_username(decode_token(token)) - (db_runs, next_page_token) = list_runs_db(page_size, page_token, sort_order, state, username) + (db_runs, next_page_token) = list_runs_db(page_size, page_token, sort_order, state, run_ids, username) + if latest: + runs = [create_run_summary(run.run_id) for run in db_runs] + else: + runs = db_runs_to_run_summaries(db_runs) return RunListResponse( - runs=db_runs_to_run_summaries(db_runs), + runs=runs, next_page_token=next_page_token, ) diff --git a/tests/py_tests/test_get_runs.py b/tests/py_tests/test_get_runs.py index df2de33..d722f29 100644 --- a/tests/py_tests/test_get_runs.py +++ b/tests/py_tests/test_get_runs.py @@ -107,3 +107,25 @@ def test_get_runs_state(mocker, tmpdir): # type: ignore assert response.status_code == 200 data = response.json() assert len(data["runs"]) == 2 + + +def test_get_runs_run_ids(mocker, tmpdir): # type: ignore + client = anyhow_get_test_client(None, mocker, tmpdir) + add_dummy_data() # type: ignore + + response = client.get("/runs?run_ids=63d1f6b7-1cfe-44b3-9f66-1640496b1b01&run_ids=4f3de7a3-acb2-4568-b046-5bb7e63fa84c") + assert response.status_code == 200 + data = response.json() + assert len(data["runs"]) == 2 + + +def test_get_runs_latest(mocker, tmpdir): # type: ignore + client = anyhow_get_test_client(None, mocker, tmpdir) + run_id = run_cwltool_remote_wf(client) # type: ignore + + response = client.get("/runs?latest=true") + assert response.status_code == 200 + data = response.json() + + assert len(data["runs"]) == 1 + assert data["runs"][0]["run_id"] == run_id