# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved. #
# This file is part of the AiiDA code. #
# #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.aiida.net #
###########################################################################
"""Module with utilities and data structures pertaining to links between nodes in the provenance graph."""
from collections import namedtuple
from enum import Enum
from .lang import isidentifier, type_check
__all__ = ('GraphTraversalRule', 'GraphTraversalRules', 'LinkType', 'validate_link_label')
[docs]
class LinkType(Enum):
"""A simple enum of allowed link types."""
CREATE = 'create'
RETURN = 'return'
INPUT_CALC = 'input_calc'
INPUT_WORK = 'input_work'
CALL_CALC = 'call_calc'
CALL_WORK = 'call_work'
GraphTraversalRule = namedtuple('GraphTraversalRule', ['link_type', 'direction', 'toggleable', 'default'])
"""A namedtuple that defines a graph traversal rule.
When starting from a certain sub set of nodes, the graph traversal rules specify which links should be followed to
add adjacent nodes to finally arrive at a set of nodes that represent a valid and consistent sub graph.
:param link_type: the `LinkType` that the rule applies to
:param direction: whether the link type should be followed backwards or forwards
:param toggleable: boolean to indicate whether the rule can be changed from the default value. If this is `False` it
means the default value can never be changed as it will result in an inconsistent graph.
:param default: boolean, the default value of the rule, if `True` means that the link type for the given direction
should be followed.
"""
[docs]
class GraphTraversalRules(Enum):
"""Graph traversal rules when deleting or exporting nodes."""
DEFAULT = {
'input_calc_forward': GraphTraversalRule(LinkType.INPUT_CALC, 'forward', True, False),
'input_calc_backward': GraphTraversalRule(LinkType.INPUT_CALC, 'backward', True, False),
'create_forward': GraphTraversalRule(LinkType.CREATE, 'forward', True, False),
'create_backward': GraphTraversalRule(LinkType.CREATE, 'backward', True, False),
'return_forward': GraphTraversalRule(LinkType.RETURN, 'forward', True, False),
'return_backward': GraphTraversalRule(LinkType.RETURN, 'backward', True, False),
'input_work_forward': GraphTraversalRule(LinkType.INPUT_WORK, 'forward', True, False),
'input_work_backward': GraphTraversalRule(LinkType.INPUT_WORK, 'backward', True, False),
'call_calc_forward': GraphTraversalRule(LinkType.CALL_CALC, 'forward', True, False),
'call_calc_backward': GraphTraversalRule(LinkType.CALL_CALC, 'backward', True, False),
'call_work_forward': GraphTraversalRule(LinkType.CALL_WORK, 'forward', True, False),
'call_work_backward': GraphTraversalRule(LinkType.CALL_WORK, 'backward', True, False)
}
DELETE = {
'input_calc_forward': GraphTraversalRule(LinkType.INPUT_CALC, 'forward', False, True),
'input_calc_backward': GraphTraversalRule(LinkType.INPUT_CALC, 'backward', False, False),
'create_forward': GraphTraversalRule(LinkType.CREATE, 'forward', True, True),
'create_backward': GraphTraversalRule(LinkType.CREATE, 'backward', False, True),
'return_forward': GraphTraversalRule(LinkType.RETURN, 'forward', False, False),
'return_backward': GraphTraversalRule(LinkType.RETURN, 'backward', False, True),
'input_work_forward': GraphTraversalRule(LinkType.INPUT_WORK, 'forward', False, True),
'input_work_backward': GraphTraversalRule(LinkType.INPUT_WORK, 'backward', False, False),
'call_calc_forward': GraphTraversalRule(LinkType.CALL_CALC, 'forward', True, True),
'call_calc_backward': GraphTraversalRule(LinkType.CALL_CALC, 'backward', False, True),
'call_work_forward': GraphTraversalRule(LinkType.CALL_WORK, 'forward', True, True),
'call_work_backward': GraphTraversalRule(LinkType.CALL_WORK, 'backward', False, True)
}
EXPORT = {
'input_calc_forward': GraphTraversalRule(LinkType.INPUT_CALC, 'forward', True, False),
'input_calc_backward': GraphTraversalRule(LinkType.INPUT_CALC, 'backward', False, True),
'create_forward': GraphTraversalRule(LinkType.CREATE, 'forward', False, True),
'create_backward': GraphTraversalRule(LinkType.CREATE, 'backward', True, True),
'return_forward': GraphTraversalRule(LinkType.RETURN, 'forward', False, True),
'return_backward': GraphTraversalRule(LinkType.RETURN, 'backward', True, False),
'input_work_forward': GraphTraversalRule(LinkType.INPUT_WORK, 'forward', True, False),
'input_work_backward': GraphTraversalRule(LinkType.INPUT_WORK, 'backward', False, True),
'call_calc_forward': GraphTraversalRule(LinkType.CALL_CALC, 'forward', False, True),
'call_calc_backward': GraphTraversalRule(LinkType.CALL_CALC, 'backward', True, True),
'call_work_forward': GraphTraversalRule(LinkType.CALL_WORK, 'forward', False, True),
'call_work_backward': GraphTraversalRule(LinkType.CALL_WORK, 'backward', True, True)
}
[docs]
def validate_link_label(link_label):
"""Validate the given link label.
Valid link labels adhere to the following restrictions:
* Has to be a valid python identifier
* Can only contain alphanumeric characters and underscores
* Can not start or end with an underscore
:raises TypeError: if the link label is not a string type
:raises ValueError: if the link label is invalid
"""
import re
message = f'invalid link label `{link_label}`: should be string type but is instead: {type(link_label)}'
type_check(link_label, str, message)
allowed_character_set = '[a-zA-Z0-9_]'
if link_label.endswith('_'):
raise ValueError('cannot end with an underscore')
if link_label.startswith('_'):
raise ValueError('cannot start with an underscore')
if re.sub(allowed_character_set, '', link_label):
raise ValueError('only alphanumeric and underscores are allowed')
if not isidentifier(link_label):
raise ValueError('not a valid python identifier')