-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add /processes endpoint with methods POST (create new processes) and GET (list active processes).
- Loading branch information
Showing
5 changed files
with
305 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Declaration of FastAPI router for processes.""" | ||
|
||
from typing import List, Optional | ||
|
||
from aiida import orm | ||
from aiida.cmdline.utils.decorators import with_dbenv | ||
from aiida.common.exceptions import NotExistent | ||
from aiida.engine import submit | ||
from aiida.orm.querybuilder import QueryBuilder | ||
from aiida.plugins import load_entry_point_from_string | ||
from fastapi import APIRouter, Depends, HTTPException | ||
|
||
from aiida_restapi.models import Process, Process_Post, User | ||
|
||
from .auth import get_current_active_user | ||
|
||
router = APIRouter() | ||
|
||
|
||
def substitute_node(input_dict: dict) -> dict: | ||
"""Substitutes node ids with nodes""" | ||
node_ids = { | ||
key: node_id for key, node_id in input_dict.items() if not key.endswith(".uuid") | ||
} | ||
|
||
for key, value in input_dict.items(): | ||
if key not in node_ids.keys(): | ||
try: | ||
node_ids[key[:-5]] = orm.Node.get(uuid=value) | ||
except NotExistent as exc: | ||
raise HTTPException( | ||
status_code=404, | ||
detail="Node ID: {} does not exist.".format(value), | ||
) from exc | ||
|
||
return node_ids | ||
|
||
|
||
@router.get("/processes", response_model=List[Process]) | ||
@with_dbenv() | ||
async def read_processes() -> List[Process]: | ||
"""Get list of all processes""" | ||
|
||
return Process.get_entities() | ||
|
||
|
||
@router.get("/processes/projectable_properties", response_model=List[str]) | ||
async def get_processes_projectable_properties() -> List[str]: | ||
"""Get projectable properties for processes endpoint""" | ||
|
||
return Process.get_projectable_properties() | ||
|
||
|
||
@router.get("/processes/{proc_id}", response_model=Process) | ||
@with_dbenv() | ||
async def read_process(proc_id: int) -> Optional[Process]: | ||
"""Get process by id.""" | ||
qbobj = QueryBuilder() | ||
qbobj.append( | ||
orm.ProcessNode, filters={"id": proc_id}, project=["**"], tag="process" | ||
).limit(1) | ||
|
||
return qbobj.dict()[0]["process"] | ||
|
||
|
||
@router.post("/processes", response_model=Process) | ||
@with_dbenv() | ||
async def post_process( | ||
process: Process_Post, | ||
current_user: User = Depends( | ||
get_current_active_user | ||
), # pylint: disable=unused-argument | ||
) -> Optional[Process]: | ||
"""Create new process.""" | ||
process_dict = process.dict(exclude_unset=True, exclude_none=True) | ||
inputs = substitute_node(process_dict["inputs"]) | ||
entry_point = process_dict.get("process_entry_point") | ||
|
||
try: | ||
entry_point_process = load_entry_point_from_string(entry_point) | ||
except ValueError as exc: | ||
raise HTTPException( | ||
status_code=404, | ||
detail="Entry point '{}' not recognized.".format(entry_point), | ||
) from exc | ||
|
||
process_node = submit(entry_point_process, **inputs) | ||
|
||
return process_node |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Test the /processes endpoint""" | ||
|
||
|
||
def test_get_processes(example_processes, client): # pylint: disable=unused-argument | ||
"""Test listing existing processes.""" | ||
response = client.get("/processes/") | ||
|
||
assert response.status_code == 200 | ||
assert len(response.json()) == 12 | ||
|
||
|
||
def test_get_processes_projectable(client): | ||
"""Test get projectable properites for processes.""" | ||
response = client.get("/processes/projectable_properties") | ||
|
||
assert response.status_code == 200 | ||
assert response.json() == [ | ||
"id", | ||
"uuid", | ||
"node_type", | ||
"process_type", | ||
"label", | ||
"description", | ||
"ctime", | ||
"mtime", | ||
"user_id", | ||
"dbcomputer_id", | ||
"attributes", | ||
"extras", | ||
] | ||
|
||
|
||
def test_get_single_processes( | ||
example_processes, client | ||
): # pylint: disable=unused-argument | ||
"""Test retrieving a single processes.""" | ||
for proc_id in example_processes: | ||
response = client.get("/processes/{}".format(proc_id)) | ||
assert response.status_code == 200 | ||
|
||
|
||
def test_add_process( | ||
default_test_add_process, client, authenticate | ||
): # pylint: disable=unused-argument | ||
"""Test adding new process""" | ||
code_id, x_id, y_id = default_test_add_process | ||
response = client.post( | ||
"/processes", | ||
json={ | ||
"label": "test_new_process", | ||
"process_entry_point": "aiida.calculations:arithmetic.add", | ||
"inputs": { | ||
"code.uuid": code_id, | ||
"x.uuid": x_id, | ||
"y.uuid": y_id, | ||
"metadata": { | ||
"description": "Test job submission with the add plugin", | ||
}, | ||
}, | ||
}, | ||
) | ||
assert response.status_code == 200 | ||
|
||
|
||
def test_add_process_invalid_entry_point( | ||
default_test_add_process, client, authenticate | ||
): # pylint: disable=unused-argument | ||
"""Test adding new process with invalid entry point""" | ||
code_id, x_id, y_id = default_test_add_process | ||
response = client.post( | ||
"/processes", | ||
json={ | ||
"label": "test_new_process", | ||
"process_entry_point": "wrong_entry_point", | ||
"inputs": { | ||
"code.uuid": code_id, | ||
"x.uuid": x_id, | ||
"y.uuid": y_id, | ||
"metadata": { | ||
"description": "Test job submission with the add plugin", | ||
}, | ||
}, | ||
}, | ||
) | ||
assert response.status_code == 404 | ||
|
||
|
||
def test_add_process_invalid_node_id( | ||
default_test_add_process, client, authenticate | ||
): # pylint: disable=unused-argument | ||
"""Test adding new process with invalid Node ID""" | ||
code_id, x_id, _ = default_test_add_process | ||
response = client.post( | ||
"/processes", | ||
json={ | ||
"label": "test_new_process", | ||
"process_entry_point": "aiida.calculations:arithmetic.add", | ||
"inputs": { | ||
"code.uuid": code_id, | ||
"x.uuid": x_id, | ||
"y.uuid": "891a9efa-f90e-11eb-9a03-0242ac130003", | ||
"metadata": { | ||
"description": "Test job submission with the add plugin", | ||
}, | ||
}, | ||
}, | ||
) | ||
assert response.status_code == 404 | ||
assert response.json() == { | ||
"detail": "Node ID: 891a9efa-f90e-11eb-9a03-0242ac130003 does not exist." | ||
} |