From 6dad4b1cc57a54c86e001877afb95f6e4006c28b Mon Sep 17 00:00:00 2001 From: Markus Scheidgen <markus.scheidgen@gmail.com> Date: Sat, 22 Dec 2018 12:00:18 +0100 Subject: [PATCH] Refactored api arepo part to proper restplus. --- nomad/api/__init__.py | 4 +- nomad/api/repository.py | 193 ---------------------------------------- nomad/api/upload.py | 20 ++--- tests/test_api.py | 10 +-- 4 files changed, 13 insertions(+), 214 deletions(-) delete mode 100644 nomad/api/repository.py diff --git a/nomad/api/__init__.py b/nomad/api/__init__.py index e42758024b..7a20557905 100644 --- a/nomad/api/__init__.py +++ b/nomad/api/__init__.py @@ -24,12 +24,12 @@ There is a separate documentation for the API endpoints from a client perspectiv .. automodule:: nomad.api.app .. automodule:: nomad.api.auth .. automodule:: nomad.api.upload -.. automodule:: nomad.api.repository +.. automodule:: nomad.api.repo .. automodule:: nomad.api.archive .. automodule:: nomad.api.admin """ from .app import app -from . import auth, admin, upload, repository, archive, raw +from . import auth, admin, upload, repo, archive, raw @app.before_first_request diff --git a/nomad/api/repository.py b/nomad/api/repository.py deleted file mode 100644 index c6a9fae295..0000000000 --- a/nomad/api/repository.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright 2018 Markus Scheidgen -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an"AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -The repository API of the nomad@FAIRDI APIs. Currently allows to resolve repository -meta-data. -""" - -from elasticsearch.exceptions import NotFoundError -from flask import g, request -from flask_restplus import Resource, abort - -from nomad.repo import RepoCalc - -from .app import api, base_path -from .auth import login_if_available - - -class RepoCalcRes(Resource): - def get(self, upload_hash, calc_hash): - """ - Get calculation data in repository form, which only entails the quanties shown - in the repository. This is basically the elastic search index entry for the - requested calculations. Calcs are references via *upload_hash*, *calc_hash* - pairs. - - .. :quickref: repo; Get calculation data in repository form. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/repo/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "calc_hash":"7ddvtfRfZAvc3Crr7jOJ8UH0T34I", - "upload_time":"2018-08-30T08:41:51.771367", - "upload_id":"5b87adb813a441000a70a968", - "upload_hash":"W36aqCzAKxOCfIiMFsBJh3nHPb4a", - "mainfile":"RopD3Mo8oMV_-E5bh8uW5PiiCRkH1/data/BrK_svSi/TFCC010.CAB/vasprun.xml.relax1", - "program_name":"VASP", - "program_version":"4.6.35 3Apr08 complex parallel LinuxIFC", - "chemical_composition":"BrKSi2", - "basis_set_type":"plane waves", - "atom_species":[ - 35, - 19, - 14, - 14 - ], - "system_type":"Bulk", - "crystal_system":"orthorhombic", - "space_group_number":47, - "configuration_raw_gid":"sq6wTJjRKb2VTajoDLVWDxHCgyN6i", - "XC_functional_name":"GGA_X_PBE" - } - - :param string upload_hash: the hash of the upload (from uploaded file contents) - :param string calc_hash: the hash of the calculation (from mainfile) - :resheader Content-Type: application/json - :status 200: calc successfully retrieved - :status 404: calc with given hashes does not exist - :returns: the repository calculation entry - """ - try: - return RepoCalc.get(id='%s/%s' % (upload_hash, calc_hash)).json_dict, 200 - except NotFoundError: - abort(404, message='There is no calculation for %s/%s' % (upload_hash, calc_hash)) - except Exception as e: - abort(500, message=str(e)) - - -class RepoCalcsRes(Resource): - @login_if_available - def get(self): - """ - Get *'all'* calculations in repository from, paginated. - - .. :quickref: repo; Get *'all'* calculations in repository from, paginated. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/repo?page=1&per_page=25 HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "pagination":{ - "total":1, - "page":1, - "per_page":25 - }, - "results":[ - { - "calc_hash":"7ddvtfRfZAvc3Crr7jOJ8UH0T34I", - "upload_time":"2018-08-30T08:41:51.771367", - "upload_id":"5b87adb813a441000a70a968", - "upload_hash":"W36aqCzAKxOCfIiMFsBJh3nHPb4a", - "mainfile":"RopD3Mo8oMV_-E5bh8uW5PiiCRkH1/data/BrK_svSi/TFCC010.CAB/vasprun.xml.relax1", - "program_name":"VASP", - "program_version":"4.6.35 3Apr08 complex parallel LinuxIFC", - "chemical_composition":"BrKSi2", - "basis_set_type":"plane waves", - "atom_species":[ - 35, - 19, - 14, - 14 - ], - "system_type":"Bulk", - "crystal_system":"orthorhombic", - "space_group_number":47, - "configuration_raw_gid":"sq6wTJjRKb2VTajoDLVWDxHCgyN6i", - "XC_functional_name":"GGA_X_PBE" - } - ] - } - - :qparam int page: the page starting with 1 - :qparam int per_page: desired calcs per page - :qparam string owner: specifies which cals to return: all|user|staging, default is all - :resheader Content-Type: application/json - :status 200: calcs successfully retrieved - :returns: a list of repository entries in ``results`` and pagination info - """ - # TODO use argparse? bad request reponse an bad params, pagination as decorator - page = int(request.args.get('page', 1)) - per_page = int(request.args.get('per_page', 10)) - owner = request.args.get('owner', 'all') - - try: - assert page >= 1 - assert per_page > 0 - except AssertionError: - abort(400, message='invalid pagination') - - if owner == 'all': - search = RepoCalc.search().query('match_all') - elif owner == 'user': - if g.user is None: - abort(401, message='Authentication required for owner value user.') - search = RepoCalc.search().query('match_all') - search = search.filter('term', user_id=str(g.user.user_id)) - elif owner == 'staging': - if g.user is None: - abort(401, message='Authentication required for owner value user.') - search = RepoCalc.search().query('match_all') - search = search.filter('term', user_id=str(g.user.user_id)).filter('term', staging=True) - else: - abort(400, message='Invalid owner value. Valid values are all|user|staging, default is all') - - search = search[(page - 1) * per_page: page * per_page] - return { - 'pagination': { - 'total': search.count(), - 'page': page, - 'per_page': per_page - }, - 'results': [result.json_dict for result in search] - } - - -api.add_resource(RepoCalcsRes, '%s/repo' % base_path) -api.add_resource(RepoCalcRes, '%s/repo/<string:upload_hash>/<string:calc_hash>' % base_path) diff --git a/nomad/api/upload.py b/nomad/api/upload.py index 9144a6bffe..39ba5fc27c 100644 --- a/nomad/api/upload.py +++ b/nomad/api/upload.py @@ -28,6 +28,7 @@ from nomad.files import UploadFile from .app import api, base_path from .auth import login_really_required +from .common import pagination_request_parser, pagination_model ns = api.namespace( @@ -47,7 +48,7 @@ proc_model = api.model('Processing', { '_async_status': fields.String(description='Only for debugging nomad') }) -upload_model = api.inherit('Upload', proc_model, { +upload_model = api.inherit('UploadProcessing', proc_model, { 'name': fields.String( description='The name of the upload. This can be provided during upload ' 'using the name query parameter.'), @@ -65,7 +66,7 @@ upload_model = api.inherit('Upload', proc_model, { 'upload_time': fields.DateTime(dt_format='iso8601'), }) -calc_model = api.inherit('Calculation', proc_model, { +calc_model = api.inherit('UploadCalculationProcessing', proc_model, { 'archive_id': fields.String, 'mainfile': fields.String, 'upload_id': fields.String, @@ -77,13 +78,10 @@ upload_with_calcs_model = api.inherit('UploadWithPaginatedCalculations', upload_ 'total_calcs': fields.Integer, 'failed_calcs': fields.Integer, 'pending_calcs': fields.Integer, - 'calcs': fields.Nested(model=api.model('PaginatedCalculations', { - 'pagination': fields.Nested(model=api.model('Pagination', { - 'total': fields.Integer, + 'calcs': fields.Nested(model=api.model('UploadPaginatedCalculations', { + 'pagination': fields.Nested(model=api.inherit('UploadCalculationPagination', pagination_model, { 'successes': fields.Integer, 'failures': fields.Integer, - 'page': fields.Integer, - 'per_page': fields.Integer, })), 'results': fields.List(fields.Nested(model=calc_model)) })) @@ -183,18 +181,12 @@ class ProxyUpload: return self.upload.__getattribute__(name) -pagination_parser = api.parser() -pagination_parser.add_argument('page', type=int, help='The page, starting with 1.', location='args') -pagination_parser.add_argument('per_page', type=int, help='Desired calcs per page.', location='args') -pagination_parser.add_argument('order_by', type=str, help='The field to sort the calcs by, use [status,mainfile].', location='args') - - @ns.route('/<string:upload_id>') @api.doc(params={'upload_id': 'The unique id for the requested upload.'}) class Upload(Resource): @api.response(404, 'Upload does not exist') @api.marshal_with(upload_with_calcs_model, skip_none=True, code=200, description='Upload send') - @api.expect(pagination_parser) + @api.expect(pagination_request_parser) @login_really_required def get(self, upload_id: str): """ diff --git a/tests/test_api.py b/tests/test_api.py index 6af77eaca5..a9de7f6014 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -255,7 +255,7 @@ class TestRepo: assert rv.status_code == 404 def test_calcs(self, client, example_elastic_calc, no_warn): - rv = client.get('/repo') + rv = client.get('/repo/') assert rv.status_code == 200 data = json.loads(rv.data) results = data.get('results', None) @@ -264,7 +264,7 @@ class TestRepo: assert len(results) >= 1 def test_calcs_pagination(self, client, example_elastic_calc, no_warn): - rv = client.get('/repo?page=1&per_page=1') + rv = client.get('/repo/?page=1&per_page=1') assert rv.status_code == 200 data = json.loads(rv.data) results = data.get('results', None) @@ -273,7 +273,7 @@ class TestRepo: assert len(results) == 1 def test_calcs_user(self, client, example_elastic_calc, test_user_auth, no_warn): - rv = client.get('/repo?owner=user', headers=test_user_auth) + rv = client.get('/repo/?owner=user', headers=test_user_auth) assert rv.status_code == 200 data = json.loads(rv.data) results = data.get('results', None) @@ -281,11 +281,11 @@ class TestRepo: assert len(results) >= 1 def test_calcs_user_authrequired(self, client, example_elastic_calc, no_warn): - rv = client.get('/repo?owner=user') + rv = client.get('/repo/?owner=user') assert rv.status_code == 401 def test_calcs_user_invisible(self, client, example_elastic_calc, test_other_user_auth, no_warn): - rv = client.get('/repo?owner=user', headers=test_other_user_auth) + rv = client.get('/repo/?owner=user', headers=test_other_user_auth) assert rv.status_code == 200 data = json.loads(rv.data) results = data.get('results', None) -- GitLab