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