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

Update AiiDA REST API #878

Merged
merged 47 commits into from
Oct 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1cf1b45
Merge remote-tracking branch 'main_repo/develop' into develop
Mar 14, 2017
0f8718d
Merge remote-tracking branch 'main_repo/develop' into develop
Mar 28, 2017
86c994d
Merge remote-tracking branch 'main_repo/develop' into develop
Jun 9, 2017
224128c
Merge remote-tracking branch 'main_repo/develop' into develop
Jun 26, 2017
f573ea2
Merge remote-tracking branch 'main_repo/develop' into develop
Jul 4, 2017
a3af5fa
Merge remote-tracking branch 'main_repo/develop' into develop
Jul 17, 2017
dc3d9b6
In Restapi, fixed nodecount id to visualise node tree
Aug 10, 2017
f49ff83
fixed issue in band visualization
Sep 6, 2017
0fbc324
updated statistics function in restapi
Sep 14, 2017
2ea94bf
Merge branch 'develop' of https://github.com/aiidateam/aiida_core int…
Oct 2, 2017
9136eb5
Merge branch 'develop' into materialscloud
Oct 3, 2017
6fc554d
Merge pull request #740 from waychal/materialscloud
ltalirz Oct 3, 2017
aadba66
Merge branch 'release_v0.10.0' into materialscloud
Oct 23, 2017
8800172
added custom schema file
Oct 23, 2017
f507150
Merge branch 'release_v0.10.0' into materialscloud
Oct 24, 2017
43ad11a
Merged release_v0.10.0
Oct 24, 2017
01a6896
Merge pull request #839 from waychal/materialscloud
ltalirz Oct 24, 2017
c30e7fc
Extended structure visualization endpoint to return data in different…
Oct 24, 2017
1473cf7
Added info endpoint to return server info like current aiida version
Oct 24, 2017
63787fa
Added /server/enpoints in REST API
Oct 24, 2017
029d780
Returned exception to handle in parent if not 404 exception
Oct 24, 2017
60e3eac
Merge branch 'release_v0.10.0' into materialscloud
Oct 25, 2017
3cde5ab
Merge branch 'release_v0.10.0' into issue_793
Oct 25, 2017
58dc815
Merge branch 'release_v0.10.0' into issues_805_restapi_structure_endp…
Oct 25, 2017
a544868
Merge pull request #845 from waychal/materialscloud
Oct 25, 2017
95efb61
Updated structure endpoint to download and visualize data in diff for…
Oct 25, 2017
4022c56
Added testcase for structure visualization endpoint
Oct 25, 2017
96ff216
Merge branch 'materialscloud_aiidateam' into issues_805_restapi_struc…
Oct 25, 2017
26c3459
Merge branch 'materialscloud_aiidateam' into issue_777_restapi_user_e…
Oct 25, 2017
597ea82
Merge branch 'materialscloud_aiidateam' into issue_793_restapi_server…
Oct 25, 2017
109a154
Merge pull request #848 from waychal/issue_793_restapi_server_endpoin…
ltalirz Oct 25, 2017
058dee2
update documentation of /users endpoint
ltalirz Oct 25, 2017
4e7121f
Enabled user endpoint
Oct 25, 2017
255527a
Merge branch 'issue_777_restapi_user_endpoint_mcloud' of https://gith…
Oct 26, 2017
f33eb60
Merge pull request #849 from waychal/issue_777_restapi_user_endpoint_…
Oct 26, 2017
855cb85
Extended structure visualization endpoint to return data in different…
Oct 24, 2017
f7609c2
Updated structure endpoint to download and visualize data in diff for…
Oct 25, 2017
0e0a7c5
Added testcase for structure visualization endpoint
Oct 25, 2017
d6ae1d7
Merge branch 'issues_805_restapi_structure_endpoint_mcloud' of https:…
Oct 26, 2017
b0ace5c
Merge pull request #850 from waychal/issues_805_restapi_structure_end…
Oct 26, 2017
5ed12eb
Fixed issue #872
Oct 27, 2017
84a2e7c
Merge branch 'issues_805_restapi_structure_endpoint' into issues_805_…
Oct 27, 2017
e243c75
Merge branch 'materialscloud_aiidateam' into issues_805_restapi_struc…
Oct 27, 2017
510f08c
Fixed issue #872
Oct 27, 2017
d49ab18
Merge branch 'issues_805_restapi_structure_endpoint' into issues_805_…
Oct 27, 2017
99589f5
Merge pull request #873 from waychal/issues_805_restapi_structure_end…
Oct 27, 2017
79d2f65
Merge branch 'develop' into materialscloud
ltalirz Oct 31, 2017
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
21 changes: 20 additions & 1 deletion aiida/backends/tests/restapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def process_dummy_data(cls):
"""
computer_projects = ["id", "uuid", "name", "hostname",
"transport_type", "scheduler_type"]
computers = expected_data = QueryBuilder().append(
computers = QueryBuilder().append(
Computer, tag="comp", project=computer_projects).order_by(
{'comp': [{'name': {'order': 'asc'}}]}).dict()

Expand Down Expand Up @@ -750,3 +750,22 @@ def test_calculation_attributes_alist_filter(self):
RESTApiTestCase.compare_extra_response_data(self, "calculations",
url,
response, uuid=node_uuid)

############### Structure visualization and download #############
def test_structure_visualization(self):
"""
Get the list of give calculation inputs
"""
node_uuid = self.get_dummy_data()["data"][3]["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)
expected_visdata = """#\\#CIF1.1\n##########################################################################\n# Crystallographic Information Format file \n# Produced by PyCifRW module\n# \n# This is a CIF file. CIF has been adopted by the International\n# Union of Crystallography as the standard for data archiving and \n# transmission.\n#\n# For information on this file format, follow the CIF links at\n# http://www.iucr.org\n##########################################################################\n\ndata_0\nloop_\n _atom_site_label\n _atom_site_fract_x\n _atom_site_fract_y\n _atom_site_fract_z\n _atom_site_type_symbol\n Ba1 0.0 0.0 0.0 Ba\n \n_cell_angle_alpha 90.0\n_cell_angle_beta 90.0\n_cell_angle_gamma 90.0\n_cell_length_a 2.0\n_cell_length_b 2.0\n_cell_length_c 2.0\nloop_\n _symmetry_equiv_pos_as_xyz\n 'x, y, z'\n \n_symmetry_int_tables_number 1\n_symmetry_space_group_name_H-M 'P 1'\n"""
self.assertEquals(response["data"]["visualization"]["cif"],expected_visdata)
RESTApiTestCase.compare_extra_response_data(self, "structures",
url,
response, uuid=node_uuid)

63 changes: 60 additions & 3 deletions aiida/restapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from flask import Flask, jsonify
from flask_restful import Api
from werkzeug.exceptions import HTTPException

class App(Flask):
"""
Expand All @@ -37,7 +38,7 @@ def __init__(self, *args, **kwargs):

# Error handler
from aiida.restapi.common.exceptions import RestInputValidationError, \
RestValidationError
RestValidationError, RestFeatureNotAvailable

if catch_internal_server:

Expand All @@ -52,6 +53,10 @@ def error_handler(error):
response = jsonify({'message': error.message})
response.status_code = 400

elif isinstance(error, RestFeatureNotAvailable):
response = jsonify({'message': error.message})
response.status_code = 501

# Generic server-side error (not to make the api crash if an
# unhandled exception is raised. Caution is never enough!!)
else:
Expand Down Expand Up @@ -87,9 +92,19 @@ def __init__(self, app=None, **kwargs):
"""

from aiida.restapi.resources import Calculation, Computer, User, Code, Data, \
Group, Node, StructureData, KpointsData, BandsData
Group, Node, StructureData, KpointsData, BandsData, ServerInfo

self.app = app

super(AiidaApi, self).__init__(app=app, prefix=kwargs['PREFIX'], catch_all_404s=True)


super(AiidaApi, self).__init__(app=app, prefix=kwargs['PREFIX'])
self.add_resource(ServerInfo,
"/server/",
"/server/endpoints/",
endpoint='server',
strict_slashes=False,
resource_class_kwargs=kwargs)

## Add resources and endpoints to the api
self.add_resource(Computer,
Expand All @@ -99,6 +114,7 @@ def __init__(self, app=None, **kwargs):
'/computers/page/<int:page>/',
'/computers/<id>/',
'/computers/schema/',
endpoint='computers',
strict_slashes=False,
resource_class_kwargs=kwargs)

Expand All @@ -119,6 +135,7 @@ def __init__(self, app=None, **kwargs):
'/nodes/<id>/content/attributes/',
'/nodes/<id>/content/extras/',
'/nodes/<id>/content/visualization/',
endpoint='nodes',
strict_slashes=False,
resource_class_kwargs=kwargs)

Expand All @@ -137,6 +154,7 @@ def __init__(self, app=None, **kwargs):
'/calculations/<id>/io/tree/',
'/calculations/<id>/content/attributes/',
'/calculations/<id>/content/extras/',
endpoint='calculations',
strict_slashes=False,
resource_class_kwargs=kwargs)

Expand All @@ -156,6 +174,8 @@ def __init__(self, app=None, **kwargs):
'/data/<id>/content/attributes/',
'/data/<id>/content/extras/',
'/data/<id>/content/visualization/',
'/data/<id>/content/download/',
endpoint='data',
strict_slashes=False,
resource_class_kwargs=kwargs)

Expand All @@ -175,6 +195,7 @@ def __init__(self, app=None, **kwargs):
'/codes/<id>/content/attributes/',
'/codes/<id>/content/extras/',
'/codes/<id>/content/visualization/',
endpoint='codes',
strict_slashes=False,
resource_class_kwargs=kwargs)

Expand All @@ -194,6 +215,8 @@ def __init__(self, app=None, **kwargs):
'/structures/<id>/content/attributes/',
'/structures/<id>/content/extras/',
'/structures/<id>/content/visualization/',
'/structures/<id>/content/download/',
endpoint='structures',
strict_slashes=False,
resource_class_kwargs=kwargs
)
Expand All @@ -214,6 +237,7 @@ def __init__(self, app=None, **kwargs):
'/kpoints/<id>/content/attributes/',
'/kpoints/<id>/content/extras/',
'/kpoints/<id>/content/visualization/',
endpoint='kpoints',
strict_slashes=False,
resource_class_kwargs=kwargs
)
Expand All @@ -234,6 +258,7 @@ def __init__(self, app=None, **kwargs):
'/bands/<id>/content/attributes/',
'/bands/<id>/content/extras/',
'/bands/<id>/content/visualization/',
endpoint='bands',
strict_slashes=False,
resource_class_kwargs=kwargs
)
Expand All @@ -253,5 +278,37 @@ def __init__(self, app=None, **kwargs):
'/groups/page/',
'/groups/page/<int:page>/',
'/groups/<id>/',
endpoint='groups',
strict_slashes=False,
resource_class_kwargs=kwargs)


def handle_error(self, e):
"""
this method handles the 404 "URL not found" exception and return custom message
:param e: raised exception
:return: list of available endpoints
"""

if isinstance(e, HTTPException):
if e.code == 404:

from aiida.restapi.common.config import PREFIX
import json

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)

return jsonify(response)

else:
raise e
6 changes: 5 additions & 1 deletion aiida/restapi/common/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
or internal errors, are not embedded into the HTTP response.
"""

from aiida.common.exceptions import ValidationError, InputValidationError
from aiida.common.exceptions import ValidationError, InputValidationError, \
FeatureNotAvailable

class RestValidationError(ValidationError):
"""
Expand All @@ -31,4 +32,7 @@ class RestInputValidationError(InputValidationError):
"""
document with an example
"""
pass

class RestFeatureNotAvailable(FeatureNotAvailable):
pass
33 changes: 29 additions & 4 deletions aiida/restapi/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,8 @@ def validate_request(self, limit=None, offset=None, perpage=None, page=None,
raise RestValidationError("perpage key requires that a page is "
"requested (i.e. the path must contain "
"/page/)")
# 4. No querystring if query type = schema', 'visualization', 'schema'
if query_type in ('schema', 'visualization', 'statistics') and \
is_querystring_defined:
# 4. No querystring if query type = schema'
if query_type in ('schema') and is_querystring_defined:
raise RestInputValidationError("schema requests do not allow "
"specifying a query string")

Expand Down Expand Up @@ -509,6 +508,8 @@ def build_translator_parameters(self, field_list):
nalist = None
elist = None
nelist = None
downloadformat = None
visformat = None

## Count how many time a key has been used for the filters and check if
# reserved keyword
Expand Down Expand Up @@ -560,6 +561,14 @@ def build_translator_parameters(self, field_list):
raise RestInputValidationError(
"You cannot specify nelist more than "
"once")
if 'format' in field_counts.keys() and field_counts['format'] > 1:
raise RestInputValidationError(
"You cannot specify format more than "
"once")
if 'visformat' in field_counts.keys() and field_counts['visformat'] > 1:
raise RestInputValidationError(
"You cannot specify visformat more than "
"once")

## Extract results
for field in field_list:
Expand Down Expand Up @@ -627,6 +636,22 @@ def build_translator_parameters(self, field_list):
raise RestInputValidationError(
"only assignment operator '=' "
"is permitted after 'orderby'")

elif field[0] == 'format':
if field[1] == '=':
downloadformat = field[2]
else:
raise RestInputValidationError(
"only assignment operator '=' "
"is permitted after 'format'")
elif field[0] == 'visformat':
if field[1] == '=':
visformat = field[2]
else:
raise RestInputValidationError(
"only assignment operator '=' "
"is permitted after 'visformat'")

else:

## Construct the filter entry.
Expand Down Expand Up @@ -659,7 +684,7 @@ def build_translator_parameters(self, field_list):
# limit = self.LIMIT_DEFAULT

return (limit, offset, perpage, orderby, filters, alist, nalist, elist,
nelist)
nelist, downloadformat, visformat)

def parse_query_string(self, query_string):
"""
Expand Down
Loading