Source code for aiida.restapi.resources

# -*- 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               #
###########################################################################
""" Resources for REST API """

from urllib import unquote

from flask import request, make_response
from flask_restful import Resource

from aiida.restapi.common.utils import Utils


# pylint: disable=missing-docstring,fixme
[docs]class ServerInfo(Resource):
[docs] def __init__(self, **kwargs): # Configure utils utils_conf_keys = ('PREFIX', 'PERPAGE_DEFAULT', 'LIMIT_DEFAULT') self.utils_confs = { k: kwargs[k] for k in utils_conf_keys if k in kwargs } self.utils = Utils(**self.utils_confs)
[docs] def get(self): """ It returns the general info about the REST API :return: returns current AiiDA version defined in aiida/__init__.py """ ## Decode url parts path = unquote(request.path) query_string = unquote(request.query_string) url = unquote(request.url) url_root = unquote(request.url_root) pathlist = self.utils.split_path(self.utils.strip_prefix(path)) if len(pathlist) > 1: resource_type = pathlist.pop(1) else: resource_type = "info" response = {} import aiida.restapi.common.config as conf from aiida import __version__ if resource_type == "info": response = [] # Add Rest API version response.append("REST API version: " + conf.PREFIX.split("/")[-1]) # Add Rest API prefix response.append("REST API Prefix: " + conf.PREFIX) # Add AiiDA version response.append("AiiDA==" + __version__) elif resource_type == "endpoints": from aiida.restapi.common.utils import list_routes response["available_endpoints"] = list_routes() headers = self.utils.build_headers(url=request.url, total_count=1) ## Build response and return it data = dict( method=request.method, url=url, url_root=url_root, path=path, query_string=query_string, resource_type="Info", data=response) return self.utils.build_response(status=200, headers=headers, data=data)
## TODO add the caching support. I cache total count, results, and possibly # set_query
[docs]class BaseResource(Resource): """ Each derived class will instantiate a different type of translator. This is the only difference in the classes. """
[docs] def __init__(self, **kwargs): self.trans = None # Flag to tell the path parser whether to expect a pk or a uuid pattern self.parse_pk_uuid = None # Configure utils utils_conf_keys = ('PREFIX', 'PERPAGE_DEFAULT', 'LIMIT_DEFAULT') self.utils_confs = { k: kwargs[k] for k in utils_conf_keys if k in kwargs } self.utils = Utils(**self.utils_confs) self.method_decorators = {'get': kwargs.get('get_decorators', [])}
# pylint: disable=too-many-locals,redefined-builtin,invalid-name
[docs] def get(self, id=None, page=None): """ Get method for the Computer resource :return: """ ## Decode url parts path = unquote(request.path) query_string = unquote(request.query_string) url = unquote(request.url) url_root = unquote(request.url_root) ## Parse request (resource_type, page, id, query_type) = self.utils.parse_path( path, parse_pk_uuid=self.parse_pk_uuid) (limit, offset, perpage, orderby, filters, _alist, _nalist, _elist, _nelist, _downloadformat, _visformat, _filename, _rtype) = self.utils.parse_query_string(query_string) ## Validate request self.utils.validate_request( limit=limit, offset=offset, perpage=perpage, page=page, query_type=query_type, is_querystring_defined=(bool(query_string))) ## Treat the schema case which does not imply access to the DataBase if query_type == 'schema': ## Retrieve the schema results = self.trans.get_schema() ## Build response and return it headers = self.utils.build_headers(url=request.url, total_count=1) else: ## Set the query, and initialize qb object self.trans.set_query(filters=filters, orders=orderby, id=id) ## Count results total_count = self.trans.get_total_count() ## Pagination (if required) if page is not None: (limit, offset, rel_pages) = self.utils.paginate( page, perpage, total_count) self.trans.set_limit_offset(limit=limit, offset=offset) headers = self.utils.build_headers( rel_pages=rel_pages, url=request.url, total_count=total_count) else: self.trans.set_limit_offset(limit=limit, offset=offset) headers = self.utils.build_headers( url=request.url, total_count=total_count) ## Retrieve results results = self.trans.get_results() ## Build response and return it data = dict( method=request.method, url=url, url_root=url_root, path=request.path, id=id, query_string=request.query_string, resource_type=resource_type, data=results) return self.utils.build_response(status=200, headers=headers, data=data)
[docs]class Node(Resource): """ Differs from BaseResource in trans.set_query() mostly because it takes query_type as an input and the presence of additional result types like "tree" """
[docs] def __init__(self, **kwargs): # Set translator from aiida.restapi.translator.node import NodeTranslator self.trans = NodeTranslator(**kwargs) from aiida.orm import Node as tNode self.tclass = tNode # Parse a uuid pattern in the URL path (not a pk) self.parse_pk_uuid = 'uuid' # Configure utils utils_conf_keys = ('PREFIX', 'PERPAGE_DEFAULT', 'LIMIT_DEFAULT') self.utils_confs = { k: kwargs[k] for k in utils_conf_keys if k in kwargs } self.utils = Utils(**self.utils_confs) self.method_decorators = {'get': kwargs.get('get_decorators', [])}
#pylint: disable=too-many-locals,too-many-statements #pylint: disable=redefined-builtin,invalid-name,too-many-branches
[docs] def get(self, id=None, page=None): """ Get method for the Node resource. :return: """ ## Decode url parts path = unquote(request.path) query_string = unquote(request.query_string) url = unquote(request.url) url_root = unquote(request.url_root) ## Parse request (resource_type, page, id, query_type) = self.utils.parse_path( path, parse_pk_uuid=self.parse_pk_uuid) (limit, offset, perpage, orderby, filters, alist, nalist, elist, nelist, downloadformat, visformat, filename, rtype) = self.utils.parse_query_string(query_string) ## Validate request self.utils.validate_request( limit=limit, offset=offset, perpage=perpage, page=page, query_type=query_type, is_querystring_defined=(bool(query_string))) ## Treat the schema case which does not imply access to the DataBase if query_type == 'schema': ## Retrieve the schema results = self.trans.get_schema() ## Build response and return it headers = self.utils.build_headers(url=request.url, total_count=1) ## Treat the statistics elif query_type == "statistics": (limit, offset, perpage, orderby, filters, alist, nalist, elist, nelist, downloadformat, visformat, filename, rtype) = self.utils.parse_query_string(query_string) headers = self.utils.build_headers(url=request.url, total_count=0) if filters: usr = filters["user"]["=="] else: usr = None results = self.trans.get_statistics(usr) # TODO Might need to be improved elif query_type == "tree": headers = self.utils.build_headers(url=request.url, total_count=0) results = self.trans.get_io_tree(id) else: ## Initialize the translator self.trans.set_query( filters=filters, orders=orderby, query_type=query_type, id=id, alist=alist, nalist=nalist, elist=elist, nelist=nelist, downloadformat=downloadformat, visformat=visformat, filename=filename, rtype=rtype) ## Count results total_count = self.trans.get_total_count() ## Pagination (if required) if page is not None: (limit, offset, rel_pages) = self.utils.paginate( page, perpage, total_count) self.trans.set_limit_offset(limit=limit, offset=offset) ## Retrieve results results = self.trans.get_results() headers = self.utils.build_headers( rel_pages=rel_pages, url=request.url, total_count=total_count) else: self.trans.set_limit_offset(limit=limit, offset=offset) ## Retrieve results results = self.trans.get_results() if query_type == "download" and results: if results["download"]["status"] == 200: data = results["download"]["data"] response = make_response(data) response.headers[ 'content-type'] = 'application/octet-stream' response.headers[ 'Content-Disposition'] = 'attachment; filename="{}"'.format( results["download"]["filename"]) return response else: results = results["download"]["data"] if query_type in ["retrieved_inputs", "retrieved_outputs" ] and results: try: status = results[query_type]["status"] except KeyError: status = "" except TypeError: status = "" if status == 200: data = results[query_type]["data"] response = make_response(data) response.headers[ 'content-type'] = 'application/octet-stream' response.headers[ 'Content-Disposition'] = 'attachment; filename="{}"'.format( results[query_type]["filename"]) return response elif status == 500: results = results[query_type]["data"] headers = self.utils.build_headers( url=request.url, total_count=total_count) ## Build response data = dict( method=request.method, url=url, url_root=url_root, path=path, id=id, query_string=query_string, resource_type=resource_type, data=results) return self.utils.build_response(status=200, headers=headers, data=data)
[docs]class Computer(BaseResource):
[docs] def __init__(self, **kwargs): super(Computer, self).__init__(**kwargs) ## Instantiate the correspondent translator from aiida.restapi.translator.computer import ComputerTranslator self.trans = ComputerTranslator(**kwargs) # Set wheteher to expect a pk (integer) or a uuid pattern (string) in # the URL path self.parse_pk_uuid = "uuid"
[docs]class Group(BaseResource):
[docs] def __init__(self, **kwargs): super(Group, self).__init__(**kwargs) from aiida.restapi.translator.group import GroupTranslator self.trans = GroupTranslator(**kwargs) self.parse_pk_uuid = 'uuid'
[docs]class User(BaseResource):
[docs] def __init__(self, **kwargs): super(User, self).__init__(**kwargs) from aiida.restapi.translator.user import UserTranslator self.trans = UserTranslator(**kwargs) self.parse_pk_uuid = 'pk'
[docs]class Calculation(Node):
[docs] def __init__(self, **kwargs): super(Calculation, self).__init__(**kwargs) from aiida.restapi.translator.calculation import CalculationTranslator self.trans = CalculationTranslator(**kwargs) from aiida.orm import Calculation as CalculationTclass self.tclass = CalculationTclass self.parse_pk_uuid = 'uuid'
[docs]class Code(Node):
[docs] def __init__(self, **kwargs): super(Code, self).__init__(**kwargs) from aiida.restapi.translator.code import CodeTranslator self.trans = CodeTranslator(**kwargs) from aiida.orm import Code as CodeTclass self.tclass = CodeTclass self.parse_pk_uuid = 'uuid'
[docs]class Data(Node):
[docs] def __init__(self, **kwargs): super(Data, self).__init__(**kwargs) from aiida.restapi.translator.data import DataTranslator self.trans = DataTranslator(**kwargs) from aiida.orm import Data as DataTclass self.tclass = DataTclass self.parse_pk_uuid = 'uuid'
[docs]class StructureData(Data):
[docs] def __init__(self, **kwargs): super(StructureData, self).__init__(**kwargs) from aiida.restapi.translator.data.structure import \ StructureDataTranslator self.trans = StructureDataTranslator(**kwargs) from aiida.orm.data.structure import StructureData as StructureDataTclass self.tclass = StructureDataTclass self.parse_pk_uuid = 'uuid'
[docs]class KpointsData(Data):
[docs] def __init__(self, **kwargs): super(KpointsData, self).__init__(**kwargs) from aiida.restapi.translator.data.kpoints import KpointsDataTranslator self.trans = KpointsDataTranslator(**kwargs) from aiida.orm.data.array.kpoints import KpointsData as KpointsDataTclass self.tclass = KpointsDataTclass self.parse_pk_uuid = 'uuid'
[docs]class BandsData(Data):
[docs] def __init__(self, **kwargs): super(BandsData, self).__init__(**kwargs) from aiida.restapi.translator.data.bands import \ BandsDataTranslator self.trans = BandsDataTranslator(**kwargs) from aiida.orm.data.array.bands import BandsData as BandsDataTclass self.tclass = BandsDataTclass self.parse_pk_uuid = 'uuid'
[docs]class CifData(Data):
[docs] def __init__(self, **kwargs): super(CifData, self).__init__(**kwargs) from aiida.restapi.translator.data.cif import \ CifDataTranslator self.trans = CifDataTranslator(**kwargs) from aiida.orm.data.cif import CifData as CifDataTclass self.tclass = CifDataTclass self.parse_pk_uuid = 'uuid'
[docs]class UpfData(Data):
[docs] def __init__(self, **kwargs): super(UpfData, self).__init__(**kwargs) from aiida.restapi.translator.data.upf import \ UpfDataTranslator self.trans = UpfDataTranslator(**kwargs) from aiida.orm.data.upf import UpfData as UpfDataTclass self.tclass = UpfDataTclass self.parse_pk_uuid = 'uuid'