From a5485feef5a9469f8fc6261e3627965b88fc7f87 Mon Sep 17 00:00:00 2001 From: Markus Scheidgen <markus.scheidgen@gmail.com> Date: Fri, 31 Aug 2018 17:04:28 +0200 Subject: [PATCH] Added some REST api documentation. --- docs/api.rst | 14 +++ docs/conf.py | 7 +- docs/index.rst | 3 +- docs/{modules.md => reference.md} | 7 +- nomad/api.py | 192 ++++++++++++++++++++++++++++-- nomad/data.py | 20 +++- nomad/dependencies.py | 1 + nomad/files.py | 2 +- nomad/parsing.py | 3 + nomad/processing/__init__.py | 3 + nomad/users.py | 99 --------------- requirements.txt | 3 +- 12 files changed, 237 insertions(+), 117 deletions(-) create mode 100644 docs/api.rst rename docs/{modules.md => reference.md} (83%) delete mode 100644 nomad/users.py diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000000..121ae97a2f --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,14 @@ +API Documentation +================= + +Summary +------- + +.. qrefflask:: nomad.api:app + :undoc-static: + +API Details +----------- + +.. autoflask:: nomad.api:app + :undoc-static: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 585bbed46d..1a1a16a4aa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,14 +44,17 @@ extensions = [ 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', - 'sphinx.ext.napoleon' + 'sphinx.ext.napoleon', + 'sphinxcontrib.httpdomain', + 'sphinxcontrib.autohttp.flask', + 'sphinxcontrib.autohttp.flaskqref', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] source_parsers = { - '.md': 'recommonmark.parser.CommonMarkParser', + '.md': 'recommonmark.parser.CommonMarkParser', } # The suffix(es) of source filenames. diff --git a/docs/index.rst b/docs/index.rst index d2f0e8205e..5b413eda18 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,5 +9,6 @@ and infrastructure with a simplyfied and improved architecture. setup introduction - modules + api + reference contributing diff --git a/docs/modules.md b/docs/reference.md similarity index 83% rename from docs/modules.md rename to docs/reference.md index 96b684e3b5..d7ff9d0627 100644 --- a/docs/modules.md +++ b/docs/reference.md @@ -1,4 +1,4 @@ -# Modules +# Reference ## nomad.config ```eval_rst @@ -26,3 +26,8 @@ ```eval_rst .. automodule:: nomad.processing ``` + +## nomad.data +```eval_rst +.. automodule:: nomad.data +``` diff --git a/nomad/api.py b/nomad/api.py index 107eab53e7..639a726927 100644 --- a/nomad/api.py +++ b/nomad/api.py @@ -5,7 +5,7 @@ from elasticsearch.exceptions import NotFoundError from nomad import config, files from nomad.utils import get_logger -from nomad.data import Calc, Upload, User, InvalidId, NotAllowedDuringProcessing +from nomad.data import Calc, Upload, User, InvalidId, NotAllowedDuringProcessing, me base_path = config.services.api_base_path @@ -17,19 +17,120 @@ CORS(app) api = Api(app) -# provid a fake user for testing -me = User.objects(email='me@gmail.com').first() -if me is None: - me = User(email='me@gmail.com', name='Me Meyer') - me.save() +class UploadsRes(Resource): + """ Uploads """ + def get(self): + """ + Get a list of current users uploads. + .. :quickref: Get a list of current users uploads. -class UploadsRes(Resource): + **Example request**: - def get(self): + .. sourcecode:: http + + GET /nomadxt/api/uploads HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + [ + { + "name": "examples_vasp_6.zip", + "upload_id": "5b89469e0d80d40008077dbc", + "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", + "create_time": "2018-08-31T13:46:06.781000", + "upload_time": "2018-08-31T13:46:07.531000", + "is_stale": false, + "is_ready": true, + "proc": { + "task_names": [ + "uploading", + "extracting", + "parse_all", + "cleanup" + ], + "current_task_name": "cleanup", + "status": "SUCCESS", + "errors": [], + "warnings": [], + "upload_id": "5b89469e0d80d40008077dbc", + "upload_hash": "rMB5F-gyHT0KY22eePoTjXibK95S", + "calc_procs": [] + } + } + ] + + :resheader Content-Type: application/json + :status 200: uploads successfully provided + :returns: list of :class:`nomad.data.Upload` + """ return [upload.json_dict for upload in Upload.user_uploads(me)], 200 def post(self): + """ + Create a new upload. Creating an upload on its own wont do much, but provide + a *presigned* upload URL. PUT a file to this URL to do the actual upload and + initiate the processing. + + .. :quickref: Create a new upload. + + **Example request**: + + .. sourcecode:: http + + POST /nomadxt/api/uploads HTTP/1.1 + Accept: application/json + Content-Type: application/json + + { + name: 'vasp_data.zip' + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + [ + { + "name": "vasp_data.zip", + "upload_id": "5b89469e0d80d40008077dbc", + "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", + "create_time": "2018-08-31T13:46:06.781000", + "is_stale": false, + "is_ready": false, + "proc": { + "task_names": [ + "uploading", + "extracting", + "parse_all", + "cleanup" + ], + "current_task_name": "uploading", + "status": "PENDING", + "errors": [], + "warnings": [], + "upload_id": "5b89469e0d80d40008077dbc", + "calc_procs": [] + } + } + ] + :jsonparam string name: An optional name for the upload. + :reqheader Content-Type: application/json + :resheader Content-Type: application/json + :status 200: upload successfully created + :returns: a new instance of :class:`nomad.data.Upload` + """ json_data = request.get_json() if json_data is None: json_data = {} @@ -38,7 +139,62 @@ class UploadsRes(Resource): class UploadRes(Resource): + """ Uploads """ def get(self, upload_id): + """ + Get an update for an existing upload. If the upload will be upaded with new data from the + processing infrastucture. You can use this endpoint to periodically pull the + new processing state until the upload ``is_ready``. + + .. :quickref: Get an update for an existing upload. + + **Example request**: + + .. sourcecode:: http + + GET /nomadxt/api/uploads/5b89469e0d80d40008077dbc HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + [ + { + "name": "vasp_data.zip", + "upload_id": "5b89469e0d80d40008077dbc", + "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", + "create_time": "2018-08-31T13:46:06.781000", + "upload_time": "2018-08-31T13:46:16.824000", + "is_stale": false, + "is_ready": false, + "proc": { + "task_names": [ + "uploading", + "extracting", + "parse_all", + "cleanup" + ], + "current_task_name": "extracting", + "status": "PROGRESS", + "errors": [], + "warnings": [], + "upload_id": "5b89469e0d80d40008077dbc", + "calc_procs": [] + } + } + ] + :param string upload_id: the id for the upload + :resheader Content-Type: application/json + :status 200: upload successfully updated and retrieved + :status 400: bad upload id + :status 404: upload with id does not exist + :returns: the :class:`nomad.data.Upload` instance + """ try: return Upload.get(upload_id=upload_id).json_dict, 200 except InvalidId: @@ -47,6 +203,26 @@ class UploadRes(Resource): abort(404, message='Upload with id %s does not exist.' % upload_id) def delete(self, upload_id): + """ + Deletes an existing upload. Only ``is_ready`` or ``is_stale`` uploads + can be deleted. Deleting an upload in processing is not allowed. + + .. :quickref: Delete an existing upload. + + **Example request**: + + .. sourcecode:: http + + DELETE /nomadxt/api/uploads/5b89469e0d80d40008077dbc HTTP/1.1 + Accept: application/json + + :param string upload_id: the id for the upload + :resheader Content-Type: application/json + :status 200: upload successfully deleted + :status 400: upload cannot be deleted + :status 404: upload with id does not exist + :returns: the :class:`nomad.data.Upload` instance with the latest processing state + """ try: return Upload.get(upload_id=upload_id).delete().json_dict, 200 except InvalidId: diff --git a/nomad/data.py b/nomad/data.py index 54309d6edc..b73e647460 100644 --- a/nomad/data.py +++ b/nomad/data.py @@ -17,10 +17,14 @@ This module comprises a set of persistent document classes that hold all user re data. These are information about users, their uploads and datasets, the associated calculations, and files -..autoclass:: nomad.data.Calc -..autoclass:: nomad.data.Upload -..autoclass:: nomad.data.DataSet -..autoclass:: nomad.data.User + +.. autoclass:: Calc + :members: +.. autoclass:: Upload + :members: +.. autoclass:: DataSet +.. autoclass:: User + """ from typing import List @@ -386,3 +390,11 @@ class DataSet(Document): 'calcs' ] } + +# provid a fake user for testing +me = None +if 'sphinx' not in sys.modules: + me = User.objects(email='me@gmail.com').first() + if me is None: + me = User(email='me@gmail.com', name='Me Meyer') + me.save() diff --git a/nomad/dependencies.py b/nomad/dependencies.py index 052cc40e0d..e7a4e7c06b 100644 --- a/nomad/dependencies.py +++ b/nomad/dependencies.py @@ -26,6 +26,7 @@ Preparing dependencies To make GIT maintained python modules available, we use: .. autoclass:: PythonGit + :members: Dependencies are configured in diff --git a/nomad/files.py b/nomad/files.py index b1cdd1c3ab..189e519ad5 100644 --- a/nomad/files.py +++ b/nomad/files.py @@ -36,6 +36,7 @@ authentication hassly, presigned URLs can be created that can be used directly t Uploads ------- .. autoclass:: Upload + :members: """ from typing import Callable, List, Any, Generator, IO, TextIO, cast @@ -127,7 +128,6 @@ def upload_put_handler(func: Callable[[str], None]) -> Callable[[], None]: 'Unhandled bucket event due to unexprected event format', bucket_event_record=event_record) - def wrapper(*args, **kwargs) -> None: logger.info('Start listening to uploads notifications.') diff --git a/nomad/parsing.py b/nomad/parsing.py index 2f3a1f9daf..1b9af32efb 100644 --- a/nomad/parsing.py +++ b/nomad/parsing.py @@ -30,6 +30,7 @@ For now, we make a few assumption about parsers Each parser is defined via an instance of :class:`Parser`. .. autoclass:: nomad.parsing.Parser + :members: The parser definitions are available via the following two variables. @@ -39,7 +40,9 @@ The parser definitions are available via the following two variables. Parsers in NOMAD-coe use a *backend* to create output. .. autoclass:: nomad.parsing.AbstractParserBackend + :members: .. autoclass:: nomad.parsing.LocalBackend + :members: """ from typing import TextIO, Tuple, List, Any, Callable, IO diff --git a/nomad/processing/__init__.py b/nomad/processing/__init__.py index c4104181ae..27448b50be 100644 --- a/nomad/processing/__init__.py +++ b/nomad/processing/__init__.py @@ -36,8 +36,11 @@ Represent processing state .. autoclass:: ProcPipeline + :members: .. autoclass:: UploadProc + :members: .. autoclass:: CalcProc + :members: Initiate processing ------------------- diff --git a/nomad/users.py b/nomad/users.py deleted file mode 100644 index b662eb2716..0000000000 --- a/nomad/users.py +++ /dev/null @@ -1,99 +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. - -""" -This module comprises a set of persistent document classes that hold all user related -data. These are information about users, their uploads and datasets, and the -associations between users and the assets stored in nomad-xt. - -..autoclass:: nomad.users.User -..autoclass:: nomad.users.Upload -..autoclass:: nomad.users.DataSet -""" - -import sys -from mongoengine import \ - Document, EmailField, StringField, BooleanField, DateTimeField, ListField, \ - DictField, ReferenceField, connect - -from nomad import config - -# ensure mongo connection -if 'sphinx' not in sys.modules: - connect(db=config.mongo.users_db, host=config.mongo.host) - - -class User(Document): - """ Represents users in the database. """ - email = EmailField(primary=True) - name = StringField() - - -class Upload(Document): - """ - Represents uploads in the databases. Provides persistence access to the files storage, - and processing system. - - Attributes: - file_name: Optional user provided upload name - upload_id: The upload id. Generated by the database. - in_staging: True if the upload is still in staging and can be edited by the uploader. - is_private: True if the upload and its derivitaves are only visible to the uploader. - proc: The :class:`nomad.processing.UploadProc` that holds the processing state. - created_time: The timestamp this upload was created. - upload_time: The timestamp when the system realised the upload. - proc_time: The timestamp when the processing realised finished by the system. - """ - - name = StringField(default=None) - - in_staging = BooleanField(default=True) - is_private = BooleanField(default=False) - - presigned_url = StringField() - upload_time = DateTimeField() - create_time = DateTimeField() - - proc_time = DateTimeField() - proc = DictField() - - user = ReferenceField(User, required=True) - - meta = { - 'indexes': [ - 'proc.upload_hash', - 'user' - ] - } - - @property - def upload_id(self): - return self.id.__str__() - - -class DataSet(Document): - name = StringField() - description = StringField() - doi = StringField() - - user = ReferenceField(User) - calcs = ListField(StringField) - - meta = { - 'indexes': [ - 'user', - 'doi', - 'calcs' - ] - } diff --git a/requirements.txt b/requirements.txt index 788b21caae..cc1afba35b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ numpy cython>=0.19 spglib gunicorn +structlog sphinx recommonmark -structlog +sphinxcontrib.httpdomain -- GitLab