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
      }
    }
  }
}

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:

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 query

  • field: 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
    }
  }
}