Commit 4a68029c authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added index database and moved optimade to v0. #325

parent 66c7a325
Pipeline #76318 failed with stages
in 27 minutes and 37 seconds
......@@ -22,5 +22,6 @@ from flask_restplus import Api
from .api import blueprint, url, api
# TODO ReferenceList, Reference, ReferenceInfo, Links are missing, because the implement
# the wrong thing.
from .endpoints import CalculationList, Calculation, CalculationInfo, Info, Structure
from .endpoints import CalculationList, Calculation, CalculationInfo, Info, Structure, StructuresInfo, StructureList
from .index import Info
from .filterparser import parse_filter
......@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from flask import Blueprint
from flask import Blueprint, request, abort
from flask_restplus import Api
import urllib.parse
......@@ -25,12 +25,18 @@ base_url = 'http://%s/%s/optimade' % (
config.services.api_base_path.strip('/'))
def url(endpoint: str = None, **kwargs):
def url(endpoint: str = None, version='v0', **kwargs):
''' Returns the full optimade api url (for a given endpoint) including query parameters. '''
if endpoint is None:
url = base_url
if version is not None:
url = '%s/%s' % (base_url, version)
else:
url = base_url
else:
url = '%s/%s' % (base_url, endpoint)
if version is not None:
url = '%s/%s/%s' % (base_url, version, endpoint)
else:
url = '%s/%s' % (base_url, endpoint)
if len(kwargs) > 0:
return '%s?%s' % (url, urllib.parse.urlencode(kwargs))
......@@ -38,6 +44,17 @@ def url(endpoint: str = None, **kwargs):
return url
# TODO replace with decorator that filters response_fields
def base_request_args():
if request.args.get('response_format', 'json') != 'json':
abort(400, 'Response format is not supported.')
properties_str = request.args.get('request_fields', None)
if properties_str is not None:
return properties_str.split(',')
return None
api = Api(
blueprint,
version='1.0', title='NOMAD\'s OPTiMaDe API implementation',
......
......@@ -20,7 +20,7 @@ from elasticsearch_dsl import Q
from nomad import search, files, datamodel
from nomad.datamodel import OptimadeEntry
from .api import api, url
from .api import api, url, base_request_args
from .models import json_api_single_response_model, entry_listing_endpoint_parser, Meta, \
Links as LinksModel, CalculationDataObject, single_entry_endpoint_parser, base_endpoint_parser, \
json_api_info_response_model, json_api_list_response_model, ReferenceObject, StructureObject, \
......@@ -28,21 +28,9 @@ from .models import json_api_single_response_model, entry_listing_endpoint_parse
json_api_structure_response_model, json_api_structures_response_model
from .filterparser import parse_filter, FilterException
ns = api.namespace('v0', description='The version v0 API namespace with all OPTiMaDe endpoints.')
# TODO replace with decorator that filters response_fields
def base_request_args():
if request.args.get('response_format', 'json') != 'json':
abort(400, 'Response format is not supported.')
properties_str = request.args.get('request_fields', None)
if properties_str is not None:
return properties_str.split(',')
return None
def base_search_request():
''' Creates a search request for all public and optimade enabled data. '''
return search.SearchRequest().owner('all', None).search_parameter('processed', True).query(
......@@ -326,7 +314,7 @@ class Links(Resource):
@ns.route('/structures')
class Structures(Resource):
class StructureList(Resource):
@api.doc('structures')
@api.response(400, 'Invalid requests, e.g. bad parameter.')
@api.response(422, 'Validation 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 typing import List, Dict, Any
from flask_restplus import Resource, abort
from flask import request
from elasticsearch_dsl import Q
from nomad import search, files, datamodel
from nomad.datamodel import OptimadeEntry
from .api import api, url, base_request_args
from .models import json_api_single_response_model, entry_listing_endpoint_parser, Meta, \
Links as LinksModel, CalculationDataObject, single_entry_endpoint_parser, base_endpoint_parser, \
json_api_info_response_model, json_api_list_response_model, ReferenceObject, StructureObject, \
ToplevelLinks, LinkObject, json_api_links_response_model, json_api_references_response_model, \
json_api_structure_response_model, json_api_structures_response_model
from .filterparser import parse_filter, FilterException
ns = api.namespace('', description='This is the OPTiMaDe index for NOMAD\' implementations.')
@ns.route('/info')
class Info(Resource):
@api.doc('index_info')
@api.response(400, 'Invalid requests, e.g. bad parameter.')
@api.expect(base_endpoint_parser, validate=True)
@api.marshal_with(json_api_single_response_model, skip_none=True, code=200)
def get(self):
''' Returns information relating to the API implementation- '''
base_request_args()
result = {
'type': 'info',
'id': '/',
'attributes': {
'api_version': '0.10.0',
'available_api_versions': [{
'url': url(),
'version': '0.10.0'
}],
'formats': ['json'],
'entry_types_by_format': {
'json': ['links', 'info']
},
'available_endpoints': ['links', 'info'],
'is_index': True
}
}
return dict(
meta=Meta(query=request.url, returned=1),
data=result
), 200
@ns.route('/links')
class Links(Resource):
@api.doc('index_info')
@api.response(400, 'Invalid requests, e.g. bad parameter.')
@api.expect(base_endpoint_parser, validate=True)
@api.marshal_with(json_api_single_response_model, skip_none=True, code=200)
def get(self):
''' Returns information relating to the API implementation- '''
base_request_args()
result = [
{
"type": "parent",
"id": "index",
"attributes": {
"name": "NOMAD OPTiMaDe index",
"description": "Index for NOMAD's OPTiMaDe implemenations",
"base_url": url(version=None),
"homepage": "http://nomad-coe.eu"
}
},
{
"type": "child",
"id": "v0",
"attributes": {
"name": "NOMAD OPTiMaDe v0",
"description": "Novel Materials Discovery OPTiMaDe implementations v0",
"base_url": {
"href": url(),
},
"homepage": "http://nomad-coe.eu"
}
}
]
return dict(
meta=Meta(query=request.url, returned=2),
data=result
), 200
......@@ -194,13 +194,13 @@ json_api_response_model = api.model('Response', {
model=json_api_links_model),
'meta': fields.Nested(
required=True,
required=True, skip_none=True,
description='JSON API meta object.',
model=json_api_meta_object_model),
'included': fields.List(
fields.Arbitrary(),
required=False,
required=False, skip_none=True,
description=('A list of JSON API resource objects related to the primary data '
'contained in data. Responses that contain related resources under '
'included are known as compound documents in the JSON API.'))
......@@ -226,6 +226,7 @@ json_api_data_object_model = api.model('DataObject', {
# further optional fields: links, meta, relationships
})
json_api_calculation_info_model = api.model('CalculationInfo', {
'description': fields.String(
description='Description of the entry'),
......@@ -354,7 +355,7 @@ json_api_single_response_model = api.inherit(
'SingleResponse', json_api_response_model, {
'data': fields.Nested(
model=json_api_data_object_model,
required=True,
required=True, skip_none=True,
description=('The returned response object.'))
})
......@@ -362,7 +363,7 @@ json_api_list_response_model = api.inherit(
'ListResponse', json_api_response_model, {
'data': fields.List(
fields.Nested(json_api_data_object_model),
required=True,
required=True, skip_none=True,
description=('The list of returned response objects.'))
})
......
......@@ -29,6 +29,21 @@ def api(session_client):
return BlueprintClient(session_client, '/optimade/v0')
@pytest.fixture(scope='session')
def index_api(session_client):
return BlueprintClient(session_client, '/optimade')
def test_index(index_api):
rv = index_api.get('/info')
assert rv.status_code == 200
rv = index_api.get('/links')
assert rv.status_code == 200
data = json.loads(rv.data)
assert data['data'][1]['attributes']['base_url']['href'].endswith('v0')
def test_get_entry(published: Upload):
calc_id = list(published.calcs)[0].calc_id
with published.upload_files.read_archive(calc_id) as archive:
......@@ -139,7 +154,7 @@ def test_optimade_parser(example_structures, query, results):
def test_url():
assert url('endpoint', param='value').endswith('/optimade/endpoint?param=value')
assert url('endpoint', param='value').endswith('/optimade/v0/endpoint?param=value')
def test_list_endpoint(api, example_structures):
......
Supports Markdown
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