GraphQL server (/graphql)#
What is GraphQL?#
From graphql.org:
GraphQL is a query language for APIs and a runtime for fulfilling those queries. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more.
Features:
Ask for what you need, get exactly that.
Get many resources in a single request.
Describe what’s possible with a clear schema.
Why GraphQL?#
GitHub provided a very concise blog of why they switched to GraphQL: https://github.blog/2016-09-14-the-github-graphql-api/
GraphQL represents a massive leap forward for API development. Type safety, introspection, generated documentation, and predictable responses benefit both the maintainers and consumers of our platform.
More so than this, GraphQL maps very well with the data structure of an AiiDA profile, and makes it very intuitive for clients to construct complex queries, for example:
{
aiidaVersion
aiidaEntryPointGroups
nodes(filters: "node_type LIKE '%Calc%' & mtime >= 2018-02-01") {
count
rows(limit: 10, offset: 10) {
uuid
node_type
mtime
incoming {
count
}
outgoing {
count
}
}
}
}
Example response
{
"data": {
"aiidaVersion": "1.6.3",
"aiidaEntryPointGroups": [
"aiida.calculations",
"aiida.cmdline.data",
"aiida.cmdline.data.structure.import",
"aiida.cmdline.computer.configure",
"aiida.data",
"aiida.groups",
"aiida.node",
"aiida.parsers",
"aiida.schedulers",
"aiida.tools.calculations",
"aiida.tools.data.orbitals",
"aiida.tools.dbexporters",
"aiida.tools.dbimporters",
"aiida.transports",
"aiida.workflows"
],
"nodes": {
"count": 17784,
"rows": [
{
"uuid": "0f487263-999e-42dd-a71a-66ed6c4d39ba",
"node_type": "process.calculation.calcjob.CalcJobNode.",
"mtime": "2018-02-03T07:18:35.129525+01:00",
"incoming": {
"count": 8
},
"outgoing": {
"count": 5
}
},
{
"uuid": "cff1e914-5a34-4930-9429-9dcc6d38feb1",
"node_type": "process.calculation.calcfunction.CalcFunctionNode.",
"mtime": "2018-02-03T07:18:35.129813+01:00",
"incoming": {
"count": 2
},
"outgoing": {
"count": 3
}
},
{
"uuid": "2be5f351-6dd7-4a68-9873-4bc1b3b581fb",
"node_type": "process.calculation.calcfunction.CalcFunctionNode.",
"mtime": "2018-02-03T07:18:35.129843+01:00",
"incoming": {
"count": 2
},
"outgoing": {
"count": 3
}
},
{
"uuid": "3e4c9793-49f1-4165-85e1-beaba5a1c4f0",
"node_type": "process.calculation.calcfunction.CalcFunctionNode.",
"mtime": "2018-02-03T07:18:35.130197+01:00",
"incoming": {
"count": 2
},
"outgoing": {
"count": 2
}
},
{
"uuid": "3c397478-06af-4b76-8de8-a01fa7248d13",
"node_type": "process.calculation.calcfunction.CalcFunctionNode.",
"mtime": "2018-02-03T07:18:35.130550+01:00",
"incoming": {
"count": 2
},
"outgoing": {
"count": 3
}
},
{
"uuid": "d872a924-b831-4c91-92a9-59945544cea8",
"node_type": "process.calculation.calcjob.CalcJobNode.",
"mtime": "2018-02-03T07:18:35.130714+01:00",
"incoming": {
"count": 8
},
"outgoing": {
"count": 4
}
},
{
"uuid": "dfe24253-9993-4abd-91c3-8ed0f2a4fd6f",
"node_type": "process.calculation.calcjob.CalcJobNode.",
"mtime": "2018-02-03T07:18:35.131319+01:00",
"incoming": {
"count": 7
},
"outgoing": {
"count": 6
}
},
{
"uuid": "1feace00-7282-481c-bf6c-51659ef5b115",
"node_type": "process.calculation.calcjob.CalcJobNode.",
"mtime": "2018-02-03T07:18:35.131384+01:00",
"incoming": {
"count": 8
},
"outgoing": {
"count": 6
}
},
{
"uuid": "899d7d18-4880-4942-b45b-2885ca341d55",
"node_type": "process.calculation.calcfunction.CalcFunctionNode.",
"mtime": "2018-02-03T07:18:35.131482+01:00",
"incoming": {
"count": 2
},
"outgoing": {
"count": 1
}
},
{
"uuid": "483f69f7-1f23-43ce-b9de-1bb5598083f3",
"node_type": "process.calculation.calcjob.CalcJobNode.",
"mtime": "2018-02-03T07:18:35.132315+01:00",
"incoming": {
"count": 8
},
"outgoing": {
"count": 6
}
}
]
}
}
}
The GraphQL schema#
The current Graphql schema is:
schema {
query: RootQuery
}
"""The root query"""
type RootQuery {
"""Maximum number of entity rows allowed to be returned from a query"""
rowLimitMax: Int
"""Version of aiida-core"""
aiidaVersion: String
"""Query for a single Comment"""
comment(id: Int, uuid: String): CommentQuery
"""Query for multiple Comments"""
comments(filters: FilterString): CommentsQuery
"""Query for a single Log"""
log(id: Int, uuid: String): LogQuery
"""Query for multiple Logs"""
logs(filters: FilterString): LogsQuery
"""Query for a single Node"""
node(id: Int, uuid: String): NodeQuery
"""Query for multiple Nodes"""
nodes(filters: FilterString): NodesQuery
"""Query for a single Computer"""
computer(id: Int, uuid: String): ComputerQuery
"""Query for multiple Computers"""
computers(filters: FilterString): ComputersQuery
"""List of the entrypoint group names"""
aiidaEntryPointGroups: [String]
"""List of the entrypoint names in a group"""
aiidaEntryPoints(group: String!): EntryPoints
"""Query for a single Group"""
group(id: Int, uuid: String, label: String): GroupQuery
"""Query for multiple Groups"""
groups(filters: FilterString): GroupsQuery
"""Query for a single User"""
user(id: Int, email: String): UserQuery
"""Query for multiple Users"""
users(filters: FilterString): UsersQuery
}
"""Query an AiiDA Comment"""
type CommentQuery {
"""Unique id (pk)"""
id: Int
"""Universally unique id"""
uuid: ID
"""Creation time"""
ctime: DateTime
"""Last modification time"""
mtime: DateTime
"""Content of the comment"""
content: String
"""Created by user id (pk)"""
user_id: Int
"""Associated node id (pk)"""
dbnode_id: Int
}
"""
The `DateTime` scalar type represents a DateTime
value as specified by
[iso8601](https://en.wikipedia.org/wiki/ISO_8601).
"""
scalar DateTime
"""Query all AiiDA Comments."""
type CommentsQuery {
"""Total number of rows of comments"""
count: Int
rows(
"""Maximum number of rows to return (no more than 100)"""
limit: Int = 100
"""Skip the first n rows"""
offset: Int = 0
"""Field to order rows by"""
orderBy: String = "id"
"""Sort field in ascending order, else descending."""
orderAsc: Boolean = true
): [CommentQuery]
}
"""A string adhering to the AiiDA filter syntax."""
scalar FilterString
"""Query an AiiDA Log"""
type LogQuery {
"""Unique id (pk)"""
id: Int
"""Universally unique id"""
uuid: ID
"""Creation time"""
time: DateTime
"""The loggers name"""
loggername: String
"""The log level"""
levelname: String
"""The log message"""
message: String
"""Metadata associated with the log"""
metadata: JSON
"""Associated node id (pk)"""
dbnode_id: Int
}
"""
Custom scalar type for JSON values that could be:
String, Boolean, Int, Float, List or Object.
"""
scalar JSON
"""Query all AiiDA Logs."""
type LogsQuery {
"""Total number of rows of logs"""
count: Int
rows(
"""Maximum number of rows to return (no more than 100)"""
limit: Int = 100
"""Skip the first n rows"""
offset: Int = 0
"""Field to order rows by"""
orderBy: String = "id"
"""Sort field in ascending order, else descending."""
orderAsc: Boolean = true
): [LogQuery]
}
"""Query an AiiDA Node"""
type NodeQuery {
"""Unique id (pk)"""
id: Int
"""Universally unique id"""
uuid: ID
"""Node type"""
node_type: String
"""Process type"""
process_type: String
"""Label of node"""
label: String
"""Description of node"""
description: String
"""Creation time"""
ctime: DateTime
"""Last modification time"""
mtime: DateTime
"""Created by user id (pk)"""
user_id: Int
"""Associated computer id (pk)"""
dbcomputer_id: Int
"""Variable attributes of the node"""
attributes(
"""return an exact set of attributes keys (non-existent will return null)"""
filter: [String]
): JSON
"""Variable extras (unsealed) of the node"""
extras(
"""return an exact set of extras keys (non-existent will return null)"""
filter: [String]
): JSON
"""Comments attached to a node"""
comments: CommentsQuery
"""Logs attached to a process node"""
logs: LogsQuery
"""Query for incoming nodes"""
incoming(filters: FilterString): LinksQuery
"""Query for outgoing nodes"""
outgoing(filters: FilterString): LinksQuery
"""Query for ancestor nodes"""
ancestors(filters: FilterString): NodesQuery
"""Query for descendant nodes"""
descendants(filters: FilterString): NodesQuery
}
"""Query all AiiDA Links."""
type LinksQuery {
"""Total number of rows of nodes"""
count: Int
rows(
"""Maximum number of rows to return (no more than 100)"""
limit: Int = 100
"""Skip the first n rows"""
offset: Int = 0
"""Field to order rows by"""
orderBy: String = "id"
"""Sort field in ascending order, else descending."""
orderAsc: Boolean = true
): [LinkQuery]
}
"""A link and its end node."""
type LinkQuery {
link: LinkObjectType
node: NodeQuery
}
type LinkObjectType {
"""Unique id (pk)"""
id: Int
"""Unique id (pk) of the input node"""
input_id: Int
"""Unique id (pk) of the output node"""
output_id: Int
"""The label of the link"""
label: String
"""The type of link"""
type: String
}
"""Query all AiiDA Nodes"""
type NodesQuery {
"""Total number of rows of nodes"""
count: Int
rows(
"""Maximum number of rows to return (no more than 100)"""
limit: Int = 100
"""Skip the first n rows"""
offset: Int = 0
"""Field to order rows by"""
orderBy: String = "id"
"""Sort field in ascending order, else descending."""
orderAsc: Boolean = true
): [NodeQuery]
}
"""Query an AiiDA Computer"""
type ComputerQuery {
"""Unique id (pk)"""
id: Int
"""Universally unique id"""
uuid: ID
"""Computer name"""
label: String
"""Identifier for the computer within the network"""
hostname: String
"""Description of the computer"""
description: String
"""Scheduler plugin type, to manage compute jobs"""
scheduler_type: String
"""Transport plugin type, to manage file transfers"""
transport_type: String
"""Metadata of the computer"""
metadata: JSON
nodes(filters: FilterString): NodesQuery
}
"""Query all AiiDA Computers"""
type ComputersQuery {
"""Total number of rows of computers"""
count: Int
rows(
"""Maximum number of rows to return (no more than 100)"""
limit: Int = 100
"""Skip the first n rows"""
offset: Int = 0
"""Field to order rows by"""
orderBy: String = "id"
"""Sort field in ascending order, else descending."""
orderAsc: Boolean = true
): [ComputerQuery]
}
"""
Return type from an entry point group and its list of registered names.
"""
type EntryPoints {
group: String
names: [String]
}
"""Query an AiiDA Group"""
type GroupQuery {
"""Unique id (pk)"""
id: Int
"""Universally unique id"""
uuid: ID
"""Label of group"""
label: String
"""type of the group"""
type_string: String
"""Created time"""
time: DateTime
"""Description of group"""
description: String
"""extra data about for the group"""
extras: JSON
"""Created by user id (pk)"""
user_id: Int
nodes: NodesQuery
}
"""Query all AiiDA Groups"""
type GroupsQuery {
"""Total number of rows of groups"""
count: Int
rows(
"""Maximum number of rows to return (no more than 100)"""
limit: Int = 100
"""Skip the first n rows"""
offset: Int = 0
"""Field to order rows by"""
orderBy: String = "id"
"""Sort field in ascending order, else descending."""
orderAsc: Boolean = true
): [GroupQuery]
}
"""Query an AiiDA User"""
type UserQuery {
"""Unique id (pk)"""
id: Int
"""Email address of the user"""
email: String
"""First name of the user"""
first_name: String
"""Last name of the user"""
last_name: String
"""Host institution or workplace of the user"""
institution: String
nodes(filters: FilterString): NodesQuery
}
"""Query all AiiDA Users"""
type UsersQuery {
"""Total number of rows of users"""
count: Int
rows(
"""Maximum number of rows to return (no more than 100)"""
limit: Int = 100
"""Skip the first n rows"""
offset: Int = 0
"""Field to order rows by"""
orderBy: String = "id"
"""Sort field in ascending order, else descending."""
orderAsc: Boolean = true
): [UserQuery]
}
Data Limits and Pagination#
The maximum number of rows of data returned is limited. To query this limit use:
{ rowLimitMax }
Use the offset option in conjunction with limit in order to retrieve all the rows of data over multiple requests. For example, for pages of length 50:
Page 1:
{
nodes {
count
rows(limit: 50, offset: 0) {
attributes
}
}
}
Page 2:
{
nodes {
count
rows(limit: 50, offset: 50) {
attributes
}
}
}
Filtering#
The filters option for computers, comments, groups, logs, nodes, and users, accepts a FilterString, which maps a string to the filters input of the QueryBuilder (see the reference table for more information).
For example:
{ nodes(filters: "node_type ILIKE '%Calc%' & mtime >= 2018-02-01") { count } }
maps to:
QueryBuilder().append(Node, filters={"node_type": {"ilike": "%Calc%"}, "mtime": {">=": datetime(2018, 2, 1, 0, 0)}}).count()
The syntax is defined by the following EBNF Grammar:
FilterString Syntax
// The AiiDA QueryBuilder filter grammar
// Defines an EBNF Grammar for specifying QueryBuilder filters as a string.
// (see https://github.com/lark-parser for more details)
// version: 0.1.0
filter: [SPACES] comparison ( AND comparison )*
// Comparisons
comparison: PROPERTY rhs_comparisons [SPACES]
rhs_comparisons: value_op_rhs
| fuzzy_string_op_rhs
| length_op_rhs
| contains_op_rhs
| is_in_op_rhs
| has_op_rhs
value_op_rhs: OPERATOR value
fuzzy_string_op_rhs: ILIKE STRING | LIKE STRING
length_op_rhs: [OF] LENGTH DIGITS
contains_op_rhs: CONTAINS valuelist
is_in_op_rhs: [IS] IN valuelist
has_op_rhs: HAS [KEY] ( STRING | PROPERTY )
// Values
value: STRING | FLOAT | INTEGER | PROPERTY | DATE | TIME | DATETIME
valuelist: value (COMMA value)*
// Separators
DOT: "." [SPACES]
COMMA: "," [SPACES]
COLON: ":" [SPACES]
SEMICOLON: ";" [SPACES]
AND: [SPACES] ("AND" | "&") [SPACES]
// Relations
OPERATOR: [SPACES] ( "<" [ "=" ] | ">" [ "=" ] | "!==" | "==" ) [SPACES]
LIKE: [SPACES] "LIKE" [SPACES]
ILIKE: [SPACES] "ILIKE" [SPACES]
OF: [SPACES] "OF" [SPACES]
LENGTH: [SPACES] "LENGTH" [SPACES]
CONTAINS: [SPACES] "CONTAINS" [SPACES]
IS: [SPACES] "IS" [SPACES]
IN: [SPACES] "IN" [SPACES]
HAS: [SPACES] "HAS" [SPACES]
KEY: [SPACES] "KEY" [SPACES]
// Datetime
// partial implementation of ISO 8601
DATE: DIGIT DIGIT DIGIT DIGIT "-" DIGIT DIGIT "-" DIGIT DIGIT
TIME: DIGIT DIGIT ":" DIGIT DIGIT | DIGIT DIGIT ":" DIGIT DIGIT ":" DIGIT DIGIT
DATETIME: DATE [SPACE] TIME
// Property
%import common.LCASE_LETTER -> LCASE_LETTER
IDENTIFIER: ( LCASE_LETTER | "_" ) ( LCASE_LETTER | "_" | DIGIT )*
PROPERTY: IDENTIFIER ( DOT IDENTIFIER )*
// Strings
%import common._STRING_ESC_INNER -> _STRING_ESC_INNER
STRING: "'" _STRING_ESC_INNER "'" | "\"" _STRING_ESC_INNER "\""
// Numbers
DIGIT: "0".."9"
DIGITS: DIGIT+
%import common.SIGNED_FLOAT
FLOAT: SIGNED_FLOAT
%import common.SIGNED_INT
INTEGER: SIGNED_INT
// White-space
SPACE: /[ \t\f\r\n]/
SPACES: SPACE+
Query Plugins#
All top-level queries are plugins.
A plugin is defined as a QueryPlugin object, which simply includes three items:
name: The name by which to call the queryfield: The graphene field to return (see graphene types reference)resolver: The function that resolves the field.
For example:
from aiida_restapi.graphql.plugins import QueryPlugin
import graphene as gr
def resolver(parent, info):
return "halloworld!"
myplugin = QueryPlugin(
name="myQuery",
field=gr.String(description="Return some data"),
resolver=resolver
)
Would be called like:
{ myQuery }
and return:
{
"myQuery": "halloworld!"
}
(TODO: loading plugins as entry points)
REST Migration Guide#
This section helps AiiDA users migrate API calls between the REST API built into aiida-core and the GraphQL API of this plugin.
Most of the listed calls are taken from the aiida-core documentation.
General#
http://localhost:5000/api/v4/server/endpoints
http://localhost:5000/graphql
It is important to note that (in contrast to REST) with GraphQL
you select the fields you want to retrieve, and
you can combine multiple queries in one request.
Nodes#
http://localhost:5000/api/v4/nodes?id=in=45,56,78
{
nodes(filters: "id IN 45,56,78") {
count
rows {
id
}
}
}
http://localhost:5000/api/v4/nodes?limit=2&offset=8&orderby=-id
{
nodes {
rows(limit: 2, offset: 8, orderBy: "id", orderAsc: false) {
id
}
}
}
http://localhost:5000/api/v4/nodes?attributes=true&attributes_filter=pbc1
{
nodes {
rows {
attributes(filter: ["pbc1"])
}
}
}
http://localhost:5000/api/v4/nodes/full_types
NOT YET SPECIFICALLY IMPLEMENTED (although this needs further investigation, because full types is basically not documented anywhere)
http://localhost:5000/api/v4/nodes/download_formats
Not implemented for GraphQL, please use the REST API for this use case.
http://localhost:5000/api/v4/nodes/12f95e1c
{ node(uuid: "dee1f869-c45e-40d9-9f9c-f492f4117f13") { uuid } }
Partial UUIDs are not yet implemented (but you can also select using id).
http://localhost:5000/api/v4/nodes/de83b1/links/incoming?limit=2
{
node(id: 1011) {
incoming {
rows(limit: 2) {
link {
label
type
}
node {
id
label
}
}
}
}
}
http://localhost:5000/api/v4/nodes/de83b1/links/incoming?full_type="data.dict.Dict.|"
{
node(id: 1011) {
incoming(filters: "node_type == 'data.dict.Dict.'") {
count
rows {
link {
label
type
}
node {
id
label
}
}
}
}
}
http://localhost:5000/api/v4/nodes/a67fba41/links/outgoing?full_type="data.dict.Dict.|"
{
node(id: 1011) {
outgoing(filters: "node_type == 'data.dict.Dict.'") {
count
rows {
link {
label
type
}
node {
id
label
}
}
}
}
}
http://localhost:5000/api/v4/nodes/ffe11/contents/attributes
{ node(uuid: "dee1f869-c45e-40d9-9f9c-f492f4117f13") { attributes } }
http://localhost:5000/api/v4/nodes/ffe11/contents/attributes?attributes_filter=append_text,is_local
{ node(uuid: "dee1f869-c45e-40d9-9f9c-f492f4117f13") { attributes(filter: ["append_text", "is_local"]) } }
http://localhost:5000/api/v4/nodes/ffe11/contents/comments
{
node(id: 1011) {
comments {
count
rows {
content
}
}
}
}
Repository based queries are not yet implemented:
http://localhost:5000/api/v4/nodes/ffe11/repo/list
http://localhost:5000/api/v4/nodes/ffe11/repo/contents?filename="aiida.in"
Not implemented for GraphQL, please use the REST API for this use case.
http://localhost:5000/api/v4/nodes/fafdsf/download?download_format=xsf
http://localhost:5000/api/v4/nodes?mtime>=2019-04-23
{
nodes(filters: "mtime>=2019-04-23") {
count
rows {
uuid
}
}
}
Processes#
NOT YET IMPLEMENTED
http://localhost:5000/api/v4/processes/8b95cd85/report
http://localhost:5000/api/v4/calcjobs/sffs241j/input_files
Computers#
http://localhost:5000/api/v4/computers?limit=3&offset=2&orderby=id
{
computers {
count
rows(limit: 3, offset: 3, orderBy: "id") {
id
}
}
http://localhost:5000/api/v4/computers/5d490d77
{
computer(uuid: "3d09ebd4-4bda-44c1-86c3-530a778911d5") {
label
}
}
Partial UUIDs are not yet implemented (but you can also select using id).
http://localhost:5000/api/v4/computers/?scheduler_type=in="slurm","pbs"
{
computers(filters: "scheduler_type IN slurm,pbs") {
count
rows {
scheduler_type
}
}
}
http://localhost:5000/api/v4/computers?orderby=+name
{
computers {
rows(orderBy: "name") {
id
}
}
}
http://localhost:5000/api/v4/computers/page/1?perpage=5
{
computers {
rows(limit: 5) {
id
}
}
}
Users#
http://localhost:5000/api/v4/users/
{
users {
count
rows {
id
}
}
}
http://localhost:5000/api/v4/users/?first_name=ilike="aii%"
{
users(filters: "first_name ILIKE 'aii%'") {
count
rows {
email
first_name
last_name
institution
}
}
}
Groups#
http://localhost:5000/api/v4/groups/?limit=10&orderby=-user_id
{
groups {
count
rows(limit: 10, orderBy: "user_id", orderAsc: false) {
id
}
}
}
http://localhost:5000/api/v4/groups/a6e5b
{
group(uuid: "3d09ebd4-4bda-44c1-86c3-530a778911d5") {
id
label
nodes {
count
}
}
}
Partial UUIDs are not yet implemented (but you can also select using id).