Skip to content

Commit

Permalink
Docs: Add example for how to get a node by uuid
Browse files Browse the repository at this point in the history
  • Loading branch information
agoscinski committed May 22, 2024
1 parent a6744e9 commit 1f2a338
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 0 deletions.
57 changes: 57 additions & 0 deletions examples/node_managment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Process Management

## Description

This example shows how to manage processes over the web API, such as checking the status and retrieving outputs.
First, the example submits an `ArithmeticAddCalculation` (a calculation plugin that ships with `aiida-core`) to the daemon.
Then, the status of the calculation is queried for and when it is done, the final results are retrieved.

## Instructions

### Server

1. Install `aiida-restapi`:

```bash
pip install aiida-restapi[auth]
```

1. Start the web API server:

```bash
uvicorn aiida_restapi:app
```

1. Configure an `InstalledCode` to run the `core.arithmetic.add` plugin on a `Computer`:

```bash
verdi computer setup -n -L localhost -H localhost -T core.local -S core.direct -w /tmp
verdi computer configure core.local localhost -n
verdi code create core.code.installed \
--non-interactive \
--label 'bash' \
--computer localhost \
--filepath-executable /bin/bash \
--default-calc-job-plugin 'core.arithmetic.add'
```

1. Start the daemon

```bash
verdi daemon start
```


### Client

1. Install Python prerequisites:

```bash
pip install click requests
```

1. Execute the example script:

```bash
./script.py
```
141 changes: 141 additions & 0 deletions examples/node_managment/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Example script to demonstrate process management over the web API."""
from __future__ import annotations

import os
import time
import typing as t

import click
import requests

BASE_URL = "http://127.0.0.1:8000"


def echo_error(message: str) -> None:
"""Echo the message prefixed with ``Error`` in bold red.
:param message: The error message to echo.
"""
click.echo(click.style("Error: ", fg="red", bold=True), nl=False)
click.echo(message)


def request(
url,
json: dict[str, t.Any] | None = None,
data: dict[str, t.Any] | None = None,
method="POST",
) -> dict[str, t.Any] | None:
"""Perform a request to the web API of ``aiida-restapi``.
If the ``ACCESS_TOKEN`` environment variable is defined, it is passed in the ``Authorization`` header.
:param url: The relative URL path without leading slash, e.g., `nodes`.
:param json: A JSON serializable dictionary to send in the body of the request.
:param data: Dictionary, list of tuples, bytes, or file-like object to send in the body of the request.
:param method: The request method, POST by default.
:returns: The response in JSON or ``None``.
"""
access_token = os.getenv("ACCESS_TOKEN", None)

if access_token:
headers = {"Authorization": f"Bearer {access_token}"}
else:
headers = {}

url = f"{BASE_URL}/{url}"
click.echo(f"Sending request:\n method:\n {method}\n url:\n {url}\n"
f"json:\n {json}\n data:\n {data}\n headers:\n {headers}\n")
response = requests.request( # pylint: disable=missing-timeout
method, url, json=json, data=data, headers=headers
)

try:
response.raise_for_status()
except requests.HTTPError:
results = response.json()

echo_error(f"{response.status_code} {response.reason}")

if "detail" in results:
echo_error(results["detail"])

for error in results.get("errors", []):
click.echo(error["message"])

return None
return response.json()


def authenticate(
username: str = "[email protected]", password: str = "secret"
) -> str | None:
"""Authenticate with the web API to obtain an access token.
Note that if authentication is successful, the access token is stored in the ``ACCESS_TOKEN`` environment variable.
:param username: The username.
:param password: The password.
:returns: The access token or ``None`` if authentication was unsuccessful.
"""
results = request("token", data={"username": username, "password": password})

if results:
access_token = results["access_token"]
os.environ["ACCESS_TOKEN"] = access_token
return access_token

return None

def get_code(uuid: str) -> dict[str, t.Any] | None:
"""Return a code that has the given default calculation job plugin.
Returns the first code that is matched.
:param default_calc_job_plugin: The default calculation job plugin the code should have.
:raises ValueError: If no code could be found.
"""
# PR COMMENT: IS seems to be not working so I used LIKE
# replace at the end is bad coding style but is more convenient
variables = {"uuid": uuid}
query = """
{
nodes(filters: "uuid LIKE 'UUID_VARIABLE'") {
rows {
uuid
label
attributes
}
}
}
""".replace("UUID_VARIABLE", uuid)
results = request("graphql", {"query": query, "variables": variables})
return results

@click.command()
def main():
"""Authenticate with the web API and submit an ``ArithmeticAddCalculation``."""
token = authenticate()

if token is None:
echo_error("Could not authenticate with the API, aborting")
return

# PR COMMENT Please use uuid from a node that exist
#uuid = "a2bb48e1-ece8-4658-a643-2381851f8be8"
uuid = "cc72e31f-f7be-45e1-92df-b8af7af9b5a2"
output = get_code(uuid)
# PR COMMENT for debugging
#click.echo(f"Output whole:\n{output}")
if output is not None:
click.echo(f"Found {len(output['data']['nodes']['rows'])} result(s)")
if len(output['data']['nodes']['rows']) == 1:
click.echo(f"Successfully found node with uuid {uuid!r}:")
click.echo(f"{output['data']['nodes']['rows'][0]}")



if __name__ == "__main__":
main() # pylint: disable=no-value-for-parameter

0 comments on commit 1f2a338

Please sign in to comment.