Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add REST endpoint for CifData #1228

Merged
merged 9 commits into from
Mar 7, 2018
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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/.*

Expand All @@ -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
)$

Expand Down
73 changes: 52 additions & 21 deletions aiida/backends/tests/restapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from aiida.restapi.api import App, AiidaApi

StructureData = DataFactory('structure')
CifData = DataFactory('cif')
ParameterData = DataFactory('parameter')
KpointsData = DataFactory('array.kpoints')

Expand Down Expand Up @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -127,41 +132,53 @@ 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)
computers = [_['comp'] for _ in computers]
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):
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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")

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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] )


36 changes: 27 additions & 9 deletions aiida/restapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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/<int:page>',
'/cifs/<id>/',
'/cifs/<id>/io/inputs/',
'/cifs/<id>/io/inputs/page/',
'/cifs/<id>/io/inputs/page/<int:page>/',
'/cifs/<id>/io/outputs/',
'/cifs/<id>/io/outputs/page/',
'/cifs/<id>/io/outputs/page/<int:page>/',
'/cifs/<id>/io/tree/',
'/cifs/<id>/content/attributes/',
'/cifs/<id>/content/extras/',
'/cifs/<id>/content/visualization/',
'/cifs/<id>/content/download/',
endpoint='cifs',
strict_slashes=False,
resource_class_kwargs=kwargs
)

self.add_resource(User,
'/users/',
'/users/schema/',
'/users/page/',
'/users/page/<int:page>/',
'/users/<id>/',
endpoint='users',
strict_slashes=False,
resource_class_kwargs=kwargs)

Expand All @@ -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)

Expand Down
18 changes: 18 additions & 0 deletions aiida/restapi/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Loading