Skip to content

Commit

Permalink
Merge branch 'develop' into feature/performance
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderWatzinger committed Oct 24, 2023
2 parents 7dc471d + e879fd5 commit c403410
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 7 deletions.
1 change: 1 addition & 0 deletions config/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@

# Used to connect to password protected Vocabs systems
VOCABS_PASS = ''
API_VERSIONS = ['0.3']
27 changes: 25 additions & 2 deletions openatlas/api/endpoints/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@

from openatlas import app
from openatlas.api.resources.model_mapper import get_overview_counts
from openatlas.api.resources.parser import language
from openatlas.api.resources.parser import language, default
from openatlas.api.resources.resolve_endpoints import download
from openatlas.api.resources.templates import (
class_overview_template, content_template, overview_template)
class_overview_template, content_template, overview_template,
backend_details_template)
from openatlas.models.content import get_translation


Expand All @@ -28,6 +29,28 @@ def get() -> Union[tuple[Resource, int], Response]:
return marshal(content, content_template()), 200


class GetBackendDetails(Resource):
@staticmethod
def get() -> Union[tuple[Resource, int], Response]:
parser = default.parse_args()
details = {
'version': app.config['VERSION'],
'apiVersions': app.config['API_VERSIONS'],
'siteName': g.settings['site_name'],
'imageProcessing': {
'enabled': g.settings['image_processing'],
'availableImageSizes':
app.config['IMAGE_SIZE']
if g.settings['image_processing'] else None},
'IIIF': {
'enabled': app.config['IIIF']['enabled'],
'url': app.config['IIIF']['url'],
'version': app.config['IIIF']['version']}}
if parser['download']:
download(details, backend_details_template(), 'content')
return marshal(details, backend_details_template()), 200


class ClassMapping(Resource):
@staticmethod
def get() -> Union[tuple[Resource, int], Response]:
Expand Down
16 changes: 16 additions & 0 deletions openatlas/api/resources/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,22 @@ def content_template() -> dict[str, Type[String]]:
'imageSizes': fields.Raw}


def backend_details_template() -> dict[str, Type[String]]:
image_processing = {
'enabled': fields.String,
'availableImageSizes': fields.Raw}
iiif = {
'enabled': fields.String,
'url': fields.String,
'version': fields.String}
return {
'version': fields.String,
'apiVersions': fields.Raw,
'siteName': fields.String,
'imageProcessing': fields.Nested(image_processing),
'IIIF': fields.Nested(iiif)}


def licensed_file_template(entities: list[Entity]) -> dict[str, Any]:
file = {
'display': fields.String,
Expand Down
6 changes: 5 additions & 1 deletion openatlas/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from openatlas.api.endpoints.iiif import \
(IIIFManifest, IIIFImageV2, IIIFCanvasV2, IIIFSequenceV2)
from openatlas.api.endpoints.content import ClassMapping, \
GetContent, SystemClassCount
GetContent, SystemClassCount, GetBackendDetails
from openatlas.api.endpoints.special import GetGeometricEntities, \
ExportDatabase, GetSubunits
from openatlas.api.endpoints.display_image import \
Expand Down Expand Up @@ -74,6 +74,10 @@ def add_routes_v03(api: Api) -> None:
GetContent,
'/content/',
endpoint="content")
api.add_resource(
GetBackendDetails,
'/backend_details/',
endpoint="backend_details")
api.add_resource(
ClassMapping,
'/classes/',
Expand Down
76 changes: 76 additions & 0 deletions openatlas/api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,35 @@
}
}
},
"/backend_details/": {
"get": {
"tags": [
"Administrative Endpoints"
],
"description": "Retrieves a list of information of the backend configuration.",
"operationId": "GetBackendDetails",
"parameters": [
{
"$ref": "#/components/parameters/download"
}
],
"responses": {
"200": {
"description": "OpenAtlas backend details",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BackendDetailsModel"
}
}
}
},
"404": {
"description": "Something went wrong. Please consult the error message."
}
}
}
},
"/system_class_count/": {
"get": {
"tags": [
Expand Down Expand Up @@ -2357,6 +2386,53 @@
}
}
},
"BackendDetailsModel": {
"type": "object",
"properties": {
"version": {
"type": "string"
},
"apiVersions": {
"type": "string"
},
"siteName": {
"type": "string"
},
"imageProcessing": {
"type": "object",
"properties": {
"enabled": {
"type": "string"
},
"availableImageSizes": {
"type": "object",
"properties": {
"thumbnail": {
"type": "string"
},
"table": {
"type": "string"
}
}
}
}
},
"IIIF": {
"type": "object",
"properties": {
"enabled": {
"type": "string"
},
"url": {
"type": "string"
},
"version": {
"type": "string"
}
}
}
}
},
"SystemClassCountModel": {
"type": "object",
"properties": {
Expand Down
26 changes: 25 additions & 1 deletion openatlas/views/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ def forbidden(e: Exception) -> tuple[str, int]:
def page_not_found(e: Exception) -> tuple[Any, int]:
if request.path.startswith('/api/'):
return jsonify({
'message': 'Endpoint not found',
'title': 'Endpoint not found',
'message':
'The requestes endpoint does not exist. '
'Please consult the manual or the Swagger documentation: '
f'{request.url_root }swagger',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 404}), 404
Expand Down Expand Up @@ -65,6 +69,7 @@ def access_denied(_e: Exception) -> tuple[Any, int]:
'title': 'Access denied',
'message': 'You do not have access to the API. '
'Please ask the data provider for permission.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 403}), 403

Expand All @@ -74,6 +79,7 @@ def file_not_found(_e: Exception) -> tuple[Any, int]:
return jsonify({
'title': 'File not found',
'message': 'No file was found for the requested ID.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 404}), 404

Expand All @@ -83,6 +89,7 @@ def entity_does_not_exist(_e: Exception) -> tuple[Any, int]:
return jsonify({
'title': 'Entity does not exist',
'message': 'The requested entity does not exist in the database.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 404}), 404

Expand All @@ -94,6 +101,7 @@ def invalid_cidoc_class_code(_e: Exception) -> tuple[Any, int]:
'message':
'The CIDOC class value is invalid, use "all" or '
+ str(list(g.cidoc_classes)),
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -104,6 +112,7 @@ def invalid_limit(_e: Exception) -> tuple[Any, int]:
'title': 'Invalid limit value',
'message':
'Only integers between 1 and 100 are allowed for the limit.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -115,6 +124,7 @@ def invalid_search_syntax(_e: Exception) -> tuple[Any, int]:
'message':
'The search request contains major errors. '
'Please confer the manual.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -126,6 +136,7 @@ def invalid_system_class(_e: Exception) -> tuple[Any, int]:
'message':
'The system_classes value is invalid, use "all" or '
+ str(list(g.classes)),
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -137,6 +148,7 @@ def invalid_view_class(_e: Exception) -> tuple[Any, int]:
'message':
'The view_classes value is invalid, use "all" or '
+ str(list(g.view_class_mapping)),
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -147,6 +159,7 @@ def last_entity_error(_e: Exception) -> tuple[Any, int]:
'title': 'ID is last entity',
'message':
'The requested ID is the last entity, please choose another ID.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -158,6 +171,7 @@ def invalid_logical_operator(_e: Exception) -> tuple[Any, int]:
'message':
'The logical operator is invalid. Please use: '
f'{app.config["LOGICAL_OPERATOR"]}',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -167,6 +181,7 @@ def no_entity_available(_e: Exception) -> tuple[Any, int]:
return jsonify({
'title': 'No entity available',
'message': 'No entity exist for this request.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 404}), 404

Expand All @@ -177,6 +192,7 @@ def no_license(_e: Exception) -> tuple[Any, int]:
'title': 'No license',
'message':
'The requested file has no license and cannot be displayed.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 409}), 409

Expand All @@ -186,6 +202,7 @@ def no_search_string(_e: Exception) -> tuple[Any, int]:
return jsonify({
'title': 'No search values',
'message': 'Search values are empty.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -195,6 +212,7 @@ def not_a_type(_e: Exception) -> tuple[Any, int]:
return jsonify({
'title': 'Entity is not a type',
'message': 'Requested ID either does not exist or is not a Type.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -204,6 +222,7 @@ def not_a_place(_e: Exception) -> tuple[Any, int]:
return jsonify({
'title': 'ID is not a valid place',
'message': 'This endpoint requires a valid ID of a place entity.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -215,6 +234,7 @@ def invalid_operator(_e: Exception) -> tuple[Any, int]:
'message':
'The compare operator is invalid. '
f'Please use: {app.config["COMPARE_OPERATORS"]}',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -227,6 +247,7 @@ def empty_query(_e: Exception) -> tuple[Any, int]:
'The /query endpoint requires at least one of the following '
'parameters: entities, cidoc_classes, view_classes, '
'system_classes.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -238,6 +259,7 @@ def invalid_search_category(_e: Exception) -> tuple[Any, int]:
'message':
'The search category is invalid. Please use: '
f'{app.config["VALID_CATEGORIES"]}',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -248,6 +270,7 @@ def one_id_is_not_a_type(_e: Exception) -> tuple[Any, int]:
'title': 'One entity ID is not a type',
'message':
'One of the requested ID either does not exist or is not a Type.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400

Expand All @@ -258,5 +281,6 @@ def value_not_an_integer(_e: Exception) -> tuple[Any, int]:
'title': 'Invalid search value',
'message':
'The search values need to be an integer for the chosen category.',
'url': request.url,
'timestamp': datetime.datetime.now(),
'status': 400}), 400
5 changes: 3 additions & 2 deletions sphinx/source/technical/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ Example:
.. code-block::
{
"title": "entity does not exist",
"message": "Requested entity does not exist. Try another ID"
"title": "entity does not exist",
"message": "Requested entity does not exist. Try another ID",
"url": "https://demo.openatlas.eu/api/entity/9999/,
"timestamp": "Tue, 19 Jul 2022 13:59:13 GMT",
"status": 404
}
Expand Down
8 changes: 7 additions & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ def test_api(self) -> None:
rv = self.app.get(url_for('api_03.content')).get_json()
assert bool(rv['intro'] == 'This is English')

rv = self.app.get(url_for('api_03.backend_details')).get_json()
assert bool(rv['version'] == app.config['VERSION'])
rv = self.app.get(
url_for('api_03.backend_details', download=True)).get_json()
assert bool(rv['version'] == app.config['VERSION'])

rv = self.app.get(url_for('api_03.system_class_count')).get_json()
assert bool(rv['person'])

Expand Down Expand Up @@ -411,7 +417,7 @@ def test_api(self) -> None:
self.app.get(url_for('api_03.type_by_view_class')),
self.app.get(
url_for('api_03.type_by_view_class', download=True))]:
rv = rv.get_json()['place'][0]['children'][0]
rv = rv.get_json()['place'][2]['children'][0]
assert bool(rv['label'] == 'Boundary Mark')

for rv in [
Expand Down

0 comments on commit c403410

Please sign in to comment.