aiida.restapi.common package#

Submodules#

Default configuration for the REST API

This file contains the exceptions that are raised by the RESTapi at the highest level, namely that of the interaction with the client. Their specificity resides into the fact that they return a message that is embedded into the HTTP response.

Example:#

…/api/v1/nodes/ … (TODO compete this with an actual example)

Other errors arising at deeper level, e.g. those raised by the QueryBuilder or internal errors, are not embedded into the HTTP response.

exception aiida.restapi.common.exceptions.RestFeatureNotAvailable[source]#

Bases: FeatureNotAvailable

If endpoint is not emplemented for given node type

__annotations__ = {}#
__module__ = 'aiida.restapi.common.exceptions'#
exception aiida.restapi.common.exceptions.RestInputValidationError[source]#

Bases: InputValidationError

If inputs passed in query strings are wrong

__annotations__ = {}#
__module__ = 'aiida.restapi.common.exceptions'#
exception aiida.restapi.common.exceptions.RestValidationError[source]#

Bases: ValidationError

If validation error in code.

E.g. more than one node available for given uuid

__annotations__ = {}#
__module__ = 'aiida.restapi.common.exceptions'#

Utility functions to work with node “full types” which are unique node identifiers.

A node’s full_type is defined as a string that uniquely defines the node type. A valid full_type is constructed by concatenating the node_type and process_type of a node with the FULL_TYPE_CONCATENATOR. Each segment of the full type can optionally be terminated by a single LIKE_OPERATOR_CHARACTER to indicate that the node_type or process_type should start with that value but can be followed by any amount of other characters. A full type is invalid if it does not contain exactly one FULL_TYPE_CONCATENATOR character. Additionally, each segment can contain at most one occurrence of the LIKE_OPERATOR_CHARACTER and it has to be at the end of the segment.

Examples of valid full types:

‘data.bool.Bool.|’ ‘process.calculation.calcfunction.%|%’ ‘process.calculation.calcjob.CalcJobNode.|aiida.calculations:arithmetic.add’ ‘process.calculation.calcfunction.CalcFunctionNode.|aiida.workflows:codtools.primitive_structure_from_cif’

Examples of invalid full types:

‘data.bool’ # Only a single segment without concatenator ‘data.|bool.Bool.|process.’ # More than one concatenator ‘process.calculation%.calcfunction.|aiida.calculations:arithmetic.add’ # Like operator not at end of segment ‘process.calculation%.calcfunction.%|aiida.calculations:arithmetic.add’ # More than one operator in segment

class aiida.restapi.common.identifiers.Namespace(namespace, path=None, label=None, full_type=None, counter=None, is_leaf=True)[source]#

Bases: MutableMapping

Namespace that can be used to map the node class hierarchy.

__abstractmethods__ = frozenset({})#
__delitem__(key)[source]#
__dict__ = mappingproxy({'__module__': 'aiida.restapi.common.identifiers', '__doc__': 'Namespace that can be used to map the node class hierarchy.', 'namespace_separator': '.', 'mapping_path_to_label': {'node': 'Node', 'node.data': 'Data', 'node.process': 'Process', 'node.process.calculation': 'Calculation', 'node.process.calculation.calcjob': 'Calculation job', 'node.process.calculation.calcfunction': 'Calculation function', 'node.process.workflow': 'Workflow', 'node.process.workflow.workchain': 'Work chain', 'node.process.workflow.workfunction': 'Work function'}, 'process_full_type_mapping': {'process.calculation.calcjob.': 'process.calculation.calcjob.CalcJobNode.|aiida.calculations:{plugin_name}.%', 'process.calculation.calcfunction.': 'process.calculation.calcfunction.CalcFunctionNode.|aiida.calculations:{plugin_name}.%', 'process.workflow.workfunction.': 'process.workflow.workfunction.WorkFunctionNode.|aiida.workflows:{plugin_name}.%', 'process.workflow.workchain.': 'process.workflow.workchain.WorkChainNode.|aiida.workflows:{plugin_name}.%'}, 'process_full_type_mapping_unplugged': {'process.calculation.calcjob.': 'process.calculation.calcjob.CalcJobNode.|{plugin_name}.%', 'process.calculation.calcfunction.': 'process.calculation.calcfunction.CalcFunctionNode.|{plugin_name}.%', 'process.workflow.workfunction.': 'process.workflow.workfunction.WorkFunctionNode.|{plugin_name}.%', 'process.workflow.workchain.': 'process.workflow.workchain.WorkChainNode.|{plugin_name}.%'}, '__str__': <function Namespace.__str__>, '__init__': <function Namespace.__init__>, '_infer_full_type': <function Namespace._infer_full_type>, '__iter__': <function Namespace.__iter__>, '__len__': <function Namespace.__len__>, '__delitem__': <function Namespace.__delitem__>, '__getitem__': <function Namespace.__getitem__>, '__setitem__': <function Namespace.__setitem__>, 'is_leaf': <property object>, 'get_description': <function Namespace.get_description>, 'create_namespace': <function Namespace.create_namespace>, '__dict__': <attribute '__dict__' of 'Namespace' objects>, '__weakref__': <attribute '__weakref__' of 'Namespace' objects>, '__abstractmethods__': frozenset(), '_abc_impl': <_abc._abc_data object>, '__annotations__': {}})#
__getitem__(key)[source]#
__init__(namespace, path=None, label=None, full_type=None, counter=None, is_leaf=True)[source]#

Construct a new node class namespace.

__iter__()[source]#
__len__()[source]#
__module__ = 'aiida.restapi.common.identifiers'#
__setitem__(key, port)[source]#
__str__()[source]#

Return str(self).

__weakref__#

list of weak references to the object

_abc_impl = <_abc._abc_data object>#
_infer_full_type(full_type)[source]#

Infer the full type based on the current namespace path and the given full type of the leaf.

create_namespace(name, **kwargs)[source]#

Create and return a new Namespace in this Namespace.

If the name is namespaced, the sub Namespaces will be created recursively, except if one of the namespaces is already occupied at any level by a Port in which case a ValueError will be thrown

Parameters:
  • name – name (potentially namespaced) of the port to create and return

  • kwargs – constructor arguments that will be used only for the construction of the terminal Namespace

Returns:

Namespace

Raises:

ValueError if any sub namespace is occupied by a non-Namespace port

get_description()[source]#

Return a dictionary with a description of the ports this namespace contains.

Nested PortNamespaces will be properly recursed and Ports will print their properties in a list

Returns:

a dictionary of descriptions of the Ports contained within this PortNamespace

property is_leaf#
mapping_path_to_label = {'node': 'Node', 'node.data': 'Data', 'node.process': 'Process', 'node.process.calculation': 'Calculation', 'node.process.calculation.calcfunction': 'Calculation function', 'node.process.calculation.calcjob': 'Calculation job', 'node.process.workflow': 'Workflow', 'node.process.workflow.workchain': 'Work chain', 'node.process.workflow.workfunction': 'Work function'}#
namespace_separator = '.'#
process_full_type_mapping = {'process.calculation.calcfunction.': 'process.calculation.calcfunction.CalcFunctionNode.|aiida.calculations:{plugin_name}.%', 'process.calculation.calcjob.': 'process.calculation.calcjob.CalcJobNode.|aiida.calculations:{plugin_name}.%', 'process.workflow.workchain.': 'process.workflow.workchain.WorkChainNode.|aiida.workflows:{plugin_name}.%', 'process.workflow.workfunction.': 'process.workflow.workfunction.WorkFunctionNode.|aiida.workflows:{plugin_name}.%'}#
process_full_type_mapping_unplugged = {'process.calculation.calcfunction.': 'process.calculation.calcfunction.CalcFunctionNode.|{plugin_name}.%', 'process.calculation.calcjob.': 'process.calculation.calcjob.CalcJobNode.|{plugin_name}.%', 'process.workflow.workchain.': 'process.workflow.workchain.WorkChainNode.|{plugin_name}.%', 'process.workflow.workfunction.': 'process.workflow.workfunction.WorkFunctionNode.|{plugin_name}.%'}#
aiida.restapi.common.identifiers.construct_full_type(node_type, process_type)[source]#

Return the full type, which uniquely identifies any Node with the given node_type and process_type.

Parameters:
  • node_type – the node_type of the Node

  • process_type – the process_type of the Node

Returns:

the full type, which is a unique identifier

aiida.restapi.common.identifiers.get_full_type_filters(full_type)[source]#

Return the QueryBuilder filters that will return all Nodes identified by the given full_type.

Parameters:

full_type – the full_type unique node identifier

Returns:

dictionary of filters to be passed for the filters keyword in QueryBuilder.append

Raises:
  • ValueError – if the full_type is invalid

  • TypeError – if the full_type is not a string type

aiida.restapi.common.identifiers.get_node_namespace(user_pk=None, count_nodes=False)[source]#

Return the full namespace of all available nodes in the current database.

Returns:

complete node Namespace

aiida.restapi.common.identifiers.load_entry_point_from_full_type(full_type)[source]#

Return the loaded entry point for the given full_type unique node identifier.

Parameters:

full_type – the full_type unique node identifier

Raises:
aiida.restapi.common.identifiers.validate_full_type(full_type)[source]#

Validate that the full_type is a valid full type unique node identifier.

Parameters:

full_type – a Node full type

Raises:
  • ValueError – if the full_type is invalid

  • TypeError – if the full_type is not a string type

Util methods

class aiida.restapi.common.utils.CustomJSONProvider(app: Flask)[source]#

Bases: DefaultJSONProvider

Custom json encoder for serialization. This has to be provided to the Flask app in order to replace the default encoder.

__annotations__ = {}#
__module__ = 'aiida.restapi.common.utils'#
default(obj, **kwargs)[source]#

Override serialization of DefaultJSONProvider for datetime and bytes objects.

Parameters:

obj – Object e.g. dict, list that will be serialized.

Returns:

Serialized object as a string.

class aiida.restapi.common.utils.Utils(**kwargs)[source]#

Bases: object

A class that gathers all the utility functions for parsing URI, validating request, pass it to the translator, and building HTTP response

An istance of Utils has to be included in the api class so that the configuration parameters used to build the api are automatically accessible by the methods of Utils without the need to import them from the config.py file.

__dict__ = mappingproxy({'__module__': 'aiida.restapi.common.utils', '__doc__': 'A class that gathers all the utility functions for parsing URI,\n    validating request, pass it to the translator, and building HTTP response\n\n    An istance of Utils has to be included in the api class so that the\n    configuration parameters used to build the api are automatically\n    accessible by the methods of Utils without the need to import them from\n    the config.py file.\n\n    ', 'op_conv_map': {'=': '==', '!=': '!==', '=in=': 'in', '=notin=': '!in', '>': '>', '<': '<', '>=': '>=', '<=': '<=', '=like=': 'like', '=ilike=': 'ilike'}, '__init__': <function Utils.__init__>, 'strip_api_prefix': <function Utils.strip_api_prefix>, 'split_path': <staticmethod(<function Utils.split_path>)>, 'parse_path': <function Utils.parse_path>, 'validate_request': <function Utils.validate_request>, 'paginate': <function Utils.paginate>, 'build_headers': <function Utils.build_headers>, 'build_response': <staticmethod(<function Utils.build_response>)>, 'build_datetime_filter': <staticmethod(<function Utils.build_datetime_filter>)>, 'build_translator_parameters': <function Utils.build_translator_parameters>, 'parse_query_string': <function Utils.parse_query_string>, '__dict__': <attribute '__dict__' of 'Utils' objects>, '__weakref__': <attribute '__weakref__' of 'Utils' objects>, '__annotations__': {}})#
__init__(**kwargs)[source]#

Sets internally the configuration parameters

__module__ = 'aiida.restapi.common.utils'#
__weakref__#

list of weak references to the object

static build_datetime_filter(dtobj)[source]#

This function constructs a filter for a datetime object to be in a certain datetime interval according to the precision.

The interval is [reference_datetime, reference_datetime + delta_time], where delta_time is a function fo the required precision.

This function should be used to replace a datetime filter based on the equality operator that is inehrently “picky” because it implies matching two datetime objects down to the microsecond or something, by a “tolerant” operator which checks whether the datetime is in an interval.

Returns:

a suitable entry of the filter dictionary

build_headers(rel_pages=None, url=None, total_count=None)[source]#

Construct the header dictionary for an HTTP response. It includes related pages, total count of results (before pagination).

Parameters:
  • rel_pages – a dictionary defining related pages (first, prev, next, last)

  • url – (string) the full url, i.e. the url that the client uses to get Rest resources

static build_response(status=200, headers=None, data=None)[source]#

Build the response

Parameters:
  • status – status of the response, e.g. 200=OK, 400=bad request

  • headers – dictionary for additional header k,v pairs, e.g. X-total-count=<number of rows resulting from query>

  • data – a dictionary with the data returned by the Resource

Returns:

a Flask response object

build_translator_parameters(field_list)[source]#

Takes a list of elements resulting from the parsing the query_string and elaborates them in order to provide translator-compliant instructions

Parameters:

field_list – a (nested) list of elements resulting from parsing the query_string

Returns:

the filters in the

op_conv_map = {'!=': '!==', '<': '<', '<=': '<=', '=': '==', '=ilike=': 'ilike', '=in=': 'in', '=like=': 'like', '=notin=': '!in', '>': '>', '>=': '>='}#
paginate(page, perpage, total_count)[source]#

Calculates limit and offset for the reults of a query, given the page and the number of restuls per page. Moreover, calculates the last available page and raises an exception if the required page exceeds that limit. If number of rows==0, only page 1 exists :param page: integer number of the page that has to be viewed :param perpage: integer defining how many results a page contains :param total_count: the total number of rows retrieved by the query :return: integers: limit, offset, rel_pages

parse_path(path_string, parse_pk_uuid=None)[source]#

Takes the path and parse it checking its validity. Does not parse “io”, “content” fields. I do not check the validity of the path, since I assume that this is done by the Flask routing methods.

Parameters:
  • path_string – the path string

  • parse_id_uuid – if ‘pk’ (‘uuid’) expects an integer (uuid starting pattern)

Returns:

resource_type (string) page (integer) node_id (string: uuid starting pattern, int: pk) query_type (string))

parse_query_string(query_string)[source]#

Function that parse the querystring, extracting infos for limit, offset, ordering, filters, attribute and extra projections. :param query_string (as obtained from request.query_string) :return: parsed values for the querykeys

static split_path(path)[source]#
Parameters:

path – entire path contained in flask request

Returns:

list of each element separated by ‘/’

strip_api_prefix(path)[source]#

Removes the PREFIX from an URL path. PREFIX must be defined in the config.py file:

PREFIX = "/api/v2"
path = "/api/v2/calculations/page/2"
strip_api_prefix(path) ==> "/calculations/page/2"
Parameters:

path – the URL path string

Returns:

the same URL without the prefix

validate_request(limit=None, offset=None, perpage=None, page=None, query_type=None, is_querystring_defined=False)[source]#

Performs various checks on the consistency of the request. Add here all the checks that you want to do, except validity of the page number that is done in paginate(). Future additional checks must be added here

aiida.restapi.common.utils.close_thread_connection(wrapper=None, enabled=None, adapter=None, proxy=<class 'FunctionWrapper'>)[source]#

Close the profile’s storage connection, for the current thread.

This decorator can be used for router endpoints. It is needed due to the server running in threaded mode, i.e., creating a new thread for each incoming request, and leaving connections unreleased.

Note, this is currently hard-coded to the PsqlDosBackend storage backend.

aiida.restapi.common.utils.list_routes()[source]#

List available routes