Commit 89b53964 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added first implementation for dcat api.

parent 7554fc1d
......@@ -17,8 +17,8 @@
#
'''
This module comprises the nomad@FAIRDI APIs. Currently there is NOMAD's official api, and
we will soon at the optimade api. The app module also servers documentation, gui, and
This module comprises the nomad@FAIRDI APIs. Currently there is NOMAD's official api, optimade api,
and dcat api. The app module also servers documentation, gui, and
alive.
'''
from flask import Flask, Blueprint, jsonify, url_for, abort, request, make_response
......@@ -37,6 +37,7 @@ from nomad import config, utils as nomad_utils
from .api import blueprint as api_blueprint, api
from .optimade import blueprint as optimade_blueprint, api as optimade
from .dcat import blueprint as dcat_blueprint
from .docs import blueprint as docs_blueprint
from .dist import blueprint as dist_blueprint
from .gui import blueprint as gui_blueprint
......@@ -112,6 +113,7 @@ CORS(app)
app.register_blueprint(api_blueprint, url_prefix='/api')
app.register_blueprint(optimade_blueprint, url_prefix='/optimade')
app.register_blueprint(dcat_blueprint, url_prefix='/dcat')
app.register_blueprint(docs_blueprint, url_prefix='/docs')
app.register_blueprint(dist_blueprint, url_prefix='/dist')
app.register_blueprint(gui_blueprint, url_prefix='/gui')
......
# 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 optimade implementation of NOMAD.
'''
from flask import Blueprint
from flask_restplus import Api
from .api import blueprint, api
from .datasets import Dataset
# 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.
from flask import Blueprint
from flask_restplus import Api
import urllib.parse
from nomad import config
blueprint = Blueprint('dcat', __name__)
base_url = 'https://%s/%s/dcat' % (
config.services.api_host.strip('/'),
config.services.api_base_path.strip('/'))
def url(*args, **kwargs):
''' Returns the full dcat api url for the given path (args) and query (kwargs) parameters. '''
url = base_url + '/' + '/'.join(args)
if len(kwargs) > 0:
return '%s?%s' % (url, urllib.parse.urlencode(kwargs))
else:
return url
api = Api(
blueprint,
version='1.0', title='NOMAD\'s API for servicing dcat resources',
description='NOMAD\'s API for serving dcat resources',
validate=True)
# For some unknown reason it is necessary for each fr api to have a handler.
# Otherwise the global app error handler won't be called.
@api.errorhandler(Exception)
def errorhandler(error):
'''When an internal server error is caused by an unexpected exception.'''
return str(error)
# 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.
from flask_restplus import Resource, abort, reqparse
from flask import Response
from elasticsearch.exceptions import NotFoundError
from nomad import search
from .api import api
from .mapping import Mapping
ns = api.namespace('datasets', description='The API for DCAT datasets.')
arg_parser = reqparse.RequestParser()
arg_parser.add_argument('format', type=str, choices=[
'xml',
'n3',
'turtle',
'nt',
'pretty-xml',
'trig'])
@ns.route('/<string:entry_id>')
class Dataset(Resource):
@api.doc('get_dcat_dataset')
@api.expect(arg_parser)
@api.produces(['application/xml'])
@api.response(404, 'There is no entry with the given id.')
@api.response(401, 'This entry is not publically accessible.')
@api.response(200, 'Data send', headers={'Content-Type': 'application/xml'})
def get(self, entry_id):
''' Returns a DCAT dataset for a given NOMAD entry id. '''
format_ = arg_parser.parse_args().get('format')
if format_ is None:
format_ = 'xml'
try:
entry = search.entry_document.get(entry_id)
except NotFoundError:
abort(404, message='There is no calculation with id %s' % entry_id)
if entry.with_embargo or not entry.published:
abort(401, message='Not authorized to access %s' % entry_id)
mapping = Mapping()
mapping.map_entry(entry)
return Response(
mapping.g.serialize(format=format_).decode('utf-8'), 200,
{'Content-Type': 'application/xml'})
# Copyright 2020 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.
from rdflib import Graph, Literal, RDF, URIRef, BNode
from rdflib.namespace import Namespace, DCAT, DCTERMS as DCT
from nomad import config
from nomad.datamodel import User
from nomad.datamodel import EntryMetadata, User
from .api import url
VCARD = Namespace('http://www.w3.org/2006/vcard/ns#')
class Mapping():
def __init__(self):
self.g = Graph()
self.g.namespace_manager.bind('dcat', DCAT)
self.g.namespace_manager.bind('dct', DCT)
self.g.namespace_manager.bind('vcard', VCARD)
self.vcards = {}
def map_entry(self, entry: EntryMetadata):
dataset = URIRef(url('datasets', entry.calc_id))
self.g.add((dataset, RDF.type, DCAT.Dataset))
self.g.add((dataset, DCT.identifier, Literal(entry.calc_id)))
self.g.add((dataset, DCT.issued, Literal(entry.upload_time)))
self.g.add((dataset, DCT.modified, Literal(entry.last_processing)))
self.g.add((dataset, DCAT.landing_page, URIRef('%s/entry/id/%s/%s' % (
config.gui_url(), entry.upload_id, entry.calc_id))))
self.g.add((dataset, DCT.title, Literal('unavailbale' if entry.formula is None else entry.formula)))
self.g.add((dataset, DCT.description, Literal('unavailbale' if entry.comment is None else entry.comment)))
self.g.add((dataset, DCT.publisher, self.map_user(entry.uploader)))
for author in entry.authors:
self.g.add((dataset, DCT.creator, self.map_user(author)))
return dataset
def map_user(self, user: User):
vcard = self.vcards.get(user.user_id)
if vcard is not None:
return vcard
user = User.get(user.user_id)
vcard = BNode()
self.g.add((vcard, RDF.type, VCARD.Individual))
self.g.add((vcard, VCARD.givenName, Literal(user.first_name)))
self.g.add((vcard, VCARD.familyName, Literal(user.last_name)))
self.g.add((vcard, VCARD.nickName, Literal(user.username)))
self.g.add((vcard, VCARD.hasEmail, Literal(user.email)))
self.vcards[user.user_id] = vcard
return vcard
# 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.
import json
import pytest
from datetime import datetime
from nomad.datamodel import EntryMetadata
from tests.conftest import clear_elastic
from tests.app.test_app import BlueprintClient
@pytest.fixture(scope='session')
def api(session_client):
return BlueprintClient(session_client, '/dcat')
@pytest.fixture(scope='module')
def example_entry(elastic_infra, test_user, other_test_user):
clear_elastic(elastic_infra)
entry = EntryMetadata(
calc_id='test-id',
upload_id='upload-id',
upload_time=datetime.now(),
last_processing=datetime.now(),
uploader=test_user,
coauthors=[other_test_user],
comment='this is a calculation comment',
formula='H20',
published=True)
entry.a_elastic.index()
yield
clear_elastic(elastic_infra)
def test_get_dataset(api, example_entry):
calc_id = 'test-id'
rv = api.get('/datasets/%s' % calc_id)
assert rv.status_code == 200
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment