Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion install.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ that we are using to set up Debian servers for OpenAtlas installations.
### Python 3.13 and Flask 3.1.1
sudo apt install python3 python3-flask python3-psycopg2 python3-flask-babel python3-flask-login
sudo apt install python3-jwt python3-python-flask-jwt-extended python3-flaskext.wtf python3-flask-cors
sudo apt install python3-rdflib python3-shapely python3-flasgger python3-flask-restful python3-pandas
sudo apt install python3-rdflib python3-shapely python3-flask-restful python3-pandas
sudo apt install python3-validators python3-email-validator python3-wand python3-svgwrite
sudo apt install python3-xmltodict python3-markdown exiftran python3-bcrypt python3-dateutil p7zip-full
sudo apt install python3-requests python3-bs4 python3-unidecode python3-lxml python3-unidecode python3-numpy
Expand Down
2 changes: 1 addition & 1 deletion install/container/Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ RUN --mount=type=cache,target=/var/cache/apt \
apt install -y --no-install-recommends python3 python3-bcrypt python3-dateutil python3-psycopg2 python3-levenshtein python3-flask &&\
apt install -y --no-install-recommends python3-flask-babel python3-flask-login python3-flaskext.wtf python3-markdown python3-numpy &&\
apt install -y --no-install-recommends python3-pandas python3-jinja2 python3-flask-cors python3-flask-restful p7zip-full python3-flask-bcrypt &&\
apt install -y --no-install-recommends python3-wand python3-rdflib python3-requests python3-flasgger python3-fiona python3-magic &&\
apt install -y --no-install-recommends python3-wand python3-rdflib python3-requests python3-fiona python3-magic &&\
apt install -y --no-install-recommends apache2 libapache2-mod-wsgi-py3 brotli python3-coverage python3-pytest python3-pytest-cov exiftran &&\
apt install -y --no-install-recommends iipimage-server libvips-tools python3-email-validator python3-svgwrite python3-shapely &&\
apt install -y --no-install-recommends python3-validators python3-jwt python3-python-flask-jwt-extended python3-bs4 python3-unidecode &&\
Expand Down
10 changes: 10 additions & 0 deletions install/upgrade/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ their specific directories and execute:

sudo python3 database_upgrade.py

### 9.4.x to 9.5.0
New NPM packages and security updates are available:

cd openatlas/static
npm install

One Python library is not needed by OpenAtlas anymore and may be removed:

apt purge python3-flasgger

### 9.3.x to 9.4.0
9.4.0.sql is needed but will be taken care of by the database upgrade script.

Expand Down
41 changes: 36 additions & 5 deletions openatlas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
import json
import locale
import os
import shutil
from typing import Optional

from flask import Flask, g, redirect, request, session, url_for
Expand Down Expand Up @@ -72,6 +74,11 @@ def before_request() -> Response | None:
if request.path.startswith('/display'):
return None # Avoid overheads for file display

if request.path.startswith('/swagger') or \
request.path.startswith('/openapi.json'):
write_openapi_instance()
return None # Avoid overheads for swagger

session['language'] = get_locale()
g.admins_available = admins_available()
if not g.admins_available \
Expand All @@ -98,8 +105,8 @@ def before_request() -> Response | None:
app.config['UPLOAD_PATH'],
app.config['TMP_PATH']]
g.arche_uri_rules = None
setup_files()
setup_api()
setup_files()
return None


Expand Down Expand Up @@ -128,10 +135,7 @@ def setup_files() -> None:


def setup_api() -> None:
from openatlas.api.resources.openapi_util import write_openapi_instance
if request.path.startswith('/swagger'):
write_openapi_instance()
elif request.path.startswith('/api/'):
if request.path.startswith('/api/'):
ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
if not current_user.is_authenticated \
and not g.settings['api_public'] \
Expand Down Expand Up @@ -184,3 +188,30 @@ def check_incoming_tokens(
or token_['valid_until'] < datetime.datetime.now():
return True
return False


def write_openapi_instance() -> None:
openapi = app.config['OPENAPI_FILE']
openapi_instance = app.config['OPENAPI_INSTANCE_FILE']
if not openapi_instance.exists():
shutil.copy(openapi, openapi_instance)
with openapi_instance.open(mode='r+') as i, openapi.open(mode='r') as f:
original = json.load(f)
instance = json.load(i)
if original['info']['version'] != instance['info']['version']:
shutil.copy(openapi, openapi_instance)
server = {
'url': request.host_url + 'api/{basePath}',
'description': f'{g.settings['site_name']} Server',
'variables': {'basePath': {'default': '0.4', 'enum': ['0.4']}}}
modified = False
if len(instance['servers']) == 2:
instance['servers'].insert(0, server)
modified = True
elif instance['servers'][0]['description'] != server['description']:
instance['servers'][0] = server
modified = True
if modified:
i.seek(0)
json.dump(instance, i, indent=4)
i.truncate()
34 changes: 14 additions & 20 deletions openatlas/api/api.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,13 @@
from flasgger import Swagger
from flask import Blueprint
from flask import Blueprint, render_template, send_file
from flask_cors import CORS
from flask_restful import Api

from openatlas import app
from openatlas.api.routes import routes

app.config['SWAGGER'] = {
'openapi': '3.0.2',
'uiversion': 3,
"swagger_version": "2.0",
"specs": [{
"endpoint": 'openapi_04',
"license": {
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"},
"route": '/openapi.json',
"rule_filter": lambda rule: rule.endpoint.startswith('api_04')}],
"specs_route": "/swagger/"}

app.config['PROPAGATE_EXCEPTIONS'] = True
CORS(app, resources={r"/api/*": {"origins": app.config['CORS_ALLOWANCE']}})

openapi_file = app.config['OPENAPI_FILE']
if app.config['OPENAPI_INSTANCE_FILE'].exists():
openapi_file = app.config['OPENAPI_INSTANCE_FILE']
Swagger(app, parse=False, template_file=str(openapi_file))

blueprint = Blueprint('api', __name__, url_prefix='/api')
api = Api(blueprint)
for route in routes:
Expand All @@ -38,3 +19,16 @@
for route in routes:
api_04.add_resource(route[0], route[1], endpoint=route[2])
app.register_blueprint(blueprint_04)


@app.route('/openapi.json')
def get_openapi_json():
openapi_file = app.config['OPENAPI_FILE']
if app.config['OPENAPI_INSTANCE_FILE'].exists():
openapi_file = app.config['OPENAPI_INSTANCE_FILE']
return send_file(openapi_file, mimetype='application/json')


@app.route('/swagger')
def get_swagger_ui():
return render_template("swagger.html")
33 changes: 0 additions & 33 deletions openatlas/api/resources/openapi_util.py

This file was deleted.

22 changes: 19 additions & 3 deletions openatlas/static/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions openatlas/static/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"leaflet.markercluster": "^1.5.3",
"mirador": "^4.0.0",
"save-svg-as-png": "^1.4.17",
"swagger-ui-dist": "^5.32.13",
"tinymce": "^8.3.2",
"wellknown": "^0.5.0"
}
Expand Down
23 changes: 23 additions & 0 deletions openatlas/templates/swagger.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>OpenAtlas API V0.4 - Swagger UI</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet"
href="{{ url_for('static', filename='node_modules/swagger-ui-dist/swagger-ui.css', v=config.VERSION) }}"/>
</head>
<body>
<div id="swagger-ui"></div>
<script
src="{{ url_for('static', filename='node_modules/swagger-ui-dist/swagger-ui-bundle.js', v=config.VERSION) }}"></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: '/openapi.json',
dom_id: '#swagger-ui',
});
};
</script>
</body>
</html>
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ dependencies = [
"bcrypt~=4.2.0",
"beautifulsoup4~=4.13.4",
"email-validator==2.2.0",
"flasgger==0.9.7.1",
"flask==3.1.1",
"flask-babel==4.0.0",
"flask-cors==6.0.1",
Expand Down
51 changes: 51 additions & 0 deletions tests/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import unittest
from pathlib import Path
from types import SimpleNamespace
from typing import Any, Optional

import psycopg2
from flask import url_for

from openatlas import app
from openatlas.api.resources.api_entity import ApiEntity
from openatlas.models.entity import Entity, insert as entity_insert


Expand Down Expand Up @@ -79,6 +81,55 @@ def get_class_mapping(data: dict[str, Any], locale: str) -> bool:
and data['results'][0]['icon']
and data['results'][0]['label'])

@staticmethod
def get_api_entities() -> Any:
entities = SimpleNamespace()
with app.test_request_context():
app.preprocess_request()
for entity in ApiEntity.get_by_cidoc_classes(['all']):
match entity.name:
case 'Location of Shire':
entities.location = entity
case 'Shire':
entities.place = entity
case 'Boundary Mark':
entities.boundary_mark = entity
case 'Travel to Mordor':
entities.event = entity
case 'Exchange of the one ring':
entities.event2 = entity
case 'Economical':
entities.relation_sub = entity
case 'Austria':
entities.unit_node = entity
case 'Frodo':
entities.actor = entity
case 'Sam':
entities.actor2 = entity
case 'Home of Baggins':
entities.feature = entity
case 'The One Ring':
entities.artifact = entity
case 'Sûza':
entities.alias = entity
case 'Height':
entities.height = entity
case 'Weight':
entities.weight = entity
case 'Change of Property':
entities.change_of_property = entity
case 'File not public':
entities.file_not_public = entity
case 'File without license':
entities.file_without_licences = entity
case 'File without file':
entities.file_without_file = entity
case 'OpenAtlas logo':
entities.file = entity
case 'Public domain':
entities.open_license = entity
return entities


class ImportTestCase(TestBaseCase):
def setUp(self) -> None:
Expand Down
Loading
Loading