"""Defines plugins for AiiDA nodes."""
# pylint: disable=redefined-builtin,too-few-public-methods,unused-argument
from typing import Any, Dict, List, Optional
import graphene as gr
from aiida import orm
from aiida_restapi.graphql.filter_syntax import parse_filter_str
from aiida_restapi.graphql.plugins import QueryPlugin
from .comments import CommentsQuery
from .logs import LogsQuery
from .orm_factories import (
ENTITY_DICT_TYPE,
fields_from_name,
multirow_cls_factory,
resolve_entity,
single_cls_factory,
)
from .utils import JSON, FilterString
Link = type('LinkObjectType', (gr.ObjectType,), fields_from_name('Link'))
[docs]
class LinkQuery(gr.ObjectType):
"""A link and its end node."""
link = gr.Field(Link)
# note: we must refer to this query using a string, to prevent circular dependencies
node = gr.Field('aiida_restapi.graphql.nodes.NodeQuery')
[docs]
class LinksQuery(multirow_cls_factory(LinkQuery, orm.nodes.Node, 'nodes')): # type: ignore[misc]
"""Query all AiiDA Links."""
[docs]
class NodeQuery(
single_cls_factory(orm.nodes.Node, exclude_fields=('attributes', 'extras')) # type: ignore[misc]
):
"""Query an AiiDA Node"""
attributes = JSON(
description='Variable attributes of the node',
filter=gr.List(
gr.String,
description='return an exact set of attributes keys (non-existent will return null)',
),
)
extras = JSON(
description='Variable extras (unsealed) of the node',
filter=gr.List(
gr.String,
description='return an exact set of extras keys (non-existent will return null)',
),
)
# TODO it would be ideal if the attributes/extras were filtered via the SQL query
[docs]
@staticmethod
def resolve_attributes(parent: Any, info: gr.ResolveInfo, filter: Optional[List[str]] = None) -> Dict[str, Any]:
"""Resolution function."""
attributes = parent.get('attributes')
if filter is None or attributes is None:
return attributes
return {key: attributes.get(key) for key in filter}
comments = gr.Field(CommentsQuery, description='Comments attached to a node')
logs = gr.Field(LogsQuery, description='Logs attached to a process node')
[docs]
@staticmethod
def resolve_logs(parent: Any, info: gr.ResolveInfo) -> dict:
"""Resolution function."""
# pass filter specification to CommentsQuery
filters = {}
filters['dbnode_id'] = parent['id']
return {'filters': filters}
incoming = gr.Field(LinksQuery, description='Query for incoming nodes', filters=FilterString())
[docs]
@staticmethod
def resolve_incoming(parent: Any, info: gr.ResolveInfo, filters: Optional[str] = None) -> dict:
"""Resolution function."""
# pass edge specification to LinksQuery
return {
'parent_id': parent['id'],
# this node is outgoing relative to the incoming nodes
'edge_type': 'outgoing',
'project_edge': True,
'filters': parse_filter_str(filters),
}
outgoing = gr.Field(LinksQuery, description='Query for outgoing nodes', filters=FilterString())
[docs]
@staticmethod
def resolve_outgoing(parent: Any, info: gr.ResolveInfo, filters: Optional[str] = None) -> dict:
"""Resolution function."""
# pass edge specification to LinksQuery
return {
'parent_id': parent['id'],
# this node is incoming relative to the outgoing nodes
'edge_type': 'incoming',
'project_edge': True,
'filters': parse_filter_str(filters),
}
ancestors = gr.Field(
'aiida_restapi.graphql.nodes.NodesQuery',
description='Query for ancestor nodes',
filters=FilterString(),
)
[docs]
@staticmethod
def resolve_ancestors(parent: Any, info: gr.ResolveInfo, filters: Optional[str] = None) -> dict:
"""Resolution function."""
# pass edge specification to LinksQuery
return {
'parent_id': parent['id'],
# this node is a descendant relative to the ancestor nodes
'edge_type': 'descendants',
'filters': parse_filter_str(filters),
}
descendants = gr.Field(
'aiida_restapi.graphql.nodes.NodesQuery',
description='Query for descendant nodes',
filters=FilterString(),
)
[docs]
@staticmethod
def resolve_descendants(parent: Any, info: gr.ResolveInfo, filters: Optional[str] = None) -> dict:
"""Resolution function."""
# pass edge specification to LinksQuery
return {
'parent_id': parent['id'],
# this node is an ancestor relative to the descendant nodes
'edge_type': 'ancestors',
'filters': parse_filter_str(filters),
}
[docs]
class NodesQuery(multirow_cls_factory(NodeQuery, orm.nodes.Node, 'nodes')): # type: ignore[misc]
"""Query all AiiDA Nodes"""
[docs]
def resolve_Node(
parent: Any,
info: gr.ResolveInfo,
id: Optional[int] = None,
uuid: Optional[str] = None,
) -> ENTITY_DICT_TYPE:
"""Resolution function."""
return resolve_entity(orm.nodes.Node, info, id, uuid)
[docs]
def resolve_Nodes(parent: Any, info: gr.ResolveInfo, filters: Optional[str] = None) -> dict:
"""Resolution function."""
# pass filter to NodesQuery
return {'filters': parse_filter_str(filters)}
NodeQueryPlugin = QueryPlugin(
'node',
gr.Field(NodeQuery, id=gr.Int(), uuid=gr.String(), description='Query for a single Node'),
resolve_Node,
)
NodesQueryPlugin = QueryPlugin(
'nodes',
gr.Field(NodesQuery, description='Query for multiple Nodes', filters=FilterString()),
resolve_Nodes,
)