diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3c264ae12f..3e4c6a5213 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,8 @@ .travis_data/test_setup.py| .travis-data/test_plugin_testcase.py| aiida/backends/tests/verdi_commands.py| + aiida/restapi/resources.py| + aiida/restapi/translator/data/cif.py| aiida/utils/fixtures.py )$ # $ -> end of file path, to add a directory, give full/path/.* @@ -31,6 +33,8 @@ .travis_data/test_setup.py| .travis-data/test_plugin_testcase.py| aiida/backends/tests/verdi_commands.py| + aiida/restapi/translator/data/cif.py| + aiida/restapi/resources.py| aiida/utils/fixtures.py )$ diff --git a/aiida/backends/tests/restapi.py b/aiida/backends/tests/restapi.py index 22e1efbb10..9a89e22621 100644 --- a/aiida/backends/tests/restapi.py +++ b/aiida/backends/tests/restapi.py @@ -19,6 +19,7 @@ from aiida.restapi.api import App, AiidaApi StructureData = DataFactory('structure') +CifData = DataFactory('cif') ParameterData = DataFactory('parameter') KpointsData = DataFactory('array.kpoints') @@ -50,6 +51,7 @@ def setUpClass(cls): LIMIT_DEFAULT=cls._LIMIT_DEFAULT) cls.app = App(__name__) + cls.app.config['TESTING'] = True api = AiidaApi(cls.app, **kwargs) # create test inputs @@ -58,6 +60,9 @@ def setUpClass(cls): structure.append_atom(position=(0., 0., 0.), symbols=['Ba']) structure.store() + cif = CifData(ase=structure.get_ase()) + cif.store() + parameter1 = ParameterData(dict={"a": 1, "b": 2}) parameter1.store() @@ -127,11 +132,15 @@ def process_dummy_data(cls): """ This functions prepare atomic chunks of typical responses from the RESTapi and puts them into class attributes + """ - computer_projects = ["id", "uuid", "name", "hostname", + #TODO: Storing the different nodes as lists and accessing them + # by their list index is very fragile and a pain to debug. + # Please change this! + computer_projections = ["id", "uuid", "name", "hostname", "transport_type", "scheduler_type"] computers = QueryBuilder().append( - Computer, tag="comp", project=computer_projects).order_by( + Computer, tag="comp", project=computer_projections).order_by( {'comp': [{'name': {'order': 'asc'}}]}).dict() # Cast UUID into a string (e.g. in sqlalchemy it comes as a UUID object) @@ -139,29 +148,37 @@ def process_dummy_data(cls): for comp in computers: if comp['uuid'] is not None: comp['uuid'] = str(comp['uuid']) + cls._dummy_data["computers"] = computers - calculation_projects = ["id", "uuid", "user_id", "type"] + calculation_projections = ["id", "uuid", "user_id", "type"] calculations = QueryBuilder().append(Calculation, tag="calc", - project=calculation_projects).order_by( + project=calculation_projections).order_by( {'calc': [{'id': {'order': 'desc'}}]}).dict() calculations = [_['calc'] for _ in calculations] for calc in calculations: if calc['uuid'] is not None: calc['uuid'] = str(calc['uuid']) + cls._dummy_data["calculations"] = calculations - data_projects = ["id", "uuid", "user_id", "type"] - data = QueryBuilder().append(Data, tag="data", project=data_projects).order_by( + data_projections = ["id", "uuid", "user_id", "type"] + data_types = { + 'cifdata': CifData, + 'parameterdata': ParameterData, + 'structuredata': StructureData, + 'data': Data, + } + for label, dataclass in data_types.iteritems(): + data = QueryBuilder().append(dataclass, tag="data", project=data_projections).order_by( {'data': [{'id': {'order': 'desc'}}]}).dict() - data = [_['data'] for _ in data] - for datum in data: - if datum['uuid'] is not None: - datum['uuid'] = str(datum['uuid']) + data = [_['data'] for _ in data] - cls._dummy_data["computers"] = computers - cls._dummy_data["calculations"] = calculations - cls._dummy_data["data"] = data + for datum in data: + if datum['uuid'] is not None: + datum['uuid'] = str(datum['uuid']) + + cls._dummy_data[label] = data def split_path(self, url): @@ -208,7 +225,8 @@ def process_test(self, node_type, url, full_list=False, empty_list=False, expected_errormsg=None, uuid=None, result_node_type=None, result_name=None): """ - Get the full list of nodes from database + Check whether response matches expected values. + :param node_type: url requested fot the type of the node :param url: web url :param full_list: if url is requested to get full list @@ -224,8 +242,9 @@ def process_test(self, node_type, url, full_list=False, empty_list=False, if result_node_type == None and result_name == None: result_node_type = node_type result_name = node_type + url = self._url_prefix + url - self.app.config['TESTING'] = True + with self.app.test_client() as client: rv = client.get(url) response = json.loads(rv.data) @@ -686,7 +705,7 @@ def test_calculation_inputs(self): node_uuid = self.get_dummy_data()["calculations"][1]["uuid"] self.process_test("calculations", "/calculations/" + str( node_uuid) + "/io/inputs?orderby=id", - expected_list_ids=[3, 2], uuid=node_uuid, + expected_list_ids=[4, 2], uuid=node_uuid, result_node_type="data", result_name="inputs") @@ -709,7 +728,6 @@ def test_calculation_attributes(self): node_uuid = self.get_dummy_data()["calculations"][1]["uuid"] url = self.get_url_prefix() + "/calculations/" + str( node_uuid) + "/content/attributes" - self.app.config['TESTING'] = True with self.app.test_client() as client: rv = client.get(url) response = json.loads(rv.data) @@ -726,7 +744,6 @@ def test_calculation_attributes_nalist_filter(self): node_uuid = self.get_dummy_data()["calculations"][1]["uuid"] url = self.get_url_prefix() + '/calculations/' + str( node_uuid) + '/content/attributes?nalist="attr1"' - self.app.config['TESTING'] = True with self.app.test_client() as client: rv = client.get(url) response = json.loads(rv.data) @@ -742,7 +759,6 @@ def test_calculation_attributes_alist_filter(self): node_uuid = self.get_dummy_data()["calculations"][1]["uuid"] url = self.get_url_prefix() + '/calculations/' + str( node_uuid) + '/content/attributes?alist="attr1"' - self.app.config['TESTING'] = True with self.app.test_client() as client: rv = client.get(url) response = json.loads(rv.data) @@ -756,10 +772,9 @@ def test_structure_visualization(self): """ Get the list of give calculation inputs """ - node_uuid = self.get_dummy_data()["data"][3]["uuid"] + node_uuid = self.get_dummy_data()["structuredata"][0]["uuid"] url = self.get_url_prefix() + '/structures/' + str( node_uuid) + '/content/visualization?visformat=cif' - self.app.config['TESTING'] = True with self.app.test_client() as client: rv = client.get(url) response = json.loads(rv.data) @@ -774,3 +789,19 @@ def test_structure_visualization(self): url, response, uuid=node_uuid) + def test_cif(self): + """ + Test download of cif file + """ + from aiida.orm import load_node + + node_uuid = self.get_dummy_data()["cifdata"][0]["uuid"] + url = self.get_url_prefix() + '/cifs/' + node_uuid + '/content/download' + + with self.app.test_client() as client: + rv = client.get(url) + + node = load_node(node_uuid) + self.assertEquals(rv.data, node._prepare_cif()[0] ) + + diff --git a/aiida/orm/implementation/general/group.py b/aiida/orm/implementation/general/group.py index 599c082334..0cb6c86bac 100644 --- a/aiida/orm/implementation/general/group.py +++ b/aiida/orm/implementation/general/group.py @@ -230,14 +230,15 @@ def query(cls, name=None, type_string="", pk=None, uuid=None, nodes=None, user=None, node_attributes=None, past_days=None, **kwargs): """ Query for groups. - :note: By default, query for user-defined groups only (type_string==""). + + :note: By default, query for user-defined groups only (type_string=="") If you want to query for all type of groups, pass type_string=None. If you want to query for a specific type of groups, pass a specific string as the type_string argument. :param name: the name of the group - :param nodes: a node or list of nodes that belongs to the group (alternatively, - you can also pass a DbNode or list of DbNodes) + :param nodes: a node or list of nodes that belongs to the group + (alternatively, you can also pass a DbNode or list of DbNodes) :param pk: the pk of the group :param uuid: the uuid of the group :param type_string: the string for the type of node; by default, look @@ -257,10 +258,10 @@ def query(cls, name=None, type_string="", pk=None, uuid=None, nodes=None, value=each of the values of the iterable. :param kwargs: any other filter to be passed to DbGroup.objects.filter - Example: if ``node_attributes = {'elements': ['Ba', 'Ti'], - 'md5sum': 'xxx'}``, it will find groups that contain the node - with md5sum = 'xxx', and moreover contain at least one node for - element 'Ba' and one node for element 'Ti'. + Example: ``node_attributes = {'elements': ['Ba', 'Ti'], 'md5sum': 'xxx'}`` + will find groups that contain the node with md5sum = 'xxx', and + moreover contain at least one node for element 'Ba' and one node + for element 'Ti'. """ pass diff --git a/aiida/orm/implementation/general/user.py b/aiida/orm/implementation/general/user.py index 76e20be4fb..f730274b5f 100644 --- a/aiida/orm/implementation/general/user.py +++ b/aiida/orm/implementation/general/user.py @@ -14,6 +14,10 @@ class AbstractUser(object): + """ + An AiiDA ORM implementation of a user. + """ + __metaclass__ = ABCMeta _logger = logging.getLogger(__name__) diff --git a/aiida/restapi/api.py b/aiida/restapi/api.py index c0c3999135..a4fac29854 100644 --- a/aiida/restapi/api.py +++ b/aiida/restapi/api.py @@ -91,7 +91,7 @@ def __init__(self, app=None, **kwargs): """ from aiida.restapi.resources import Calculation, Computer, User, Code, Data, \ - Group, Node, StructureData, KpointsData, BandsData, UpfData, ServerInfo + Group, Node, StructureData, KpointsData, BandsData, UpfData, CifData, ServerInfo self.app = app @@ -286,12 +286,36 @@ def __init__(self, app=None, **kwargs): resource_class_kwargs=kwargs ) + + self.add_resource(CifData, + '/cifs/', + '/cifs/schema/', + '/cifs/page/', + '/cifs/page/', + '/cifs//', + '/cifs//io/inputs/', + '/cifs//io/inputs/page/', + '/cifs//io/inputs/page//', + '/cifs//io/outputs/', + '/cifs//io/outputs/page/', + '/cifs//io/outputs/page//', + '/cifs//io/tree/', + '/cifs//content/attributes/', + '/cifs//content/extras/', + '/cifs//content/visualization/', + '/cifs//content/download/', + endpoint='cifs', + strict_slashes=False, + resource_class_kwargs=kwargs + ) + self.add_resource(User, '/users/', '/users/schema/', '/users/page/', '/users/page//', '/users//', + endpoint='users', strict_slashes=False, resource_class_kwargs=kwargs) @@ -315,20 +339,14 @@ def handle_error(self, e): if isinstance(e, HTTPException): if e.code == 404: - from aiida.restapi.common.config import PREFIX - import json + from aiida.restapi.common.utils import list_routes response = {} response["status"] = "404 Not Found" response["message"] = "The requested URL is not found on the server." - response["available_endpoints"] = [] - tmp = [] - for rule in sorted(self.app.url_map.iter_rules()): - if rule.endpoint not in tmp and rule.endpoint != "static": - tmp.append(rule.endpoint) - response["available_endpoints"].append(PREFIX + "/" + rule.endpoint) + response["available_endpoints"] = list_routes() return jsonify(response) diff --git a/aiida/restapi/common/utils.py b/aiida/restapi/common/utils.py index 8bad804ccc..d222a91c34 100644 --- a/aiida/restapi/common/utils.py +++ b/aiida/restapi/common/utils.py @@ -869,3 +869,21 @@ def validate_time(s, loc, toks): ## return the translator instructions elaborated from the field_list return self.build_translator_parameters(field_list) + + +def list_routes(): + """List available routes""" + import urllib + from flask import current_app, url_for + + output = [] + for rule in current_app.url_map.iter_rules(): + if rule.endpoint is "static": + continue + + methods = ','.join(rule.methods) + line = urllib.unquote("{:15s} {:20s} {}".format(rule.endpoint, methods, rule)) + output.append(line) + + return sorted(set(output)) + diff --git a/aiida/restapi/resources.py b/aiida/restapi/resources.py index 096d73f33d..c6b0b37d74 100644 --- a/aiida/restapi/resources.py +++ b/aiida/restapi/resources.py @@ -7,6 +7,8 @@ # 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 @@ -14,12 +16,17 @@ from aiida.restapi.common.utils import Utils + +# pylint: disable=missing-docstring,fixme class ServerInfo(Resource): + 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_confs = { + k: kwargs[k] + for k in utils_conf_keys if k in kwargs + } self.utils = Utils(**self.utils_confs) def get(self): @@ -36,7 +43,7 @@ def get(self): pathlist = self.utils.split_path(self.utils.strip_prefix(path)) - if(len(pathlist) > 1): + if len(pathlist) > 1: resource_type = pathlist.pop(1) else: resource_type = "info" @@ -46,7 +53,7 @@ def get(self): import aiida.restapi.common.config as conf from aiida import __version__ - if(resource_type == "info"): + if resource_type == "info": response = [] # Add Rest API version @@ -58,36 +65,25 @@ def get(self): # Add AiiDA version response.append("AiiDA==" + __version__) - elif (resource_type == "endpoints"): - - # TODO: remove hardcoded list - response["available_endpoints"] = [ - "/api/v2/server", - "/api/v2/computers", - "/api/v2/nodes", - "/api/v2/calculations", - "/api/v2/data", - "/api/v2/codes", - "/api/v2/structures", - "/api/v2/kpoints", - "/api/v2/bands", - "/api/v2/groups" - ] + 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) + 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 class BaseResource(Resource): @@ -105,11 +101,14 @@ 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_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 def get(self, id=None, page=None): """ Get method for the Computer resource @@ -123,15 +122,20 @@ def get(self, id=None, page=None): 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) + (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))) + 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': @@ -150,29 +154,31 @@ def get(self, id=None, page=None): ## Pagination (if required) if page is not None: - (limit, offset, rel_pages) = self.utils.paginate(page, perpage, - total_count) + (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) + 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) + 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) + 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) @@ -188,19 +194,23 @@ def __init__(self, **kwargs): from aiida.restapi.translator.node import NodeTranslator self.trans = NodeTranslator(**kwargs) - from aiida.orm import Node - self.tclass = Node + 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_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 def get(self, id=None, page=None): """ Get method for the Node resource. @@ -214,15 +224,21 @@ def get(self, id=None, page=None): 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) + (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) + (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))) + 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': @@ -236,9 +252,10 @@ def get(self, id=None, page=None): ## 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) + nelist, downloadformat, visformat, filename, + rtype) = self.utils.parse_query_string(query_string) headers = self.utils.build_headers(url=request.url, total_count=0) - if len(filters) > 0: + if filters: usr = filters["user"]["=="] else: usr = [] @@ -250,46 +267,58 @@ def get(self, id=None, page=None): 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) + 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) + (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) + 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 len(results) > 0: + 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"]) + 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 len(results) > 0: + if query_type in ["retrieved_inputs", "retrieved_outputs" + ] and results: try: status = results[query_type]["status"] except KeyError: @@ -300,32 +329,35 @@ def get(self, id=None, page=None): 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"]) + 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) + 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) + 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) class Computer(BaseResource): + def __init__(self, **kwargs): super(Computer, self).__init__(**kwargs) @@ -339,6 +371,7 @@ def __init__(self, **kwargs): class Group(BaseResource): + def __init__(self, **kwargs): super(Group, self).__init__(**kwargs) @@ -347,7 +380,9 @@ def __init__(self, **kwargs): self.parse_pk_uuid = 'uuid' + class User(BaseResource): + def __init__(self, **kwargs): super(User, self).__init__(**kwargs) @@ -356,7 +391,9 @@ def __init__(self, **kwargs): self.parse_pk_uuid = 'pk' + class Calculation(Node): + def __init__(self, **kwargs): super(Calculation, self).__init__(**kwargs) @@ -369,6 +406,7 @@ def __init__(self, **kwargs): class Code(Node): + def __init__(self, **kwargs): super(Code, self).__init__(**kwargs) @@ -381,6 +419,7 @@ def __init__(self, **kwargs): class Data(Node): + def __init__(self, **kwargs): super(Data, self).__init__(**kwargs) @@ -393,6 +432,7 @@ def __init__(self, **kwargs): class StructureData(Data): + def __init__(self, **kwargs): super(StructureData, self).__init__(**kwargs) @@ -407,6 +447,7 @@ def __init__(self, **kwargs): class KpointsData(Data): + def __init__(self, **kwargs): super(KpointsData, self).__init__(**kwargs) @@ -419,6 +460,7 @@ def __init__(self, **kwargs): class BandsData(Data): + def __init__(self, **kwargs): super(BandsData, self).__init__(**kwargs) @@ -430,7 +472,24 @@ def __init__(self, **kwargs): self.parse_pk_uuid = 'uuid' + +class CifData(Data): + + 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' + + class UpfData(Data): + def __init__(self, **kwargs): super(UpfData, self).__init__(**kwargs) diff --git a/aiida/restapi/translator/data/cif.py b/aiida/restapi/translator/data/cif.py new file mode 100644 index 0000000000..be16efb89e --- /dev/null +++ b/aiida/restapi/translator/data/cif.py @@ -0,0 +1,84 @@ +# -*- 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 # +########################################################################### +""" Translator for CifData """ +from aiida.restapi.translator.data import DataTranslator +from aiida.common.exceptions import LicensingException + + +class CifDataTranslator(DataTranslator): + """ + Translator relative to resource 'structures' and aiida class CifData + """ + + # A label associated to the present class (coincides with the resource name) + __label__ = "cifs" + # The AiiDA class one-to-one associated to the present class + from aiida.orm.data.cif import CifData + _aiida_class = CifData + # The string name of the AiiDA class + _aiida_type = "data.cif.CifData" + # The string associated to the AiiDA class in the query builder lexicon + _qb_type = _aiida_type + '.' + + _result_type = __label__ + + def __init__(self, **kwargs): + """ + Initialise the parameters. + Create the basic query_help + """ + super(CifDataTranslator, self).__init__(Class=self.__class__, **kwargs) + + #pylint: disable=arguments-differ,redefined-builtin,protected-access + @staticmethod + def get_visualization_data(node, format=None): + """ + Returns: data in specified format. If format is not specified returns data + in a format required by chemdoodle to visualize a structure. + """ + response = {} + response["str_viz_info"] = {} + + if format in node.get_export_formats(): + try: + response["str_viz_info"]["data"] = node._exportstring(format)[0] + response["str_viz_info"]["format"] = format + except LicensingException as exc: + response = exc.message + + ## Add extra information + #response["dimensionality"] = node.get_dimensionality() + #response["pbc"] = node.pbc + #response["formula"] = node.get_formula() + + return response + + #pylint: disable=arguments-differ,redefined-builtin,protected-access + @staticmethod + def get_downloadable_data(node, format=None): + """ + Return cif string for download + + :param node: node representing cif file to be downloaded + :returns: cif file + """ + + response = {} + + format = 'cif' + try: + response["data"] = node._exportstring(format)[0] + response["status"] = 200 + response["filename"] = node.uuid + "." + format + except LicensingException as exc: + response["status"] = 500 + response["data"] = exc.message + + return response diff --git a/docs/source/orm/dev.rst b/docs/source/orm/dev.rst index 9d8c01024d..7425535af3 100644 --- a/docs/source/orm/dev.rst +++ b/docs/source/orm/dev.rst @@ -14,11 +14,28 @@ Some generic methods of the module aiida.orm.utils Computer ++++++++ + .. automodule:: aiida.orm.implementation.general.computer :members: + :private-members: + +Group ++++++ + +.. automodule:: aiida.orm.implementation.general.group + :members: + :private-members: + +User +++++ + +.. automodule:: aiida.orm.implementation.general.user + :members: + :private-members: Node ++++ + .. automodule:: aiida.orm.implementation.general.node :members: :private-members: diff --git a/docs/source/restapi/index.rst b/docs/source/restapi/index.rst index 4bf173f206..2536aebb8d 100644 --- a/docs/source/restapi/index.rst +++ b/docs/source/restapi/index.rst @@ -4,65 +4,64 @@ REST API for AiiDA =================== -AiiDA provides a REST API to access the information of the AiiDA objects stored -in the database. There are four types of AiiDA objects: *Computer*, *Node*, *User*, -and *Group*. The *Node* type has three subtypes: *Calculation*, *Data*, -and *Code*. Different REST urls are provided to get the list of objects, -the details of a specific object as well as its inputs/outputs/attributes/extras. +AiiDA provides a +`RESTful `_ +`API `_ +that provides access to the AiiDA objects stored in the database. -The AiiDA REST API is implemented using ``Flask RESTFul`` framework. For the time being, it only supports GET methods. The response data are always returned in ``JSON`` format. +The AiiDA REST API is implemented using the ``Flask RESTFul`` framework +and supports only GET methods (reading) for the time being. +The response contains the data in ``JSON`` format. -In this document, the paths of the file systems are defined with respect to the AiiDA installation folder. The source files of the API are contained in the folder ``aiida/restapi``. To start the REST server open a terminal and type +In this document, file paths are given relative to the AiiDA installation folder. +The source files of the API are contained in the folder ``aiida/restapi``. + +Running the REST API +++++++++++++++++++++ + +To start the REST server open a terminal and type .. code-block:: bash $ verdi restapi -This command will hook up a REST api with the default parameters, namely on port *5000* -of *localhost*, connecting to the AiiDA default profile and assuming the default folder for the REST configuration files, namely ``common``. For an overview of options accepted by ``verdi restapi`` you can type +This command will hook up a REST api with the default parameters, namely on port ``5000`` +of ``localhost``, +connect to the default AiiDA profile and assuming the default folder for the REST configuration files, namely ``common``. + +For an overview of options accepted by ``verdi restapi`` you can type .. code-block:: bash $ verdi restapi --help -As all the ``verdi`` commands the AiiDA profile can be changed by putting the option ``-p PROFILE`` right after ``verdi``. +Like all ``verdi`` commands, the AiiDA profile can be changed by putting ``-p PROFILE`` right after ``verdi``. -The base url for your REST API is be:: +The base url for your REST API is:: http://localhost:5000/api/v2 -where the last field identifies the version of the API. This field enables running multiple versions of the API simultaneously, so that the clients should not be obliged to update immediately the format of their requests when a new version of the API is deployed. The current latest version is ``v2``. - - -An alternative way to hook up the Api is to run the script ``run_api.py`` from folder ``aiida/restapi``. Move to the latter and type - -.. code-block:: bash - - $ python run_api.py - -This script has the same options as the ``verdi command`` (they actually invoke the same function) with the addition of ``--aiida-profile=AIIDA_PROFILE`` to set the AiiDA profile to which the Api should connect. +where the last field identifies the version of the API (currently ``v2``). +Simply type this URL in your browser or use command-line tools such as ``curl`` or ``wget``. -The default configuration file is ``config.py``, which by default is looked for in the folder `aiida/restapi``. The path of ``config.py`` can be overwritten by the the option ``--config-dir=CONFIG_DIR`` . All the available configuration options of the REST Api are documented therein. +For the full list of configuration options, see ``aiida/restapi/config.py``. -In order to send requests to the REST API you can simply type the url of the request in the address bar of your browser or you can use command line tools such as ``curl`` or ``wget``. - -Let us now introduce the urls supported by the API. General form of the urls ++++++++++++++++++++++++ A generic url to send requests to the REST API is formed by: - + 1. the base url. It specifies the host and the version of the API. Example:: - + http://localhost:5000/api/v2 - + 2. the path. It defines the kind of resource requested by the client and the type of query. - 3. the query string (not mandatory). It can be used for any further specification of the request, e.g. to introduce query filters, to give instructions for ordering, to set how results have to be paginated, etc. + 3. the query string (not mandatory). It can be used for any further specification of the request, e.g. to introduce query filters, to give instructions for ordering, to set how results have to be paginated, etc. The query string is introduced by the question mark character ``?``. Here are some examples:: - + http://localhost:5000/api/v2/users/ http://localhost:5000/api/v2/computers?scheduler_type="slurm" http://localhost:5000/api/v2/nodes/?id>45&type=like="%data%" @@ -84,10 +83,10 @@ The complete set of results is divided in *pages* containing by default 20 resul http://localhost:5000/api/v2/computers/page If no page number is specified, as in the last example, the system redirects the request to page 1. When pagination is used the header of the response contains two more non-empty fields: - + - ``X-Total-Counts`` (custom field): the total number of results returned by the query, i.e.the sum of the results of all pages) - ``Links``: links to the first, previous, next, and last page. Suppose you send a request whose results would fill 8 pages. Then the value of the ``Links`` field would look like:: - + <\http://localhost:5000/.../page/1?... >; rel=first, <\http://localhost:5000/.../page/3?... ;>; rel=prev, <\http://localhost:5000/.../page/5?... >; rel=next, @@ -109,21 +108,44 @@ Example:: How to build the path --------------------- -There are two type of paths: those that request the list of objects of a -specific resource, namely, the AiiDA object type you are requesting, and those -that inquire a specific object of a certain resource. In both cases the path -has to start with the name of the resource. The complete list of resources is: - -* ``nodes`` -* ``computers`` -* ``codes`` -* ``calculations``, -* ``data``, ``structures``, ``kpoints``, ``bands`` -* ``users``, ``groups``, - -If no specific endpoint is appended to the name of the resource, the Api will -return the full list of objects of that resource (the Api default limit applies -nevertheless to the number of results). Appending the endpoint ``schema`` to a +The first element of the path is the *Resource* corresponding to the +AiiDA object(s) you want to request. The following resources are available: + ++--------------------------------------------------------------------------------------------+-------------------+ +| Class | Resource | ++============================================================================================+===================+ +| :py:class:`Calculation ` | ``/calculations`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`Computer ` | ``/computers`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`Data ` | ``/data`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`Group ` | ``/groups`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`Node ` | ``/nodes`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`User ` | ``/users`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`BandsData ` | ``/bands`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`CifData ` | ``/cifs`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`KpointsData ` | ``/kpoints`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`StructureData ` | ``/structures`` | ++--------------------------------------------------------------------------------------------+-------------------+ +| :py:class:`UpfData ` | ``/upfs`` | ++--------------------------------------------------------------------------------------------+-------------------+ + +For a **full list** of available endpoints for each resource, simply query the base URL of the REST API. + +There are two types of paths: you may either request a list of objects +or one specific object of a resource. + +If no specific endpoint is appended to the name of the resource, the Api +returns the full list of objects of that resource (default limits apply). + +Appending the endpoint ``schema`` to a resource will give the list of fields that are normally returned by the Api for an object of a specific resource, whereas the endpoint ``statistics`` returns a list of statistical facts concerning a resource. @@ -161,7 +183,7 @@ How to build the query string The query string is formed by one or more fields separated by the special character ``&``. Each field has the form (``key``)(``operator``)(``value``). The same constraints that apply to the names of python variables determine what are the valid keys, namely, only alphanumeric characters plus ``_`` are allowed and the first character cannot be a number. -Special keys +Special keys ************ There are several special keys that can be specified only once in a query string. All of them must be followed by the operator ``=``. Here is the complete list: @@ -175,24 +197,24 @@ There are several special keys that can be specified only once in a query string :orderby: This key is used to impose a specific ordering to the results. Two orderings are supported, ascending or descending. The value for the ``orderby`` key must be the name of the property with respect to which to order the results. Additionally, ``+`` or ``-`` can be pre-pended to the value in order to select, respectively, ascending or descending order. Specifying no leading character is equivalent to select ascending order. Ascending (descending) order for strings corresponds to alphabetical (reverse-alphabetical) order, whereas for datetime objects it corresponds to chronological (reverse-chronological order). Examples: :: - + http://localhost:5000/api/v2/c=+id http://localhost:5000/api/v2/computers=+name http://localhost:5000/api/v2/computers/orderby=-uuid - - - :alist: This key is used to specify which attributes of a specific object have to be returned. The desired attributes have to be provided as a comma-separated list of values. It requires that the path contains the endpoint ``/content/attributes``. Example: + + + :alist: This key is used to specify which attributes of a specific object have to be returned. The desired attributes have to be provided as a comma-separated list of values. It requires that the path contains the endpoint ``/content/attributes``. Example: :: http://localhost:5000/api/v2/codes/4fb10ef1-1a/content/attributes? - alist=append_text,prepend_text + alist=append_text,prepend_text + + :nalist: (incompatible with ``alist``) This key is used to specify which attributes of a specific object should not be returned. The syntax is identical to ``alist``. The system returns all the attributes except those specified in the list of values. - :nalist: (incompatible with ``alist``) This key is used to specify which attributes of a specific object should not be returned. The syntax is identical to ``alist``. The system returns all the attributes except those specified in the list of values. - :elist: Similar to ``alist`` but for extras. It requires that the path contains the endpoint ``/content/extras``. - + :nelist: (incompatible with ``elist``) Similar to ``nalist`` but for extras. It requires that the path contains the endpoint ``/content/extras``. Filters @@ -200,23 +222,23 @@ Filters All the other fields composing a query string are filters, that is, conditions that have to be fulfilled by the retrieved objects. When a query string contains multiple filters, those are applied as if they were related by the AND logical clause, that is, the results have to fulfill all the conditions set by the filters (and not any of them). Each filter key is associated to a unique value type. The possible types are: - :string: Text enclosed in double quotes. If the string contains double quotes those have to be escaped as ``""`` (two double quotes). Note that in the unlikely occurrence of a sequence of double quotes you will have to escape it by writing twice as many double quotes. + :string: Text enclosed in double quotes. If the string contains double quotes those have to be escaped as ``""`` (two double quotes). Note that in the unlikely occurrence of a sequence of double quotes you will have to escape it by writing twice as many double quotes. :integer: Positive integer numbers. - + :datetime: Datetime objects expressed in the format ``(DATE)T(TIME)(SHIFT)`` where ``(SHIFT)`` is the time difference with respect to the UTC time. This is required to avoid any problem arising from comparing datetime values expressed in different time zones. The formats of each field are: - + 1. ``YYYY-MM-DD`` for ``(DATE)`` (mandatory). - 2. ``HH:MM:SS`` for ``(TIME)`` (optional). The formats ``HH`` and ``HH:MM`` are supported too. - 3. ``+/-HH:MM`` for ``(SHIFT)`` (optional, if present requires ``(TIME)`` to be specified). The format ``+/-HH`` is allowed too. If no shift is specified UTC time is assumed. The shift format follows the general convention that eastern (western) shifts are positive (negative). The API is unaware of daylight saving times so the user is required to adjust the shift to take them into account. - + 2. ``HH:MM:SS`` for ``(TIME)`` (optional). The formats ``HH`` and ``HH:MM`` are supported too. + 3. ``+/-HH:MM`` for ``(SHIFT)`` (optional, if present requires ``(TIME)`` to be specified). The format ``+/-HH`` is allowed too. If no shift is specified UTC time is assumed. The shift format follows the general convention that eastern (western) shifts are positive (negative). The API is unaware of daylight saving times so the user is required to adjust the shift to take them into account. + This format is ``ISO-8601`` compliant. Note that date and time fields have to be separated by the character ``T``. Examples: :: - + ctime>2016-04-23T05:45+03:45 - ctime<2016-04-23T05:45 - mtime>=2016-04-23 + ctime<2016-04-23T05:45 + mtime>=2016-04-23 :bool: It can be either true or false (lower case). @@ -276,8 +298,8 @@ The following table reports what is the value type and the supported resources a \* Key not available via the ``/users/`` endpoint for reasons of privacy. -The operators supported by a specific key are uniquely determined by the value type associated to that key. For example, a key that requires a boolean value admits only the identity operator ``=``, whereas an integer value enables the usage of the relational operators ``=``, ``<``, ``<=``, ``>``, ``>=`` plus the membership operator ``=in=``. -Please refer to the following table for a comprehensive list. +The operators supported by a specific key are uniquely determined by the value type associated to that key. For example, a key that requires a boolean value admits only the identity operator ``=``, whereas an integer value enables the usage of the relational operators ``=``, ``<``, ``<=``, ``>``, ``>=`` plus the membership operator ``=in=``. +Please refer to the following table for a comprehensive list. +-----------+------------------------+---------------------------------+ |operator |meaning |accepted value types | @@ -305,8 +327,8 @@ The pattern matching operators ``=like=`` and ``=ilike=`` must be followed by th 1. ``%`` is used to replace an arbitrary sequence of characters, including no characters. 2. ``_`` is used to replace one or zero characters. - -Differently from ``=like=``, ``=ilike=`` assumes that two characters that only differ in the case are equal. + +Differently from ``=like=``, ``=ilike=`` assumes that two characters that only differ in the case are equal. To prevent interpreting special characters as wildcards, these have to be escaped by pre-pending the character ``\``. @@ -326,13 +348,13 @@ Examples: | ``uuid=like="cdfd48%"`` | "cdfd48f9-7ed2-4969 | | | | -ba06-09c752b83d2" | | +-------------------------------+----------------------+-------------------+ -| ``description=like="This`` | "This calculation is | | +| ``description=like="This`` | "This calculation is | | | ``calculation is %\% useful"``| 100% useful" | | +-------------------------------+----------------------+-------------------+ The membership operator ``=in=`` has to be followed by a comma-separated list of values of the same type. The condition is fulfilled if the column value of an object is an element of the list. -Examples:: +Examples:: http://localhost:5000/api/v2/nodes?id=in=45,56,78 http://localhost:5000/api/v2/computers/? @@ -342,13 +364,13 @@ The relational operators '<', '>', '<=', '>=' assume natural ordering for intege Examples: - - ``http://localhost:5000/api/v2/nodes?id>578`` selects the nodes having an id larger than 578. - - ``http://localhost:5000/api/v2/users/?last_login>2014-04-07`` selects only the user that logged in for the last time after April 7th, 2014. + - ``http://localhost:5000/api/v2/nodes?id>578`` selects the nodes having an id larger than 578. + - ``http://localhost:5000/api/v2/users/?last_login>2014-04-07`` selects only the user that logged in for the last time after April 7th, 2014. - ``http://localhost:5000/api/v2/users/?last_name<="m"`` selects only the users whose last name begins with a character in the range [a-m]. .. note:: Object types have to be specified by a string that defines their position in the AiiDA source tree ending with a dot. Examples: - + - ``type="data.Data."`` selects only objects of *Data* type - ``type="data.remote.RemoteData."`` selects only objects of *RemoteData* type @@ -361,7 +383,7 @@ Examples: would first search for the outputs of the node with *uuid* starting with "a67fba41-8a" and then select only those objects of type *FolderData*. - + The HTTP response +++++++++++++++++ @@ -444,7 +466,7 @@ Computers 1. Get a list of the *Computers* objects. - REST url:: + REST url:: http://localhost:5000/api/v2/computers?limit=3&offset=2&orderby=id @@ -455,55 +477,55 @@ Computers by ascending values of ``id``. Response:: - + { "data": { "computers": [ { - "description": "Alpha Computer", - "enabled": true, - "hostname": "alpha.aiida.net", - "id": 3, - "name": "Alpha", - "scheduler_type": "slurm", - "transport_params": "{}", - "transport_type": "ssh", + "description": "Alpha Computer", + "enabled": true, + "hostname": "alpha.aiida.net", + "id": 3, + "name": "Alpha", + "scheduler_type": "slurm", + "transport_params": "{}", + "transport_type": "ssh", "uuid": "9b5c84bb-4575-4fbe-b18c-b23fc30ec55e" - }, + }, { - "description": "Beta Computer", - "enabled": true, - "hostname": "beta.aiida.net", - "id": 4, - "name": "Beta", - "scheduler_type": "slurm", - "transport_params": "{}", - "transport_type": "ssh", + "description": "Beta Computer", + "enabled": true, + "hostname": "beta.aiida.net", + "id": 4, + "name": "Beta", + "scheduler_type": "slurm", + "transport_params": "{}", + "transport_type": "ssh", "uuid": "5d490d77-638d-4d4b-8288-722f930783c8" - }, + }, { - "description": "Gamma Computer", - "enabled": true, - "hostname": "gamma.aiida.net", - "id": 5, - "name": "Gamma", - "scheduler_type": "slurm", - "transport_params": "{}", - "transport_type": "ssh", + "description": "Gamma Computer", + "enabled": true, + "hostname": "gamma.aiida.net", + "id": 5, + "name": "Gamma", + "scheduler_type": "slurm", + "transport_params": "{}", + "transport_type": "ssh", "uuid": "7a0c3ff9-1caf-405c-8e89-2369cf91b634" } ] - }, - "method": "GET", - "path": "/api/v2/computers", - "pk": null, - "query_string": "limit=3&offset=2&orderby=id", - "resource_type": "computers", - "url": "http://localhost:5000/api/v2/computers?limit=3&offset=2&orderby=id", + }, + "method": "GET", + "path": "/api/v2/computers", + "pk": null, + "query_string": "limit=3&offset=2&orderby=id", + "resource_type": "computers", + "url": "http://localhost:5000/api/v2/computers?limit=3&offset=2&orderby=id", "url_root": "http://localhost:5000/" } - - + + 2. Get details of a single *Computer* object: @@ -521,33 +543,33 @@ Computers "data": { "computers": [ { - "description": "Beta Computer", - "enabled": true, - "hostname": "beta.aiida.net", - "id": 4, - "name": "Beta", - "scheduler_type": "slurm", - "transport_params": "{}", - "transport_type": "ssh", + "description": "Beta Computer", + "enabled": true, + "hostname": "beta.aiida.net", + "id": 4, + "name": "Beta", + "scheduler_type": "slurm", + "transport_params": "{}", + "transport_type": "ssh", "uuid": "5d490d77-638d-4d4b-8288-722f930783c8" } ] - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/computers/5d490d77-638d", - "pk": 4, - "query_string": "", - "resource_type": "computers", + "pk": 4, + "query_string": "", + "resource_type": "computers", "url": "http://localhost:5000/api/v2/computers/5d490d77-638d", "url_root": "http://localhost:5000/" } - + Nodes ----- 1. Get a list of *Node* objects - + REST url:: http://localhost:5000/api/v2/nodes?limit=2&offset=8&orderby=-id @@ -564,34 +586,34 @@ Nodes "data": { "nodes ": [ { - "ctime": "Fri, 29 Apr 2016 19:24:12 GMT", - "id": 386913, - "label": "", - "mtime": "Fri, 29 Apr 2016 19:24:13 GMT", - "state": null, - "type": "calculation.inline.InlineCalculation.", + "ctime": "Fri, 29 Apr 2016 19:24:12 GMT", + "id": 386913, + "label": "", + "mtime": "Fri, 29 Apr 2016 19:24:13 GMT", + "state": null, + "type": "calculation.inline.InlineCalculation.", "uuid": "68d2ed6c-6f51-4546-8d10-7fe063525ab8" - }, + }, { - "ctime": "Fri, 29 Apr 2016 19:24:00 GMT", - "id": 386912, - "label": "", - "mtime": "Fri, 29 Apr 2016 19:24:00 GMT", - "state": null, - "type": "data.parameter.ParameterData.", + "ctime": "Fri, 29 Apr 2016 19:24:00 GMT", + "id": 386912, + "label": "", + "mtime": "Fri, 29 Apr 2016 19:24:00 GMT", + "state": null, + "type": "data.parameter.ParameterData.", "uuid": "a39dc158-fedd-4ea1-888d-d90ec6f86f35" } ] - }, - "method": "GET", - "path": "/api/v2/nodes", - "pk": null, - "query_string": "limit=2&offset=8&orderby=-id", - "resource_type": "nodes", - "url": "http://localhost:5000/api/v2/nodes?limit=2&offset=8&orderby=-id", + }, + "method": "GET", + "path": "/api/v2/nodes", + "pk": null, + "query_string": "limit=2&offset=8&orderby=-id", + "resource_type": "nodes", + "url": "http://localhost:5000/api/v2/nodes?limit=2&offset=8&orderby=-id", "url_root": "http://localhost:5000/" } - + 2. Get the details of a single *Node* object: REST url:: @@ -608,33 +630,33 @@ Nodes "data": { "nodes ": [ { - "ctime": "Fri, 14 Aug 2015 13:18:04 GMT", - "id": 1, - "label": "", - "mtime": "Mon, 25 Jan 2016 14:34:59 GMT", - "state": "IMPORTED", - "type": "data.parameter.ParameterData.", + "ctime": "Fri, 14 Aug 2015 13:18:04 GMT", + "id": 1, + "label": "", + "mtime": "Mon, 25 Jan 2016 14:34:59 GMT", + "state": "IMPORTED", + "type": "data.parameter.ParameterData.", "uuid": "e30da7cc-af50-40ca-a940-2ac8d89b2e0d" } ] - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/nodes/e30da7cc", - "pk": 1, - "query_string": "", - "resource_type": "nodes", + "pk": 1, + "query_string": "", + "resource_type": "nodes", "url": "http://localhost:5000/api/v2/nodes/e30da7cc", "url_root": "http://localhost:5000/" } - + 3. Get the list of inputs of a specific node. - REST url:: - + REST url:: + http://localhost:5000/api/v2/nodes/de83b1/io/inputs?limit=2 Description: - + returns the list of the first two input nodes (``limit=2``) of the *Node* object with ``uuid="de83b1..."``. Response:: @@ -643,43 +665,43 @@ Nodes "data": { "inputs": [ { - "ctime": "Fri, 24 Jul 2015 18:49:23 GMT", - "id": 10605, - "label": "", - "mtime": "Mon, 25 Jan 2016 14:35:00 GMT", - "state": "IMPORTED", - "type": "data.remote.RemoteData.", + "ctime": "Fri, 24 Jul 2015 18:49:23 GMT", + "id": 10605, + "label": "", + "mtime": "Mon, 25 Jan 2016 14:35:00 GMT", + "state": "IMPORTED", + "type": "data.remote.RemoteData.", "uuid": "16b93b23-8629-4d83-9259-de2a947b43ed" - }, + }, { - "ctime": "Fri, 24 Jul 2015 14:33:04 GMT", - "id": 9215, - "label": "", - "mtime": "Mon, 25 Jan 2016 14:35:00 GMT", - "state": "IMPORTED", - "type": "data.array.kpoints.KpointsData.", + "ctime": "Fri, 24 Jul 2015 14:33:04 GMT", + "id": 9215, + "label": "", + "mtime": "Mon, 25 Jan 2016 14:35:00 GMT", + "state": "IMPORTED", + "type": "data.array.kpoints.KpointsData.", "uuid": "1b4d22ec-9f29-4e0d-9d68-84ddd18ad8e7" } ] - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/nodes/de83b1/io/inputs", - "pk": 6, - "query_string": "limit=2", - "resource_type": "nodes", + "pk": 6, + "query_string": "limit=2", + "resource_type": "nodes", "url": "http://localhost:5000/api/v2/nodes/de83b1/io/inputs?limit=2", "url_root": "http://localhost:5000/" } - -4. Filter the inputs/outputs of a node by their type. - REST url:: - +4. Filter the inputs/outputs of a node by their type. + + REST url:: + http://localhost:5000/api/v2/nodes/de83b1/io/inputs?type="data.array.kpoints.KpointsData." Description: - + returns the list of the `*KpointsData* input nodes of the *Node* object with ``uuid="de83b1..."``. @@ -689,31 +711,31 @@ Nodes "data": { "inputs": [ { - "ctime": "Fri, 24 Jul 2015 14:33:04 GMT", - "id": 9215, - "label": "", - "mtime": "Mon, 25 Jan 2016 14:35:00 GMT", - "state": "IMPORTED", - "type": "data.array.kpoints.KpointsData.", + "ctime": "Fri, 24 Jul 2015 14:33:04 GMT", + "id": 9215, + "label": "", + "mtime": "Mon, 25 Jan 2016 14:35:00 GMT", + "state": "IMPORTED", + "type": "data.array.kpoints.KpointsData.", "uuid": "1b4d22ec-9f29-4e0d-9d68-84ddd18ad8e7" } ] - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/nodes/de83b1/io/inputs", - "pk": 6, - "query_string": "type=\"data.array.kpoints.KpointsData.\"", - "resource_type": "nodes", + "pk": 6, + "query_string": "type=\"data.array.kpoints.KpointsData.\"", + "resource_type": "nodes", "url": "http://localhost:5000/api/v2/nodes/de83b1/io/inputs?type=\"data.array.kpoints.KpointsData.\"", "url_root": "http://localhost:5000/" } - + REST url:: - + http://localhost:5000/api/v2/nodes/de83b1/io/outputs?type="data.remote.RemoteData." - + Description: - + returns the list of the *RemoteData* output nodes of the *Node* object with ``uuid="de83b1..."``. Response:: @@ -722,35 +744,35 @@ Nodes "data": { "outputs": [ { - "ctime": "Fri, 24 Jul 2015 20:35:02 GMT", - "id": 2811, - "label": "", - "mtime": "Mon, 25 Jan 2016 14:34:59 GMT", - "state": "IMPORTED", - "type": "data.remote.RemoteData.", + "ctime": "Fri, 24 Jul 2015 20:35:02 GMT", + "id": 2811, + "label": "", + "mtime": "Mon, 25 Jan 2016 14:34:59 GMT", + "state": "IMPORTED", + "type": "data.remote.RemoteData.", "uuid": "bd48e333-da8a-4b6f-8e1e-6aaa316852eb" } ] - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/nodes/de83b1/io/outputs", - "pk": 6, - "query_string": "type=\"data.remote.RemoteData.\"", - "resource_type": "nodes", + "pk": 6, + "query_string": "type=\"data.remote.RemoteData.\"", + "resource_type": "nodes", "url": "http://localhost:5000/api/v2/nodes/de83b1/io/outputs?type=\"data.remote.RemoteData.\"", "url_root": "http://localhost:5000/" } - + 5. Getting the list of the attributes/extras of a specific node REST url:: - + http://localhost:5000/api/v2/nodes/ffe11/content/attributes Description: - + returns the list of all attributes of the *Node* object with ``uuid="ffe11..."``. Response:: @@ -758,22 +780,22 @@ Nodes { "data": { "attributes": { - "append_text": "", - "input_plugin": "quantumespresso.pw", - "is_local": false, - "prepend_text": "", + "append_text": "", + "input_plugin": "quantumespresso.pw", + "is_local": false, + "prepend_text": "", "remote_exec_path": "/project/espresso-5.1-intel/bin/pw.x" } - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/nodes/ffe11/content/attributes", - "pk": 1822, - "query_string": "", - "resource_type": "nodes", + "pk": 1822, + "query_string": "", + "resource_type": "nodes", "url": "http://localhost:5000/api/v2/nodes/ffe11/content/attributes", "url_root": "http://localhost:5000/" } - + REST url:: @@ -781,7 +803,7 @@ Nodes http://localhost:5000/api/v2/nodes/ffe11/content/extras Description: - + returns the list of all the extras of the *Node* object with ``uuid="ffe11..."``. Response:: @@ -789,30 +811,30 @@ Nodes { "data": { "extras": { - "trialBool": true, - "trialFloat": 3.0, - "trialInt": 34, + "trialBool": true, + "trialFloat": 3.0, + "trialInt": 34, "trialStr": "trial" } - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/codes/ffe11/content/extras", - "pk": 1822, - "query_string": "", - "resource_type": "codes", + "pk": 1822, + "query_string": "", + "resource_type": "codes", "url": "http://localhost:5000/api/v2/codes/ffe11/content/extras", "url_root": "http://localhost:5000/" } - -6. Getting a user-defined list of attributes/extras of a specific node + +6. Getting a user-defined list of attributes/extras of a specific node REST url:: - + http://localhost:5000/api/v2/codes/ffe11/content/attributes?alist=append_text,is_local Description: - + returns a list of the attributes ``append_text`` and ``is_local`` of the *Node* object with ``uuid="ffe11..."``. Response:: @@ -820,27 +842,27 @@ Nodes { "data": { "attributes": { - "append_text": "", + "append_text": "", "is_local": false } - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/codes/ffe11/content/attributes", - "pk": 1822, - "query_string": "alist=append_text,is_local", - "resource_type": "codes", + "pk": 1822, + "query_string": "alist=append_text,is_local", + "resource_type": "codes", "url": "http://localhost:5000/api/v2/codes/ffe11/content/attributes?alist=append_text,is_local", "url_root": "http://localhost:5000/" } - + REST url:: - + http://localhost:5000/api/v2/codes/ffe11/content/extras?elist=trialBool,trialInt Description: - + returns a list of the extras ``trialBool`` and ``trialInt`` of the *Node* object with ``uuid="ffe11..."``. Response:: @@ -848,15 +870,15 @@ Nodes { "data": { "extras": { - "trialBool": true, + "trialBool": true, "trialInt": 34 } - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/codes/ffe11/content/extras", - "pk": 1822, - "query_string": "elist=trialBool,trialInt", - "resource_type": "codes", + "pk": 1822, + "query_string": "elist=trialBool,trialInt", + "resource_type": "codes", "url": "http://localhost:5000/api/v2/codes/ffe11/content/extras?elist=trialBool,trialInt", "url_root": "http://localhost:5000/" } @@ -869,7 +891,7 @@ Nodes http://localhost:5000/api/v2/codes/ffe11/content/attributes?nalist=append_text,is_local Description: - + returns all the attributes of the *Node* object with ``uuid="ffe11..."`` except ``append_text`` and ``is_local``. Response:: @@ -877,16 +899,16 @@ Nodes { "data": { "attributes": { - "input_plugin": "quantumespresso.pw", - "prepend_text": "", + "input_plugin": "quantumespresso.pw", + "prepend_text": "", "remote_exec_path": "/project/espresso-5.1-intel/bin/pw.x" } - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/codes/ffe11/content/attributes", - "pk": 1822, - "query_string": "nalist=append_text,is_local", - "resource_type": "codes", + "pk": 1822, + "query_string": "nalist=append_text,is_local", + "resource_type": "codes", "url": "http://localhost:5000/api/v2/codes/ffe11/content/attributes?nalist=append_text,is_local", "url_root": "http://localhost:5000/" } @@ -897,7 +919,7 @@ Nodes http://localhost:5000/api/v2/codes/ffe11/content/extras?nelist=trialBool,trialInt Description: - + returns all the extras of the *Node* object with ``uuid="ffe11..."`` except ``trialBool`` and ``trialInt``. Response:: @@ -905,15 +927,15 @@ Nodes { "data": { "extras": { - "trialFloat": 3.0, + "trialFloat": 3.0, "trialStr": "trial" } - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/codes/ffe11/content/extras", - "pk": 1822, - "query_string": "nelist=trialBool,trialInt", - "resource_type": "codes", + "pk": 1822, + "query_string": "nelist=trialBool,trialInt", + "resource_type": "codes", "url": "http://localhost:5000/api/v2/codes/ffe11/content/extras?nelist=trialBool,trialInt", "url_root": "http://localhost:5000/" } @@ -927,13 +949,13 @@ Users 1. Getting a list of the users - REST url:: + REST url:: http://localhost:5000/api/v2/users/ Description: - - returns a list of all the *User* objects. + + returns a list of all the *User* objects. Response:: @@ -941,12 +963,12 @@ Users "data": { "users": [ { - "date_joined": "Mon, 25 Jan 2016 14:31:17 GMT", - "first_name": "AiiDA", - "id": 1, - "institution": "", + "date_joined": "Mon, 25 Jan 2016 14:31:17 GMT", + "first_name": "AiiDA", + "id": 1, + "institution": "", "last_name": "Daemon" - }, + }, { "date_joined": "Thu, 11 Aug 2016 12:35:32 GMT", "first_name": "Gengis", @@ -955,24 +977,24 @@ Users "last_name": "Khan" } ] - }, - "method": "GET", - "path": "/api/v2/users/", - "pk": null, - "query_string": "", - "resource_type": "users", - "url": "http://localhost:5000/api/v2/users/", + }, + "method": "GET", + "path": "/api/v2/users/", + "pk": null, + "query_string": "", + "resource_type": "users", + "url": "http://localhost:5000/api/v2/users/", "url_root": "http://localhost:5000/" } - + 2. Getting a list of users whose first name starts with a given string - REST url:: + REST url:: http://localhost:5000/api/v2/users/?first_name=ilike="aii%" Description: - + returns a lists of the *User* objects whose first name starts with ``"aii"``, regardless the case of the characters. Response:: @@ -981,23 +1003,23 @@ Users "data": { "users": [ { - "date_joined": "Mon, 25 Jan 2016 14:31:17 GMT", - "first_name": "AiiDA", - "id": 1, - "institution": "", + "date_joined": "Mon, 25 Jan 2016 14:31:17 GMT", + "first_name": "AiiDA", + "id": 1, + "institution": "", "last_name": "Daemon" } ] - }, - "method": "GET", - "path": "/api/v2/users/", - "pk": null, - "query_string": "first_name=ilike=%22aii%%22", - "resource_type": "users", - "url": "http://localhost:5000/api/v2/users/?first_name=ilike=\"aii%\"", + }, + "method": "GET", + "path": "/api/v2/users/", + "pk": null, + "query_string": "first_name=ilike=%22aii%%22", + "resource_type": "users", + "url": "http://localhost:5000/api/v2/users/?first_name=ilike=\"aii%\"", "url_root": "http://localhost:5000/" } - + Groups ------ @@ -1009,42 +1031,42 @@ Groups http://localhost:5000/api/v2/groups/?limit=10&orderby=-user_id Description: - + returns the list of ten *Group* objects (``limit=10``) starting from the 1st row of the database table (``offset=0``) and the list will be ordered by ``user_id`` in descending order. - + Response:: { "data": { "groups": [ { - "description": "", - "id": 104, - "name": "SSSP_new_phonons_0p002", - "type": "", - "user_id": 2, + "description": "", + "id": 104, + "name": "SSSP_new_phonons_0p002", + "type": "", + "user_id": 2, "uuid": "7c0e0744-8549-4eea-b1b8-e7207c18de32" - }, + }, { - "description": "", - "id": 102, - "name": "SSSP_cubic_old_phonons_0p025", - "type": "", - "user_id": 1, + "description": "", + "id": 102, + "name": "SSSP_cubic_old_phonons_0p025", + "type": "", + "user_id": 1, "uuid": "c4e22134-495d-4779-9259-6192fcaec510" - }, + }, ... - + ] - }, - "method": "GET", - "path": "/api/v2/groups/", - "pk": null, - "query_string": "limit=10&orderby=-user_id", - "resource_type": "groups", - "url": "http://localhost:5000/api/v2/groups/?limit=10&orderby=-user_id", + }, + "method": "GET", + "path": "/api/v2/groups/", + "pk": null, + "query_string": "limit=10&orderby=-user_id", + "resource_type": "groups", + "url": "http://localhost:5000/api/v2/groups/?limit=10&orderby=-user_id", "url_root": "http://localhost:5000/" } @@ -1055,7 +1077,7 @@ Groups http://localhost:5000/api/v2/groups/a6e5b Description: - + returns the details of the *Group* object with ``uuid="a6e5b..."``. Response:: @@ -1064,21 +1086,20 @@ Groups "data": { "groups": [ { - "description": "GBRV US pseudos, version 1.2", + "description": "GBRV US pseudos, version 1.2", "id": 23, - "name": "GBRV_1.2", - "type": "data.upf.family", - "user_id": 2, + "name": "GBRV_1.2", + "type": "data.upf.family", + "user_id": 2, "uuid": "a6e5b6c6-9d47-445b-bfea-024cf8333c55" } ] - }, - "method": "GET", + }, + "method": "GET", "path": "/api/v2/groups/a6e5b", - "pk": 23, - "query_string": "", - "resource_type": "groups", + "pk": 23, + "query_string": "", + "resource_type": "groups", "url": "http://localhost:5000/api/v2/groups/a6e5b", "url_root": "http://localhost:5000/" } -