Warning
This page was created from a pull request (#24).
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
mutation: RootMutation
}
type CommentQuery {
id: Int
uuid: ID
ctime: DateTime
mtime: DateTime
content: String
user_id: Int
dbnode_id: Int
}
type CommentsQuery {
count: Int
rows(limit: Int = 100, offset: Int = 0, orderBy: String = "id", orderAsc: Boolean = true): [CommentQuery]
}
type ComputerQuery {
id: Int
uuid: ID
name: String
hostname: String
description: String
scheduler_type: String
transport_type: String
metadata: JSON
nodes(filters: FilterString): NodesQuery
}
type ComputersQuery {
count: Int
rows(limit: Int = 100, offset: Int = 0, orderBy: String = "id", orderAsc: Boolean = true): [ComputerQuery]
}
scalar DateTime
type EntryPoints {
group: String
names: [String]
}
scalar FilterString
type GroupCreate {
created: Boolean
group: GroupQuery
}
type GroupQuery {
id: Int
uuid: ID
label: String
type_string: String
time: DateTime
description: String
extras: JSON
user_id: Int
nodes: NodesQuery
}
type GroupsQuery {
count: Int
rows(limit: Int = 100, offset: Int = 0, orderBy: String = "id", orderAsc: Boolean = true): [GroupQuery]
}
scalar JSON
type LinkObjectType {
id: Int
input_id: Int
output_id: Int
label: String
type: String
}
type LinkQuery {
link: LinkObjectType
node: NodeQuery
}
type LinksQuery {
count: Int
rows(limit: Int = 100, offset: Int = 0, orderBy: String = "id", orderAsc: Boolean = true): [LinkQuery]
}
type LogQuery {
id: Int
uuid: ID
time: DateTime
loggername: String
levelname: String
message: String
metadata: JSON
dbnode_id: Int
}
type LogsQuery {
count: Int
rows(limit: Int = 100, offset: Int = 0, orderBy: String = "id", orderAsc: Boolean = true): [LogQuery]
}
type NodeQuery {
id: Int
uuid: ID
node_type: String
process_type: String
label: String
description: String
ctime: DateTime
mtime: DateTime
user_id: Int
dbcomputer_id: Int
attributes(filter: [String]): JSON
extras(filter: [String]): JSON
comments: CommentsQuery
logs: LogsQuery
incoming(filters: FilterString): LinksQuery
outgoing(filters: FilterString): LinksQuery
ancestors(filters: FilterString): NodesQuery
descendants(filters: FilterString): NodesQuery
}
type NodesQuery {
count: Int
rows(limit: Int = 100, offset: Int = 0, orderBy: String = "id", orderAsc: Boolean = true): [NodeQuery]
}
type RootMutation {
groupCreate(description: String = "", label: String!, type_string: String): GroupCreate
}
type RootQuery {
rowLimitMax: Int
aiidaVersion: String
comment(id: Int, uuid: String): CommentQuery
comments(filters: FilterString): CommentsQuery
log(id: Int, uuid: String): LogQuery
logs(filters: FilterString): LogsQuery
node(id: Int, uuid: String): NodeQuery
nodes(filters: FilterString): NodesQuery
computer(id: Int, uuid: String): ComputerQuery
computers(filters: FilterString): ComputersQuery
aiidaEntryPointGroups: [String]
aiidaEntryPoints(group: String!): EntryPoints
group(id: Int, uuid: String): GroupQuery
groups(filters: FilterString): GroupsQuery
user(id: Int, email: String): UserQuery
users(filters: FilterString): UsersQuery
}
type UserQuery {
id: Int
email: String
first_name: String
last_name: String
institution: String
nodes(filters: FilterString): NodesQuery
}
type UsersQuery {
count: Int
rows(limit: Int = 100, offset: Int = 0, orderBy: String = "id", 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 YET IMPLEMENTED
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"
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: "5d490d77") {
label
}
}
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: "a6eb") {
id
label
nodes {
count
}
}
}