From 7888fdf37ed76e1637f99d4f7f1fcbb5798a3f66 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 1 Sep 2025 14:49:51 +0200 Subject: [PATCH 01/41] feat(database): Add persistent data management using sqlite & sqlalchemy --- requirements.in | 1 + requirements.txt | 8 +++++++- src/opengeodeweb_back/database.py | 9 +++++++++ src/opengeodeweb_back/models.py | 23 +++++++++++++++++++++++ src/opengeodeweb_back/utils_functions.py | 21 +++++++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/opengeodeweb_back/database.py create mode 100644 src/opengeodeweb_back/models.py diff --git a/requirements.in b/requirements.in index 10ebe922..576cf184 100644 --- a/requirements.in +++ b/requirements.in @@ -8,3 +8,4 @@ fastjsonschema==2.16.2 Flask[async]==3.0.3 Flask-Cors==6.0.1 werkzeug==3.0.3 +sqlalchemy==2.0.43 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1e0a5aa2..bac4ea79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --pre requirements.in +# pip-compile requirements.in # asgiref==3.9.1 # via flask @@ -23,6 +23,8 @@ geode-common==33.9.0 # via geode-viewables geode-viewables==3.2.0 # via -r requirements.in +greenlet==3.2.4 + # via sqlalchemy itsdangerous==2.2.0 # via flask jinja2==3.1.6 @@ -54,6 +56,10 @@ opengeode-io==7.3.2 # -r requirements.in # geode-viewables # opengeode-geosciencesio +sqlalchemy==2.0.43 + # via -r requirements.in +typing-extensions==4.15.0 + # via sqlalchemy werkzeug==3.0.3 # via # -r requirements.in diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py new file mode 100644 index 00000000..b3e193a9 --- /dev/null +++ b/src/opengeodeweb_back/database.py @@ -0,0 +1,9 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +def get_engine(db_path: str): + return create_engine(f"sqlite:///{db_path}", echo=False) + +def get_session(engine): + Session = sessionmaker(bind=engine) + return Session() diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/models.py new file mode 100644 index 00000000..210f6b58 --- /dev/null +++ b/src/opengeodeweb_back/models.py @@ -0,0 +1,23 @@ +import uuid +from datetime import datetime +from sqlalchemy import Column, String, DateTime, JSON +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + + +def generate_uuid(): + return str(uuid.uuid4()) + + +class Data(Base): + __tablename__ = "datas" + + id = Column(String, primary_key=True, default=generate_uuid) + name = Column(String, nullable=False) + native_file_name = Column(String, nullable=False) + viewable_file_name = Column(String, nullable=False) + geode_object = Column(String, nullable=False) + binary_light_viewable = Column(String, nullable=True) + input_files = Column(JSON, nullable=True) + created_at = Column(DateTime, default=datetime.utcnow) diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index fc1aa143..4e10dc68 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -14,6 +14,8 @@ # Local application imports from . import geode_functions +from .models import Data, Base +from .database import get_engine, get_session def increment_request_counter(current_app): @@ -171,6 +173,25 @@ def save_all_viewables_and_return_info( with open(saved_light_viewable_file_path, "rb") as f: binary_light_viewable = f.read() + project_root_path = os.path.dirname(data_path) + engine = get_engine(os.path.join(project_root_path, "db.sqlite3")) + + Base.metadata.create_all(engine) + session = get_session(engine) + + data_entry = Data( + id=generated_id, + name=data.name(), + native_file_name=os.path.basename(saved_native_file_path[0]), + viewable_file_name=os.path.basename(saved_viewable_file_path), + geode_object=geode_object, + binary_light_viewable=binary_light_viewable.decode("utf-8"), + input_files=additional_files or [], + ) + session.add(data_entry) + session.commit() + session.close() + return { "name": data.name(), "native_file_name": os.path.basename(saved_native_file_path[0]), From cfcfdd44ae1db787a5338b69100cdc288f9a4b98 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 1 Sep 2025 14:56:50 +0200 Subject: [PATCH 02/41] default datetime changed for a non decprecated method --- src/opengeodeweb_back/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/models.py index 210f6b58..99347aa1 100644 --- a/src/opengeodeweb_back/models.py +++ b/src/opengeodeweb_back/models.py @@ -20,4 +20,4 @@ class Data(Base): geode_object = Column(String, nullable=False) binary_light_viewable = Column(String, nullable=True) input_files = Column(JSON, nullable=True) - created_at = Column(DateTime, default=datetime.utcnow) + created_at = Column(DateTime, default=datetime.now) From 2d24b77bc3ae40f2b4b660deaae3c75005c425d0 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:57:58 +0000 Subject: [PATCH 03/41] Apply prepare changes --- commitlint.config.js | 16 ++++++++++++++++ requirements.txt | 2 +- src/opengeodeweb_back/database.py | 2 ++ src/opengeodeweb_back/models.py | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 commitlint.config.js diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..3a29484e --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,16 @@ +export default { + extends: ["@commitlint/config-angular"], + rules: { + "scope-empty": [2, "never"], + "subject-empty": [2, "never"], + "subject-max-length": [0], + "body-leading-blank": [0], + "footer-leading-blank": [0], + "header-max-length": [0], + "scope-case": [0], + "subject-case": [0], + "subject-full-stop": [0], + "type-case": [0], + "type-empty": [0], + }, +} diff --git a/requirements.txt b/requirements.txt index bac4ea79..80e22ea2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile requirements.in +# pip-compile --pre requirements.in # asgiref==3.9.1 # via flask diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index b3e193a9..39738335 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -1,9 +1,11 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker + def get_engine(db_path: str): return create_engine(f"sqlite:///{db_path}", echo=False) + def get_session(engine): Session = sessionmaker(bind=engine) return Session() diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/models.py index 99347aa1..60b3712d 100644 --- a/src/opengeodeweb_back/models.py +++ b/src/opengeodeweb_back/models.py @@ -18,6 +18,6 @@ class Data(Base): native_file_name = Column(String, nullable=False) viewable_file_name = Column(String, nullable=False) geode_object = Column(String, nullable=False) - binary_light_viewable = Column(String, nullable=True) + binary_light_viewable = Column(String, nullable=True) input_files = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.now) From 73ff779f7f6354e300b87a741b4c87bf70509c40 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 1 Sep 2025 15:21:42 +0200 Subject: [PATCH 04/41] minor renames --- src/opengeodeweb_back/models.py | 2 +- src/opengeodeweb_back/utils_functions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/models.py index 99347aa1..94848b01 100644 --- a/src/opengeodeweb_back/models.py +++ b/src/opengeodeweb_back/models.py @@ -17,7 +17,7 @@ class Data(Base): name = Column(String, nullable=False) native_file_name = Column(String, nullable=False) viewable_file_name = Column(String, nullable=False) - geode_object = Column(String, nullable=False) binary_light_viewable = Column(String, nullable=True) + geode_object = Column(String, nullable=False) input_files = Column(JSON, nullable=True) created_at = Column(DateTime, default=datetime.now) diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 4e10dc68..54db7a77 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -184,8 +184,8 @@ def save_all_viewables_and_return_info( name=data.name(), native_file_name=os.path.basename(saved_native_file_path[0]), viewable_file_name=os.path.basename(saved_viewable_file_path), + light_viewable=os.path.basename(saved_light_viewable_file_path), geode_object=geode_object, - binary_light_viewable=binary_light_viewable.decode("utf-8"), input_files=additional_files or [], ) session.add(data_entry) From 6163517f8510d90b4ba48b99b28d7e35f230a52a Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:25:11 +0000 Subject: [PATCH 05/41] Apply prepare changes --- commitlint.config.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 commitlint.config.js diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..3a29484e --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,16 @@ +export default { + extends: ["@commitlint/config-angular"], + rules: { + "scope-empty": [2, "never"], + "subject-empty": [2, "never"], + "subject-max-length": [0], + "body-leading-blank": [0], + "footer-leading-blank": [0], + "header-max-length": [0], + "scope-case": [0], + "subject-case": [0], + "subject-full-stop": [0], + "type-case": [0], + "type-empty": [0], + }, +} From a53d0004adf14de7d06232ac774ec86b8398a09f Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Mon, 1 Sep 2025 22:21:20 +0200 Subject: [PATCH 06/41] test --- requirements.in | 2 +- src/opengeodeweb_back/database.py | 16 +++++++-------- src/opengeodeweb_back/models.py | 26 ++++++++++-------------- src/opengeodeweb_back/utils_functions.py | 17 +++++----------- 4 files changed, 24 insertions(+), 37 deletions(-) diff --git a/requirements.in b/requirements.in index 576cf184..5ad963ec 100644 --- a/requirements.in +++ b/requirements.in @@ -8,4 +8,4 @@ fastjsonschema==2.16.2 Flask[async]==3.0.3 Flask-Cors==6.0.1 werkzeug==3.0.3 -sqlalchemy==2.0.43 \ No newline at end of file +Flask-SQLAlchemy==3.1.1 \ No newline at end of file diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index 39738335..de69e33e 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -1,11 +1,9 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker +from flask_sqlalchemy import SQLAlchemy +db = SQLAlchemy() -def get_engine(db_path: str): - return create_engine(f"sqlite:///{db_path}", echo=False) - - -def get_session(engine): - Session = sessionmaker(bind=engine) - return Session() +def init_db(app): + db.init_app(app) + with app.app_context(): + db.create_all() + return db \ No newline at end of file diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/models.py index 1fd15783..fa6b135a 100644 --- a/src/opengeodeweb_back/models.py +++ b/src/opengeodeweb_back/models.py @@ -1,23 +1,19 @@ import uuid from datetime import datetime -from sqlalchemy import Column, String, DateTime, JSON -from sqlalchemy.orm import declarative_base - -Base = declarative_base() - +from sqlalchemy import String, DateTime, JSON +from .database import db def generate_uuid(): return str(uuid.uuid4()) - -class Data(Base): +class Data(db.Model): __tablename__ = "datas" - id = Column(String, primary_key=True, default=generate_uuid) - name = Column(String, nullable=False) - native_file_name = Column(String, nullable=False) - viewable_file_name = Column(String, nullable=False) - binary_light_viewable = Column(String, nullable=True) - geode_object = Column(String, nullable=False) - input_files = Column(JSON, nullable=True) - created_at = Column(DateTime, default=datetime.now) + id = db.Column(String, primary_key=True, default=generate_uuid) + name = db.Column(String, nullable=False) + native_file_name = db.Column(String, nullable=False) + viewable_file_name = db.Column(String, nullable=False) + light_viewable = db.Column(String, nullable=True) # Renommé pour correspondre au code + geode_object = db.Column(String, nullable=False) + input_files = db.Column(JSON, nullable=True) + created_at = db.Column(DateTime, default=datetime.now) diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 54db7a77..a4fc2730 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -14,9 +14,8 @@ # Local application imports from . import geode_functions -from .models import Data, Base -from .database import get_engine, get_session - +from .models import Data +from .database import db def increment_request_counter(current_app): if "REQUEST_COUNTER" in current_app.config: @@ -173,12 +172,6 @@ def save_all_viewables_and_return_info( with open(saved_light_viewable_file_path, "rb") as f: binary_light_viewable = f.read() - project_root_path = os.path.dirname(data_path) - engine = get_engine(os.path.join(project_root_path, "db.sqlite3")) - - Base.metadata.create_all(engine) - session = get_session(engine) - data_entry = Data( id=generated_id, name=data.name(), @@ -188,9 +181,9 @@ def save_all_viewables_and_return_info( geode_object=geode_object, input_files=additional_files or [], ) - session.add(data_entry) - session.commit() - session.close() + + db.session.add(data_entry) + db.session.commit() return { "name": data.name(), From 297479180a23768d87460f70388b50ed4865014d Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Mon, 1 Sep 2025 20:21:52 +0000 Subject: [PATCH 07/41] Apply prepare changes --- requirements.txt | 5 ++++- src/opengeodeweb_back/database.py | 3 ++- src/opengeodeweb_back/models.py | 6 +++++- src/opengeodeweb_back/utils_functions.py | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 80e22ea2..f6b829e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,8 +17,11 @@ flask[async]==3.0.3 # -r requirements.in # flask # flask-cors + # flask-sqlalchemy flask-cors==6.0.1 # via -r requirements.in +flask-sqlalchemy==3.1.1 + # via -r requirements.in geode-common==33.9.0 # via geode-viewables geode-viewables==3.2.0 @@ -57,7 +60,7 @@ opengeode-io==7.3.2 # geode-viewables # opengeode-geosciencesio sqlalchemy==2.0.43 - # via -r requirements.in + # via flask-sqlalchemy typing-extensions==4.15.0 # via sqlalchemy werkzeug==3.0.3 diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index de69e33e..c0cb7e8f 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -2,8 +2,9 @@ db = SQLAlchemy() + def init_db(app): db.init_app(app) with app.app_context(): db.create_all() - return db \ No newline at end of file + return db diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/models.py index fa6b135a..94df4f06 100644 --- a/src/opengeodeweb_back/models.py +++ b/src/opengeodeweb_back/models.py @@ -3,9 +3,11 @@ from sqlalchemy import String, DateTime, JSON from .database import db + def generate_uuid(): return str(uuid.uuid4()) + class Data(db.Model): __tablename__ = "datas" @@ -13,7 +15,9 @@ class Data(db.Model): name = db.Column(String, nullable=False) native_file_name = db.Column(String, nullable=False) viewable_file_name = db.Column(String, nullable=False) - light_viewable = db.Column(String, nullable=True) # Renommé pour correspondre au code + light_viewable = db.Column( + String, nullable=True + ) # Renommé pour correspondre au code geode_object = db.Column(String, nullable=False) input_files = db.Column(JSON, nullable=True) created_at = db.Column(DateTime, default=datetime.now) diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index a4fc2730..b5b07a7a 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -17,6 +17,7 @@ from .models import Data from .database import db + def increment_request_counter(current_app): if "REQUEST_COUNTER" in current_app.config: REQUEST_COUNTER = int(current_app.config.get("REQUEST_COUNTER")) @@ -181,7 +182,7 @@ def save_all_viewables_and_return_info( geode_object=geode_object, input_files=additional_files or [], ) - + db.session.add(data_entry) db.session.commit() From 652149dbdd19653ef67ea1276d21309db67f71dc Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 2 Sep 2025 16:48:50 +0200 Subject: [PATCH 08/41] Using Flask-SQLAlchemy instead of SQLAlchemy for the database --- .gitignore | 3 ++- app.py | 2 ++ src/opengeodeweb_back/app_config.py | 3 +++ src/opengeodeweb_back/database.py | 6 ++++++ src/opengeodeweb_back/models.py | 4 +--- tests/conftest.py | 14 +++++++++++--- tests/test_utils_functions.py | 29 ++++++++++++++++++++--------- 7 files changed, 45 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 1a771f5b..1723217e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ __pycache__ .vscode uploads node_modules -schemas.json \ No newline at end of file +schemas.json +*.db \ No newline at end of file diff --git a/app.py b/app.py index 23dd0e1f..f7ad4d32 100644 --- a/app.py +++ b/app.py @@ -10,6 +10,7 @@ from src.opengeodeweb_back.routes.models import blueprint_models from src.opengeodeweb_back.utils_functions import handle_exception from src.opengeodeweb_back import app_config +from src.opengeodeweb_back.database import init_db """ Global config """ @@ -58,4 +59,5 @@ def return_error(): # ''' Main ''' if __name__ == "__main__": print(f"Python is running in {FLASK_DEBUG} mode") + init_db(app) app.run(debug=FLASK_DEBUG, host=DEFAULT_HOST, port=PORT, ssl_context=SSL) diff --git a/src/opengeodeweb_back/app_config.py b/src/opengeodeweb_back/app_config.py index a4e87fc2..dea28897 100644 --- a/src/opengeodeweb_back/app_config.py +++ b/src/opengeodeweb_back/app_config.py @@ -15,6 +15,7 @@ class Config(object): REQUEST_COUNTER = 0 LAST_REQUEST_TIME = time.time() LAST_PING_TIME = time.time() + SQLALCHEMY_TRACK_MODIFICATIONS = False class ProdConfig(Config): @@ -23,6 +24,7 @@ class ProdConfig(Config): MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" DATA_FOLDER_PATH = "/data/" + SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" class DevConfig(Config): @@ -31,3 +33,4 @@ class DevConfig(Config): MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" DATA_FOLDER_PATH = "./data/" + SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index c0cb7e8f..b5c02197 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -2,9 +2,15 @@ db = SQLAlchemy() +initialized = False def init_db(app): + global initialized + if initialized: + return db + print('DB', app.config.get("SQLALCHEMY_DATABASE_URI")) db.init_app(app) with app.app_context(): db.create_all() + initialized = True return db diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/models.py index 94df4f06..538ec158 100644 --- a/src/opengeodeweb_back/models.py +++ b/src/opengeodeweb_back/models.py @@ -15,9 +15,7 @@ class Data(db.Model): name = db.Column(String, nullable=False) native_file_name = db.Column(String, nullable=False) viewable_file_name = db.Column(String, nullable=False) - light_viewable = db.Column( - String, nullable=True - ) # Renommé pour correspondre au code + light_viewable = db.Column(String, nullable=True) geode_object = db.Column(String, nullable=False) input_files = db.Column(JSON, nullable=True) created_at = db.Column(DateTime, default=datetime.now) diff --git a/tests/conftest.py b/tests/conftest.py index f9891df3..d72950da 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,12 @@ # Standard library imports import time import shutil - -# Third party imports +import os import pytest -# Local application imports + from app import app +from src.opengeodeweb_back.database import init_db TEST_ID = "1" @@ -25,6 +25,14 @@ def client(): app.config["UPLOAD_FOLDER"] = "./tests/data/" app.config["REQUEST_COUNTER"] = 0 app.config["LAST_REQUEST_TIME"] = time.time() + BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + db_path = os.path.join(BASE_DIR, "data", "project.db") + app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" + + print("Current working directory:", os.getcwd()) + print("Directory contents:", os.listdir(".")) + + init_db(app) client = app.test_client() client.headers = {"Content-type": "application/json", "Accept": "application/json"} yield client diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 52282717..f86e2930 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -7,6 +7,8 @@ import shutil # Local application imports +from src.opengeodeweb_back.database import db +from src.opengeodeweb_back.models import Data from src.opengeodeweb_back import geode_functions, utils_functions @@ -97,15 +99,24 @@ def test_save_all_viewables_and_return_info(client): geode_object, data, generated_id, data_path, additional_files ) - assert isinstance(result, dict) - assert result["name"] == data.name() - assert result["native_file_name"].startswith("native.") - assert result["viewable_file_name"].endswith(".vtm") - assert re.match(r"[0-9a-f]{32}", result["id"]) - assert isinstance(result["object_type"], str) - assert isinstance(result["binary_light_viewable"], str) - assert result["geode_object"] == geode_object - assert result["input_files"] == additional_files + assert isinstance(result, dict) + assert result["name"] == data.name() + assert result["native_file_name"].startswith("native.") + assert result["viewable_file_name"].endswith(".vtm") + assert re.match(r"[0-9a-f]{32}", result["id"]) + assert isinstance(result["object_type"], str) + assert isinstance(result["binary_light_viewable"], str) + assert result["geode_object"] == geode_object + assert result["input_files"] == additional_files + + db_entry = Data.query.get(generated_id) + assert db_entry is not None + assert db_entry.name == data.name() + assert db_entry.native_file_name == os.path.basename(result["native_file_name"]) + assert db_entry.viewable_file_name == os.path.basename(result["viewable_file_name"]) + assert db_entry.light_viewable == os.path.basename(db_entry.light_viewable) + assert db_entry.geode_object == geode_object + assert db_entry.input_files == additional_files def test_generate_native_viewable_and_light_viewable_from_object(client): From dad100a2e8dc8c31df03d8a86569e8f2729ddb5b Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:49:13 +0000 Subject: [PATCH 09/41] Apply prepare changes --- src/opengeodeweb_back/app_config.py | 8 ++++++-- src/opengeodeweb_back/database.py | 5 +++-- tests/conftest.py | 2 +- tests/test_utils_functions.py | 4 +++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/opengeodeweb_back/app_config.py b/src/opengeodeweb_back/app_config.py index dea28897..f04b579e 100644 --- a/src/opengeodeweb_back/app_config.py +++ b/src/opengeodeweb_back/app_config.py @@ -24,7 +24,9 @@ class ProdConfig(Config): MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" DATA_FOLDER_PATH = "/data/" - SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" + SQLALCHEMY_DATABASE_URI = ( + f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" + ) class DevConfig(Config): @@ -33,4 +35,6 @@ class DevConfig(Config): MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" DATA_FOLDER_PATH = "./data/" - SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" + SQLALCHEMY_DATABASE_URI = ( + f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" + ) diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index b5c02197..e084e0a5 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -4,11 +4,12 @@ initialized = False + def init_db(app): global initialized if initialized: - return db - print('DB', app.config.get("SQLALCHEMY_DATABASE_URI")) + return db + print("DB", app.config.get("SQLALCHEMY_DATABASE_URI")) db.init_app(app) with app.app_context(): db.create_all() diff --git a/tests/conftest.py b/tests/conftest.py index d72950da..b0f1fc18 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,7 +26,7 @@ def client(): app.config["REQUEST_COUNTER"] = 0 app.config["LAST_REQUEST_TIME"] = time.time() BASE_DIR = os.path.abspath(os.path.dirname(__file__)) - db_path = os.path.join(BASE_DIR, "data", "project.db") + db_path = os.path.join(BASE_DIR, "data", "project.db") app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" print("Current working directory:", os.getcwd()) diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index f86e2930..8fb03e7e 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -113,7 +113,9 @@ def test_save_all_viewables_and_return_info(client): assert db_entry is not None assert db_entry.name == data.name() assert db_entry.native_file_name == os.path.basename(result["native_file_name"]) - assert db_entry.viewable_file_name == os.path.basename(result["viewable_file_name"]) + assert db_entry.viewable_file_name == os.path.basename( + result["viewable_file_name"] + ) assert db_entry.light_viewable == os.path.basename(db_entry.light_viewable) assert db_entry.geode_object == geode_object assert db_entry.input_files == additional_files From 1905756599888f1b23fc18fd682f0acc36ada944 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 2 Sep 2025 16:52:36 +0200 Subject: [PATCH 10/41] clean this ? --- commitlint.config.js | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 commitlint.config.js diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 3a29484e..00000000 --- a/commitlint.config.js +++ /dev/null @@ -1,16 +0,0 @@ -export default { - extends: ["@commitlint/config-angular"], - rules: { - "scope-empty": [2, "never"], - "subject-empty": [2, "never"], - "subject-max-length": [0], - "body-leading-blank": [0], - "footer-leading-blank": [0], - "header-max-length": [0], - "scope-case": [0], - "subject-case": [0], - "subject-full-stop": [0], - "type-case": [0], - "type-empty": [0], - }, -} From 1d4b23145bdc11a98232a1a8a7518442aab4dbe1 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:54:34 +0000 Subject: [PATCH 11/41] Apply prepare changes --- commitlint.config.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 commitlint.config.js diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..3a29484e --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,16 @@ +export default { + extends: ["@commitlint/config-angular"], + rules: { + "scope-empty": [2, "never"], + "subject-empty": [2, "never"], + "subject-max-length": [0], + "body-leading-blank": [0], + "footer-leading-blank": [0], + "header-max-length": [0], + "scope-case": [0], + "subject-case": [0], + "subject-full-stop": [0], + "type-case": [0], + "type-empty": [0], + }, +} From 62bf4bbef8158ae8bd0cb2541a43b26d1e39ecac Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Tue, 2 Sep 2025 17:12:40 +0200 Subject: [PATCH 12/41] some comments --- .gitignore | 2 +- tests/conftest.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1723217e..98db0f41 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ __pycache__ uploads node_modules schemas.json -*.db \ No newline at end of file +*.db diff --git a/tests/conftest.py b/tests/conftest.py index b0f1fc18..f3f0cb8e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,12 @@ # Standard library imports import time import shutil + +# Third party imports import os import pytest - +# Local application imports from app import app from src.opengeodeweb_back.database import init_db From bf9dfc04848e3fa5a63f98e379c8906f960d2a6c Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 3 Sep 2025 09:06:49 +0200 Subject: [PATCH 13/41] delete commitlint --- commitlint.config.js | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 commitlint.config.js diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 3a29484e..00000000 --- a/commitlint.config.js +++ /dev/null @@ -1,16 +0,0 @@ -export default { - extends: ["@commitlint/config-angular"], - rules: { - "scope-empty": [2, "never"], - "subject-empty": [2, "never"], - "subject-max-length": [0], - "body-leading-blank": [0], - "footer-leading-blank": [0], - "header-max-length": [0], - "scope-case": [0], - "subject-case": [0], - "subject-full-stop": [0], - "type-case": [0], - "type-empty": [0], - }, -} From ac4782f29a5da8fff03bcae414c9e4b1de661848 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 3 Sep 2025 07:07:22 +0000 Subject: [PATCH 14/41] Apply prepare changes --- commitlint.config.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 commitlint.config.js diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 00000000..3a29484e --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,16 @@ +export default { + extends: ["@commitlint/config-angular"], + rules: { + "scope-empty": [2, "never"], + "subject-empty": [2, "never"], + "subject-max-length": [0], + "body-leading-blank": [0], + "footer-leading-blank": [0], + "header-max-length": [0], + "scope-case": [0], + "subject-case": [0], + "subject-full-stop": [0], + "type-case": [0], + "type-empty": [0], + }, +} From 67772631b4d97d93800f8d300c264e7c51ac25e3 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 3 Sep 2025 09:56:23 +0200 Subject: [PATCH 15/41] tests added --- tests/test_routes.py | 12 ++++++++++++ tests/test_utils_functions.py | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/tests/test_routes.py b/tests/test_routes.py index 83a0f375..3b9d5cf6 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -321,3 +321,15 @@ def test_create_point(client): # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_database_uri_path(client): + app = client.application + with app.app_context(): + base_dir = os.path.abspath(os.path.dirname(__file__)) + expected_db_path = os.path.join(base_dir, "data", "project.db") + expected_uri = f"sqlite:///{expected_db_path}" + + assert app.config["SQLALCHEMY_DATABASE_URI"] == expected_uri + + assert os.path.exists(expected_db_path) diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 8fb03e7e..9d779ff7 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -90,6 +90,13 @@ def test_create_unique_data_folder(client): def test_save_all_viewables_and_return_info(client): app = client.application with app.app_context(): + base_dir = os.path.abspath(os.path.dirname(__file__)) + expected_db_path = os.path.join(base_dir, "data", "project.db") + expected_uri = f"sqlite:///{expected_db_path}" + + assert app.config["SQLALCHEMY_DATABASE_URI"] == expected_uri + assert os.path.exists(expected_db_path) + geode_object = "BRep" data = geode_functions.load(geode_object, "./tests/data/test.og_brep") generated_id, data_path = utils_functions.create_unique_data_folder() From 2011ac560b8e3c55bec9b6106119f3923d6d0d49 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 3 Sep 2025 13:48:12 +0200 Subject: [PATCH 16/41] Converted SQLALCHEMY_DATABASE_URI to use absolute paths Fixed database behavior to prevent table recreation on every test run. Renamed database model file to data.py for clarity. Added input_file column to the Data table. Standardized ID generation to rely on client-generated IDs only. General cleanup and code readability improvements. --- app.py | 4 ++-- src/opengeodeweb_back/{models.py => data.py} | 15 ++++----------- src/opengeodeweb_back/database.py | 10 +--------- src/opengeodeweb_back/utils_functions.py | 7 ++++--- tests/conftest.py | 16 ++++++++-------- tests/test_utils_functions.py | 15 +++++++-------- 6 files changed, 26 insertions(+), 41 deletions(-) rename src/opengeodeweb_back/{models.py => data.py} (52%) diff --git a/app.py b/app.py index f7ad4d32..bfa77814 100644 --- a/app.py +++ b/app.py @@ -10,7 +10,7 @@ from src.opengeodeweb_back.routes.models import blueprint_models from src.opengeodeweb_back.utils_functions import handle_exception from src.opengeodeweb_back import app_config -from src.opengeodeweb_back.database import init_db +from src.opengeodeweb_back.database import initialize_database """ Global config """ @@ -58,6 +58,6 @@ def return_error(): # ''' Main ''' if __name__ == "__main__": + initialize_database(app) print(f"Python is running in {FLASK_DEBUG} mode") - init_db(app) app.run(debug=FLASK_DEBUG, host=DEFAULT_HOST, port=PORT, ssl_context=SSL) diff --git a/src/opengeodeweb_back/models.py b/src/opengeodeweb_back/data.py similarity index 52% rename from src/opengeodeweb_back/models.py rename to src/opengeodeweb_back/data.py index 538ec158..63dfed35 100644 --- a/src/opengeodeweb_back/models.py +++ b/src/opengeodeweb_back/data.py @@ -1,21 +1,14 @@ -import uuid -from datetime import datetime -from sqlalchemy import String, DateTime, JSON +from sqlalchemy import String, JSON from .database import db - -def generate_uuid(): - return str(uuid.uuid4()) - - class Data(db.Model): __tablename__ = "datas" - id = db.Column(String, primary_key=True, default=generate_uuid) + id = db.Column(String, primary_key=True) name = db.Column(String, nullable=False) native_file_name = db.Column(String, nullable=False) viewable_file_name = db.Column(String, nullable=False) light_viewable = db.Column(String, nullable=True) geode_object = db.Column(String, nullable=False) - input_files = db.Column(JSON, nullable=True) - created_at = db.Column(DateTime, default=datetime.now) + input_file = db.Column(JSON, nullable=True) + additional_files = db.Column(JSON, nullable=True) diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index e084e0a5..17cb3d4f 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -2,16 +2,8 @@ db = SQLAlchemy() -initialized = False - - -def init_db(app): - global initialized - if initialized: - return db - print("DB", app.config.get("SQLALCHEMY_DATABASE_URI")) +def initialize_database(app): db.init_app(app) with app.app_context(): db.create_all() - initialized = True return db diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index b5b07a7a..cfe86a0d 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -14,7 +14,7 @@ # Local application imports from . import geode_functions -from .models import Data +from .data import Data from .database import db @@ -180,7 +180,8 @@ def save_all_viewables_and_return_info( viewable_file_name=os.path.basename(saved_viewable_file_path), light_viewable=os.path.basename(saved_light_viewable_file_path), geode_object=geode_object, - input_files=additional_files or [], + input_file=additional_files or [], + additional_files=additional_files or [], ) db.session.add(data_entry) @@ -190,7 +191,7 @@ def save_all_viewables_and_return_info( "name": data.name(), "native_file_name": os.path.basename(saved_native_file_path[0]), "viewable_file_name": os.path.basename(saved_viewable_file_path), - "id": generated_id, + "id": data_entry.id, "object_type": geode_functions.get_object_type(geode_object), "binary_light_viewable": binary_light_viewable.decode("utf-8"), "geode_object": geode_object, diff --git a/tests/conftest.py b/tests/conftest.py index f3f0cb8e..22203f27 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ # Local application imports from app import app -from src.opengeodeweb_back.database import init_db +from src.opengeodeweb_back.database import initialize_database TEST_ID = "1" @@ -17,16 +17,10 @@ def copy_data(): shutil.rmtree("./data", ignore_errors=True) shutil.copytree("./tests/data/", f"./data/{TEST_ID}/", dirs_exist_ok=True) - - -@pytest.fixture -def client(): app.config["TESTING"] = True app.config["SERVER_NAME"] = "TEST" app.config["DATA_FOLDER_PATH"] = "./data/" app.config["UPLOAD_FOLDER"] = "./tests/data/" - app.config["REQUEST_COUNTER"] = 0 - app.config["LAST_REQUEST_TIME"] = time.time() BASE_DIR = os.path.abspath(os.path.dirname(__file__)) db_path = os.path.join(BASE_DIR, "data", "project.db") app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}" @@ -34,7 +28,13 @@ def client(): print("Current working directory:", os.getcwd()) print("Directory contents:", os.listdir(".")) - init_db(app) + initialize_database(app) + + +@pytest.fixture +def client(): + app.config["REQUEST_COUNTER"] = 0 + app.config["LAST_REQUEST_TIME"] = time.time() client = app.test_client() client.headers = {"Content-type": "application/json", "Accept": "application/json"} yield client diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 9d779ff7..fd1821ba 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -8,7 +8,7 @@ # Local application imports from src.opengeodeweb_back.database import db -from src.opengeodeweb_back.models import Data +from src.opengeodeweb_back.data import Data from src.opengeodeweb_back import geode_functions, utils_functions @@ -99,11 +99,11 @@ def test_save_all_viewables_and_return_info(client): geode_object = "BRep" data = geode_functions.load(geode_object, "./tests/data/test.og_brep") - generated_id, data_path = utils_functions.create_unique_data_folder() + folder_id, data_path = utils_functions.create_unique_data_folder() additional_files = ["additional_file.txt"] result = utils_functions.save_all_viewables_and_return_info( - geode_object, data, generated_id, data_path, additional_files + geode_object, data, folder_id, data_path, additional_files ) assert isinstance(result, dict) @@ -116,16 +116,15 @@ def test_save_all_viewables_and_return_info(client): assert result["geode_object"] == geode_object assert result["input_files"] == additional_files - db_entry = Data.query.get(generated_id) + db_entry = Data.query.get(result["id"]) assert db_entry is not None assert db_entry.name == data.name() assert db_entry.native_file_name == os.path.basename(result["native_file_name"]) - assert db_entry.viewable_file_name == os.path.basename( - result["viewable_file_name"] - ) + assert db_entry.viewable_file_name == os.path.basename(result["viewable_file_name"]) assert db_entry.light_viewable == os.path.basename(db_entry.light_viewable) assert db_entry.geode_object == geode_object - assert db_entry.input_files == additional_files + assert db_entry.input_file == additional_files + assert db_entry.additional_files == additional_files def test_generate_native_viewable_and_light_viewable_from_object(client): From a970a9c0a722c9f8860e3abcb3ea89e724a057b6 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 3 Sep 2025 11:48:35 +0000 Subject: [PATCH 17/41] Apply prepare changes --- src/opengeodeweb_back/data.py | 1 + src/opengeodeweb_back/database.py | 1 + tests/test_utils_functions.py | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 63dfed35..75442aaa 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,6 +1,7 @@ from sqlalchemy import String, JSON from .database import db + class Data(db.Model): __tablename__ = "datas" diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index 17cb3d4f..a96083e1 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -2,6 +2,7 @@ db = SQLAlchemy() + def initialize_database(app): db.init_app(app) with app.app_context(): diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index fd1821ba..2c43042c 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -120,7 +120,9 @@ def test_save_all_viewables_and_return_info(client): assert db_entry is not None assert db_entry.name == data.name() assert db_entry.native_file_name == os.path.basename(result["native_file_name"]) - assert db_entry.viewable_file_name == os.path.basename(result["viewable_file_name"]) + assert db_entry.viewable_file_name == os.path.basename( + result["viewable_file_name"] + ) assert db_entry.light_viewable == os.path.basename(db_entry.light_viewable) assert db_entry.geode_object == geode_object assert db_entry.input_file == additional_files From 0e9e361db6ba22ec15226e5332c852370ca864ec Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 3 Sep 2025 14:00:49 +0200 Subject: [PATCH 18/41] abs path for sqlalchemy_database_uri --- src/opengeodeweb_back/app_config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/opengeodeweb_back/app_config.py b/src/opengeodeweb_back/app_config.py index f04b579e..e34c6206 100644 --- a/src/opengeodeweb_back/app_config.py +++ b/src/opengeodeweb_back/app_config.py @@ -24,9 +24,9 @@ class ProdConfig(Config): MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" DATA_FOLDER_PATH = "/data/" - SQLALCHEMY_DATABASE_URI = ( - f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" - ) + SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.abspath( + os.path.join(DATA_FOLDER_PATH, 'db.sqlite3') + )}" class DevConfig(Config): @@ -35,6 +35,6 @@ class DevConfig(Config): MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" DATA_FOLDER_PATH = "./data/" - SQLALCHEMY_DATABASE_URI = ( - f"sqlite:///{os.path.join(DATA_FOLDER_PATH, 'db.sqlite3')}" - ) + SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.abspath( + os.path.join(DATA_FOLDER_PATH, 'db.sqlite3') + )}" From a70ef53054b2a067a63c2bf9c2d74f0e0f95b10d Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 3 Sep 2025 14:06:20 +0200 Subject: [PATCH 19/41] abs path --- src/opengeodeweb_back/app_config.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/opengeodeweb_back/app_config.py b/src/opengeodeweb_back/app_config.py index e34c6206..6d4cd173 100644 --- a/src/opengeodeweb_back/app_config.py +++ b/src/opengeodeweb_back/app_config.py @@ -23,7 +23,7 @@ class ProdConfig(Config): ORIGINS = "" MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" - DATA_FOLDER_PATH = "/data/" + DATA_FOLDER_PATH = "/data" SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.abspath( os.path.join(DATA_FOLDER_PATH, 'db.sqlite3') )}" @@ -34,7 +34,8 @@ class DevConfig(Config): ORIGINS = "*" MINUTES_BEFORE_TIMEOUT = "1" SECONDS_BETWEEN_SHUTDOWNS = "10" - DATA_FOLDER_PATH = "./data/" - SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.abspath( - os.path.join(DATA_FOLDER_PATH, 'db.sqlite3') + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + DATA_FOLDER_PATH = os.path.join(BASE_DIR, "data") + SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join( + BASE_DIR, DATA_FOLDER_PATH, 'db.sqlite3' )}" From a985ba535e7830c8b914f87bb96f46ad5ba7e2db Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Wed, 3 Sep 2025 17:06:37 +0200 Subject: [PATCH 20/41] id and data infos set by database --- src/opengeodeweb_back/app_config.py | 5 +- src/opengeodeweb_back/data.py | 38 ++++++++++---- src/opengeodeweb_back/database.py | 10 ++-- src/opengeodeweb_back/utils_functions.py | 67 ++++++++++++------------ tests/test_utils_functions.py | 33 ++++++------ 5 files changed, 88 insertions(+), 65 deletions(-) diff --git a/src/opengeodeweb_back/app_config.py b/src/opengeodeweb_back/app_config.py index 6d4cd173..9f60b76d 100644 --- a/src/opengeodeweb_back/app_config.py +++ b/src/opengeodeweb_back/app_config.py @@ -4,6 +4,7 @@ # Third party imports # Local application imports +from .database import DATABASE_FILENAME class Config(object): @@ -25,7 +26,7 @@ class ProdConfig(Config): SECONDS_BETWEEN_SHUTDOWNS = "10" DATA_FOLDER_PATH = "/data" SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.abspath( - os.path.join(DATA_FOLDER_PATH, 'db.sqlite3') + os.path.join(DATA_FOLDER_PATH, DATABASE_FILENAME) )}" @@ -37,5 +38,5 @@ class DevConfig(Config): BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_FOLDER_PATH = os.path.join(BASE_DIR, "data") SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join( - BASE_DIR, DATA_FOLDER_PATH, 'db.sqlite3' + BASE_DIR, DATA_FOLDER_PATH, DATABASE_FILENAME )}" diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 75442aaa..9d0f1b7d 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,15 +1,33 @@ from sqlalchemy import String, JSON -from .database import db +from .database import database +import uuid -class Data(db.Model): +class Data(database.Model): __tablename__ = "datas" - id = db.Column(String, primary_key=True) - name = db.Column(String, nullable=False) - native_file_name = db.Column(String, nullable=False) - viewable_file_name = db.Column(String, nullable=False) - light_viewable = db.Column(String, nullable=True) - geode_object = db.Column(String, nullable=False) - input_file = db.Column(JSON, nullable=True) - additional_files = db.Column(JSON, nullable=True) + id = database.Column( + String, + primary_key=True, + default=lambda: str(uuid.uuid4()).replace("-", "") + ) + name = database.Column(String, nullable=False) + native_file_name = database.Column(String, nullable=False) + viewable_file_name = database.Column(String, nullable=False) + light_viewable = database.Column(String, nullable=True) + geode_object = database.Column(String, nullable=False) + input_file = database.Column(JSON, nullable=True) + additional_files = database.Column(JSON, nullable=True) + + @staticmethod + def create(name, geode_object, input_file, additional_files=[]): + data_entry = Data( + name=name, + geode_object=geode_object, + input_file=input_file, + additional_files=additional_files, + ) + + database.session.add(data_entry) + database.session.flush() + return data_entry diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index a96083e1..db8245b6 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -1,10 +1,12 @@ from flask_sqlalchemy import SQLAlchemy -db = SQLAlchemy() +DATABASE_FILENAME = "project.db" + +database = SQLAlchemy() def initialize_database(app): - db.init_app(app) + database.init_app(app) with app.app_context(): - db.create_all() - return db + database.create_all() + return database diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index cfe86a0d..9a341452 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -2,7 +2,6 @@ import os import threading import time -import uuid import zipfile # Third party imports @@ -15,7 +14,7 @@ # Local application imports from . import geode_functions from .data import Data -from .database import db +from .database import database def increment_request_counter(current_app): @@ -147,17 +146,23 @@ def handle_exception(e): return response -def create_unique_data_folder() -> tuple[str, str]: +def create_data_folder_from_id(data_id: str) -> str: base_data_folder = flask.current_app.config["DATA_FOLDER_PATH"] - generated_id = str(uuid.uuid4()).replace("-", "") - data_path = os.path.join(base_data_folder, generated_id) + data_path = os.path.join(base_data_folder, data_id) os.makedirs(data_path, exist_ok=True) - return generated_id, data_path + return data_path def save_all_viewables_and_return_info( - geode_object, data, generated_id, data_path, additional_files=None + geode_object, data, input_file, additional_files=None ): + data_entry = Data.create( + name=data.name(), + geode_object=geode_object, + input_file=input_file, + additional_files=additional_files + ) + data_path = create_data_folder_from_id(data_entry.id) saved_native_file_path = geode_functions.save( geode_object, data, @@ -172,42 +177,35 @@ def save_all_viewables_and_return_info( ) with open(saved_light_viewable_file_path, "rb") as f: binary_light_viewable = f.read() - - data_entry = Data( - id=generated_id, - name=data.name(), - native_file_name=os.path.basename(saved_native_file_path[0]), - viewable_file_name=os.path.basename(saved_viewable_file_path), - light_viewable=os.path.basename(saved_light_viewable_file_path), - geode_object=geode_object, - input_file=additional_files or [], - additional_files=additional_files or [], - ) - - db.session.add(data_entry) - db.session.commit() + data_entry.native_file_name = os.path.basename(saved_native_file_path[0]) + data_entry.viewable_file_name = os.path.basename(saved_viewable_file_path) + data_entry.light_viewable = os.path.basename(saved_light_viewable_file_path) + + database.session.commit() return { - "name": data.name(), - "native_file_name": os.path.basename(saved_native_file_path[0]), - "viewable_file_name": os.path.basename(saved_viewable_file_path), + "name": data_entry.name, + "native_file_name": data_entry.native_file_name, + "viewable_file_name": data_entry.viewable_file_name, "id": data_entry.id, "object_type": geode_functions.get_object_type(geode_object), "binary_light_viewable": binary_light_viewable.decode("utf-8"), - "geode_object": geode_object, - "input_files": additional_files or [], + "geode_object": data_entry.geode_object, + "input_files": data_entry.additional_files, } def generate_native_viewable_and_light_viewable_from_object(geode_object, data): - generated_id, data_path = create_unique_data_folder() - return save_all_viewables_and_return_info( - geode_object, data, generated_id, data_path - ) + return save_all_viewables_and_return_info(geode_object, data) def generate_native_viewable_and_light_viewable_from_file(geode_object, input_filename): - generated_id, data_path = create_unique_data_folder() + temp_data_entry = Data.create_and_get_id( + name="temp", + geode_object=geode_object + ) + + data_path = create_data_folder_from_id(temp_data_entry.id) full_input_filename = geode_functions.upload_file_path(input_filename) copied_full_path = os.path.join( @@ -230,12 +228,13 @@ def generate_native_viewable_and_light_viewable_from_file(geode_object, input_fi shutil.copy2(source_path, dest_path) additional_files_copied.append(additional_file.filename) - data = geode_functions.load_data(geode_object, generated_id, input_filename) + data = geode_functions.load_data(geode_object, temp_data_entry.id, input_filename) + + database.session.delete(temp_data_entry) + database.session.flush() return save_all_viewables_and_return_info( geode_object, data, - generated_id, - data_path, additional_files=additional_files_copied, ) diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 2c43042c..4b90b5dc 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -5,9 +5,10 @@ # Third party imports import flask import shutil +import uuid # Local application imports -from src.opengeodeweb_back.database import db +from src.opengeodeweb_back.database import database from src.opengeodeweb_back.data import Data from src.opengeodeweb_back import geode_functions, utils_functions @@ -74,15 +75,15 @@ def test_handle_exception(client): assert type(data["code"]) is int -def test_create_unique_data_folder(client): +def test_create_data_folder_from_id(client): app = client.application with app.app_context(): - generated_id, data_path = utils_functions.create_unique_data_folder() - assert isinstance(generated_id, str) - assert re.fullmatch(r"[0-9a-f]{32}", generated_id) + test_id = str(uuid.uuid4()).replace("-", "") + data_path = utils_functions.create_data_folder_from_id(test_id) + assert isinstance(data_path, str) assert os.path.exists(data_path) assert data_path.startswith(flask.current_app.config["DATA_FOLDER_PATH"]) - assert generated_id in data_path + assert test_id in data_path shutil.rmtree(data_path, ignore_errors=True) assert not os.path.exists(data_path) @@ -99,35 +100,37 @@ def test_save_all_viewables_and_return_info(client): geode_object = "BRep" data = geode_functions.load(geode_object, "./tests/data/test.og_brep") - folder_id, data_path = utils_functions.create_unique_data_folder() + input_file = ["test.og_brep"] additional_files = ["additional_file.txt"] result = utils_functions.save_all_viewables_and_return_info( - geode_object, data, folder_id, data_path, additional_files + geode_object, data, input_file, additional_files ) assert isinstance(result, dict) assert result["name"] == data.name() assert result["native_file_name"].startswith("native.") assert result["viewable_file_name"].endswith(".vtm") + assert isinstance(result["id"], str) + assert len(result["id"]) == 32 assert re.match(r"[0-9a-f]{32}", result["id"]) assert isinstance(result["object_type"], str) assert isinstance(result["binary_light_viewable"], str) assert result["geode_object"] == geode_object - assert result["input_files"] == additional_files + assert result["input_files"] == input_file db_entry = Data.query.get(result["id"]) assert db_entry is not None assert db_entry.name == data.name() - assert db_entry.native_file_name == os.path.basename(result["native_file_name"]) - assert db_entry.viewable_file_name == os.path.basename( - result["viewable_file_name"] - ) - assert db_entry.light_viewable == os.path.basename(db_entry.light_viewable) + assert db_entry.native_file_name == result["native_file_name"] + assert db_entry.viewable_file_name == result["viewable_file_name"] assert db_entry.geode_object == geode_object - assert db_entry.input_file == additional_files + assert db_entry.input_file == input_file assert db_entry.additional_files == additional_files + expected_data_path = os.path.join(app.config["DATA_FOLDER_PATH"], result["id"]) + assert os.path.exists(expected_data_path) + def test_generate_native_viewable_and_light_viewable_from_object(client): app = client.application From 4772ac46365247b62a71afd0fc9296422fd7c08f Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:07:01 +0000 Subject: [PATCH 21/41] Apply prepare changes --- mypy.ini | 3 +++ src/opengeodeweb_back/data.py | 6 ++---- src/opengeodeweb_back/utils_functions.py | 11 ++++------- tests/test_utils_functions.py | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..82403124 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +strict = True +files = src/ \ No newline at end of file diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 9d0f1b7d..b6a696ed 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -7,9 +7,7 @@ class Data(database.Model): __tablename__ = "datas" id = database.Column( - String, - primary_key=True, - default=lambda: str(uuid.uuid4()).replace("-", "") + String, primary_key=True, default=lambda: str(uuid.uuid4()).replace("-", "") ) name = database.Column(String, nullable=False) native_file_name = database.Column(String, nullable=False) @@ -27,7 +25,7 @@ def create(name, geode_object, input_file, additional_files=[]): input_file=input_file, additional_files=additional_files, ) - + database.session.add(data_entry) database.session.flush() return data_entry diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 9a341452..2efbb775 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -160,7 +160,7 @@ def save_all_viewables_and_return_info( name=data.name(), geode_object=geode_object, input_file=input_file, - additional_files=additional_files + additional_files=additional_files, ) data_path = create_data_folder_from_id(data_entry.id) saved_native_file_path = geode_functions.save( @@ -180,7 +180,7 @@ def save_all_viewables_and_return_info( data_entry.native_file_name = os.path.basename(saved_native_file_path[0]) data_entry.viewable_file_name = os.path.basename(saved_viewable_file_path) data_entry.light_viewable = os.path.basename(saved_light_viewable_file_path) - + database.session.commit() return { @@ -200,11 +200,8 @@ def generate_native_viewable_and_light_viewable_from_object(geode_object, data): def generate_native_viewable_and_light_viewable_from_file(geode_object, input_filename): - temp_data_entry = Data.create_and_get_id( - name="temp", - geode_object=geode_object - ) - + temp_data_entry = Data.create_and_get_id(name="temp", geode_object=geode_object) + data_path = create_data_folder_from_id(temp_data_entry.id) full_input_filename = geode_functions.upload_file_path(input_filename) diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 4b90b5dc..d8bfd471 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -117,7 +117,7 @@ def test_save_all_viewables_and_return_info(client): assert isinstance(result["object_type"], str) assert isinstance(result["binary_light_viewable"], str) assert result["geode_object"] == geode_object - assert result["input_files"] == input_file + assert result["input_files"] == input_file db_entry = Data.query.get(result["id"]) assert db_entry is not None From 2e9266fef537a6a9546d46b3cde86e5a6bb7e2ec Mon Sep 17 00:00:00 2001 From: Arnaud Botella Date: Wed, 3 Sep 2025 19:18:29 +0200 Subject: [PATCH 22/41] trigger From 65990296853f41389d2aaea6844deafb7a6ed08d Mon Sep 17 00:00:00 2001 From: Arnaud Botella Date: Wed, 3 Sep 2025 19:25:50 +0200 Subject: [PATCH 23/41] again From 25adb82cafc8e6e3e56d967c0e5c3860d079b5e4 Mon Sep 17 00:00:00 2001 From: Arnaud Botella Date: Wed, 3 Sep 2025 19:32:00 +0200 Subject: [PATCH 24/41] trigger From 4088ff4930bd39eae95da959e3614253cda42ef5 Mon Sep 17 00:00:00 2001 From: Arnaud Botella Date: Thu, 4 Sep 2025 08:42:55 +0200 Subject: [PATCH 25/41] again From b8e6ffc7864bf15442a4cc88e4aa980a99b7bb4d Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 10:55:11 +0200 Subject: [PATCH 26/41] new test session.commit to see if it is relevant used non legacy SQLAlchemy methods mypy typing --- src/opengeodeweb_back/data.py | 14 ++++++++++- src/opengeodeweb_back/database.py | 3 ++- src/opengeodeweb_back/utils_functions.py | 32 +++++++++++++++++------- tests/test_utils_functions.py | 21 +++++++++++++++- 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 9d0f1b7d..befb7af4 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,6 +1,7 @@ from sqlalchemy import String, JSON from .database import database import uuid +from typing import List class Data(database.Model): @@ -20,12 +21,23 @@ class Data(database.Model): additional_files = database.Column(JSON, nullable=True) @staticmethod - def create(name, geode_object, input_file, additional_files=[]): + def create( + name: str, + geode_object: str, + input_file: str, + additional_files: List[str] + ) -> 'Data': + if additional_files is None: + additional_files = [] + data_entry = Data( name=name, geode_object=geode_object, input_file=input_file, additional_files=additional_files, + native_file_name="", + viewable_file_name="", + light_viewable="", ) database.session.add(data_entry) diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index db8245b6..c5f268ca 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -1,3 +1,4 @@ +from flask import Flask from flask_sqlalchemy import SQLAlchemy DATABASE_FILENAME = "project.db" @@ -5,7 +6,7 @@ database = SQLAlchemy() -def initialize_database(app): +def initialize_database(app: Flask) -> SQLAlchemy: database.init_app(app) with app.app_context(): database.create_all() diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 9a341452..fed91b77 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -3,6 +3,7 @@ import threading import time import zipfile +from typing import List, Dict, Any # Third party imports import flask @@ -154,8 +155,11 @@ def create_data_folder_from_id(data_id: str) -> str: def save_all_viewables_and_return_info( - geode_object, data, input_file, additional_files=None -): + geode_object: str, + data: Any, + input_file: List[str], + additional_files: List[str] = [] +) -> Dict[str, Any]: data_entry = Data.create( name=data.name(), geode_object=geode_object, @@ -191,18 +195,27 @@ def save_all_viewables_and_return_info( "object_type": geode_functions.get_object_type(geode_object), "binary_light_viewable": binary_light_viewable.decode("utf-8"), "geode_object": data_entry.geode_object, - "input_files": data_entry.additional_files, + "input_files": data_entry.input_file, + "additional_files": data_entry.additional_files, } -def generate_native_viewable_and_light_viewable_from_object(geode_object, data): - return save_all_viewables_and_return_info(geode_object, data) +def generate_native_viewable_and_light_viewable_from_object( + geode_object: str, + data: Any +) -> Dict[str, Any]: + return save_all_viewables_and_return_info(geode_object, data, input_file=[]) -def generate_native_viewable_and_light_viewable_from_file(geode_object, input_filename): - temp_data_entry = Data.create_and_get_id( +def generate_native_viewable_and_light_viewable_from_file( + geode_object: str, + input_filename: str +) -> Dict[str, Any]: + temp_data_entry = Data.create( name="temp", - geode_object=geode_object + geode_object=geode_object, + input_file=[input_filename], + additional_files=[], ) data_path = create_data_folder_from_id(temp_data_entry.id) @@ -213,7 +226,7 @@ def generate_native_viewable_and_light_viewable_from_file(geode_object, input_fi ) shutil.copy2(full_input_filename, copied_full_path) - additional_files_copied = [] + additional_files_copied: List[str] = [] additional = geode_functions.additional_files(geode_object, full_input_filename) for additional_file in additional.mandatory_files + additional.optional_files: if additional_file.is_missing: @@ -236,5 +249,6 @@ def generate_native_viewable_and_light_viewable_from_file(geode_object, input_fi return save_all_viewables_and_return_info( geode_object, data, + input_file=[input_filename], additional_files=additional_files_copied, ) diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 4b90b5dc..b01a7974 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -119,7 +119,7 @@ def test_save_all_viewables_and_return_info(client): assert result["geode_object"] == geode_object assert result["input_files"] == input_file - db_entry = Data.query.get(result["id"]) + db_entry = database.session.get(Data, result["id"]) assert db_entry is not None assert db_entry.name == data.name() assert db_entry.native_file_name == result["native_file_name"] @@ -132,6 +132,25 @@ def test_save_all_viewables_and_return_info(client): assert os.path.exists(expected_data_path) +def test_save_all_viewables_commits_to_db_properly(client): + app = client.application + with app.app_context(): + geode_object = "BRep" + data = geode_functions.load(geode_object, "./tests/data/test.og_brep") + input_file = ["test.og_brep"] + result = utils_functions.save_all_viewables_and_return_info( + geode_object, data, input_file + ) + data_id = result["id"] + db_entry_before = database.session.get(Data,data_id) + assert db_entry_before is not None + assert db_entry_before.native_file_name == result["native_file_name"] + database.session.rollback() + db_entry_after = database.session.get(Data, data_id) + assert db_entry_after is not None, "database.session.commit() was not called - entry missing after rollback" + assert db_entry_after.native_file_name == result["native_file_name"] + + def test_generate_native_viewable_and_light_viewable_from_object(client): app = client.application with app.app_context(): From cb60e9c6e76b863a5d5d603aa977209e75cae435 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 4 Sep 2025 08:56:55 +0000 Subject: [PATCH 27/41] Apply prepare changes --- src/opengeodeweb_back/data.py | 9 +++------ src/opengeodeweb_back/utils_functions.py | 16 +++++++--------- tests/test_utils_functions.py | 6 ++++-- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index ab59ad05..d339d05a 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -20,14 +20,11 @@ class Data(database.Model): @staticmethod def create( - name: str, - geode_object: str, - input_file: str, - additional_files: List[str] - ) -> 'Data': + name: str, geode_object: str, input_file: str, additional_files: List[str] + ) -> "Data": if additional_files is None: additional_files = [] - + data_entry = Data( name=name, geode_object=geode_object, diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index eb787752..050321cf 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -155,10 +155,10 @@ def create_data_folder_from_id(data_id: str) -> str: def save_all_viewables_and_return_info( - geode_object: str, - data: Any, - input_file: List[str], - additional_files: List[str] = [] + geode_object: str, + data: Any, + input_file: List[str], + additional_files: List[str] = [], ) -> Dict[str, Any]: data_entry = Data.create( name=data.name(), @@ -201,15 +201,13 @@ def save_all_viewables_and_return_info( def generate_native_viewable_and_light_viewable_from_object( - geode_object: str, - data: Any + geode_object: str, data: Any ) -> Dict[str, Any]: return save_all_viewables_and_return_info(geode_object, data, input_file=[]) def generate_native_viewable_and_light_viewable_from_file( - geode_object: str, - input_filename: str + geode_object: str, input_filename: str ) -> Dict[str, Any]: temp_data_entry = Data.create( name="temp", @@ -217,7 +215,7 @@ def generate_native_viewable_and_light_viewable_from_file( input_file=[input_filename], additional_files=[], ) - + data_path = create_data_folder_from_id(temp_data_entry.id) full_input_filename = geode_functions.upload_file_path(input_filename) diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 2f64b018..270ecb3f 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -142,12 +142,14 @@ def test_save_all_viewables_commits_to_db_properly(client): geode_object, data, input_file ) data_id = result["id"] - db_entry_before = database.session.get(Data,data_id) + db_entry_before = database.session.get(Data, data_id) assert db_entry_before is not None assert db_entry_before.native_file_name == result["native_file_name"] database.session.rollback() db_entry_after = database.session.get(Data, data_id) - assert db_entry_after is not None, "database.session.commit() was not called - entry missing after rollback" + assert ( + db_entry_after is not None + ), "database.session.commit() was not called - entry missing after rollback" assert db_entry_after.native_file_name == result["native_file_name"] From 3ebe500b416f3eb3e9eb1a2db5ccc24fd6386586 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 11:30:44 +0200 Subject: [PATCH 28/41] add type for sub-class of declarative_base() input_file to str --- src/opengeodeweb_back/data.py | 13 +++++++++---- src/opengeodeweb_back/utils_functions.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index ab59ad05..3f4ca71b 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,14 +1,17 @@ from sqlalchemy import String, JSON from .database import database import uuid -from typing import List +from typing import List, Type +DatabaseModel : Type = database.Model class Data(database.Model): __tablename__ = "datas" id = database.Column( - String, primary_key=True, default=lambda: str(uuid.uuid4()).replace("-", "") + String, + primary_key=True, + default=lambda: str(uuid.uuid4()).replace("-", "") ) name = database.Column(String, nullable=False) native_file_name = database.Column(String, nullable=False) @@ -22,9 +25,11 @@ class Data(database.Model): def create( name: str, geode_object: str, - input_file: str, + input_file: str, additional_files: List[str] - ) -> 'Data': + ) -> "Data": + if input_file is None: + input_file = [] if additional_files is None: additional_files = [] diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index eb787752..ef5b290e 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -214,7 +214,7 @@ def generate_native_viewable_and_light_viewable_from_file( temp_data_entry = Data.create( name="temp", geode_object=geode_object, - input_file=[input_filename], + input_file=input_filename, additional_files=[], ) From 83ee651197877c46b4defd192b6be2aa0f489bdf Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 4 Sep 2025 09:31:34 +0000 Subject: [PATCH 29/41] Apply prepare changes --- src/opengeodeweb_back/data.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 38b9ea8f..4ae1eaf5 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -3,15 +3,14 @@ import uuid from typing import List, Type -DatabaseModel : Type = database.Model +DatabaseModel: Type = database.Model + class Data(database.Model): __tablename__ = "datas" id = database.Column( - String, - primary_key=True, - default=lambda: str(uuid.uuid4()).replace("-", "") + String, primary_key=True, default=lambda: str(uuid.uuid4()).replace("-", "") ) name = database.Column(String, nullable=False) native_file_name = database.Column(String, nullable=False) @@ -23,10 +22,7 @@ class Data(database.Model): @staticmethod def create( - name: str, - geode_object: str, - input_file: str, - additional_files: List[str] + name: str, geode_object: str, input_file: str, additional_files: List[str] ) -> "Data": if input_file is None: input_file = [] From 3bdf3b8fb3c823ead928069454c1577978149217 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 12:09:33 +0200 Subject: [PATCH 30/41] Type["Datas"] --- src/opengeodeweb_back/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 38b9ea8f..bef83f52 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,7 +1,7 @@ from sqlalchemy import String, JSON from .database import database import uuid -from typing import List, Type +from typing import Type DatabaseModel : Type = database.Model @@ -26,8 +26,8 @@ def create( name: str, geode_object: str, input_file: str, - additional_files: List[str] - ) -> "Data": + additional_files: list[str] + ) -> Type["Data"]: if input_file is None: input_file = [] if additional_files is None: From 199727ada0e570730ef1135ad09eb43dd8acd78b Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:12:16 +0000 Subject: [PATCH 31/41] Apply prepare changes --- src/opengeodeweb_back/data.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 08de38b6..435401fc 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -20,10 +20,7 @@ class Data(database.Model): @staticmethod def create( - name: str, - geode_object: str, - input_file: str, - additional_files: list[str] + name: str, geode_object: str, input_file: str, additional_files: list[str] ) -> Type["Data"]: if input_file is None: input_file = [] From f59741965f2b14ae3e3e5a66bac90a28ce4770cd Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 13:34:45 +0200 Subject: [PATCH 32/41] test type subclass --- src/opengeodeweb_back/data.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 08de38b6..c15bb2bc 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,13 +1,19 @@ +from typing import List, Optional, TYPE_CHECKING from sqlalchemy import String, JSON +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from .database import database import uuid -from typing import Type +class Base(DeclarativeBase): + pass -class Data(database.Model): +if TYPE_CHECKING: + from sqlalchemy.orm import DeclarativeBase + +class Data(Base): __tablename__ = "datas" - id = database.Column( + id: Mapped[str] = mapped_column( String, primary_key=True, default=lambda: str(uuid.uuid4()).replace("-", "") ) name = database.Column(String, nullable=False) @@ -24,7 +30,7 @@ def create( geode_object: str, input_file: str, additional_files: list[str] - ) -> Type["Data"]: + ) -> "Data": if input_file is None: input_file = [] if additional_files is None: @@ -37,7 +43,7 @@ def create( additional_files=additional_files, native_file_name="", viewable_file_name="", - light_viewable="", + light_viewable=None, ) database.session.add(data_entry) From e1490cae799207284a1683025a714ba503bbc179 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:35:38 +0000 Subject: [PATCH 33/41] Apply prepare changes --- src/opengeodeweb_back/data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index c15bb2bc..ff3e750f 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -4,12 +4,15 @@ from .database import database import uuid + class Base(DeclarativeBase): pass + if TYPE_CHECKING: from sqlalchemy.orm import DeclarativeBase + class Data(Base): __tablename__ = "datas" @@ -26,10 +29,7 @@ class Data(Base): @staticmethod def create( - name: str, - geode_object: str, - input_file: str, - additional_files: list[str] + name: str, geode_object: str, input_file: str, additional_files: list[str] ) -> "Data": if input_file is None: input_file = [] From 76762fda33ac4f68033cbdfbe89860fa54f62f04 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 14:17:17 +0200 Subject: [PATCH 34/41] mypy Base(DeclarativeBase) --- src/opengeodeweb_back/data.py | 40 +++++++++++++------------------ src/opengeodeweb_back/database.py | 5 +++- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index ff3e750f..f6630bfb 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,40 +1,32 @@ -from typing import List, Optional, TYPE_CHECKING +from typing import List, Optional from sqlalchemy import String, JSON -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column -from .database import database +from sqlalchemy.orm import Mapped, mapped_column +from .database import database, Base import uuid - -class Base(DeclarativeBase): - pass - - -if TYPE_CHECKING: - from sqlalchemy.orm import DeclarativeBase - - class Data(Base): __tablename__ = "datas" id: Mapped[str] = mapped_column( String, primary_key=True, default=lambda: str(uuid.uuid4()).replace("-", "") ) - name = database.Column(String, nullable=False) - native_file_name = database.Column(String, nullable=False) - viewable_file_name = database.Column(String, nullable=False) - light_viewable = database.Column(String, nullable=True) - geode_object = database.Column(String, nullable=False) - input_file = database.Column(JSON, nullable=True) - additional_files = database.Column(JSON, nullable=True) + name: Mapped[str] = mapped_column(String, nullable=False) + native_file_name: Mapped[str] = mapped_column(String, nullable=False) + viewable_file_name: Mapped[str] = mapped_column(String, nullable=False) + light_viewable: Mapped[Optional[str]] = mapped_column(String, nullable=True) + geode_object: Mapped[str] = mapped_column(String, nullable=False) + input_file: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) + additional_files: Mapped[Optional[List[str]]] = mapped_column(JSON, nullable=True) @staticmethod def create( - name: str, geode_object: str, input_file: str, additional_files: list[str] + name: str, + geode_object: str, + input_file: Optional[dict] = None, + additional_files: Optional[List[str]] = None ) -> "Data": - if input_file is None: - input_file = [] - if additional_files is None: - additional_files = [] + input_file = input_file if input_file is not None else {} + additional_files = additional_files if additional_files is not None else [] data_entry = Data( name=name, diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index c5f268ca..2fd05701 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -1,10 +1,13 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy +from sqlalchemy.orm import DeclarativeBase DATABASE_FILENAME = "project.db" -database = SQLAlchemy() +class Base(DeclarativeBase): + pass +database = SQLAlchemy(model_class=Base) def initialize_database(app: Flask) -> SQLAlchemy: database.init_app(app) From a85d86885b9d8c33c5ad528cb6c1327be5e691e8 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:17:43 +0000 Subject: [PATCH 35/41] Apply prepare changes --- src/opengeodeweb_back/data.py | 3 ++- src/opengeodeweb_back/database.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index f6630bfb..a6e29ad2 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -4,6 +4,7 @@ from .database import database, Base import uuid + class Data(Base): __tablename__ = "datas" @@ -23,7 +24,7 @@ def create( name: str, geode_object: str, input_file: Optional[dict] = None, - additional_files: Optional[List[str]] = None + additional_files: Optional[List[str]] = None, ) -> "Data": input_file = input_file if input_file is not None else {} additional_files = additional_files if additional_files is not None else [] diff --git a/src/opengeodeweb_back/database.py b/src/opengeodeweb_back/database.py index 2fd05701..d5e2f0f7 100644 --- a/src/opengeodeweb_back/database.py +++ b/src/opengeodeweb_back/database.py @@ -4,11 +4,14 @@ DATABASE_FILENAME = "project.db" + class Base(DeclarativeBase): pass + database = SQLAlchemy(model_class=Base) + def initialize_database(app: Flask) -> SQLAlchemy: database.init_app(app) with app.app_context(): From fd1967af8f66be787de3f10e31707edc3a968013 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 14:29:11 +0200 Subject: [PATCH 36/41] final mypy rename List, Dict --- src/opengeodeweb_back/data.py | 12 ++++++------ src/opengeodeweb_back/utils_functions.py | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index f6630bfb..bf8bece6 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -1,4 +1,3 @@ -from typing import List, Optional from sqlalchemy import String, JSON from sqlalchemy.orm import Mapped, mapped_column from .database import database, Base @@ -13,17 +12,18 @@ class Data(Base): name: Mapped[str] = mapped_column(String, nullable=False) native_file_name: Mapped[str] = mapped_column(String, nullable=False) viewable_file_name: Mapped[str] = mapped_column(String, nullable=False) - light_viewable: Mapped[Optional[str]] = mapped_column(String, nullable=True) geode_object: Mapped[str] = mapped_column(String, nullable=False) - input_file: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True) - additional_files: Mapped[Optional[List[str]]] = mapped_column(JSON, nullable=True) + + light_viewable: Mapped[str | None] = mapped_column(String, nullable=True) + input_file: Mapped[str | None] = mapped_column(String, nullable=True) + additional_files: Mapped[list[str] | None] = mapped_column(JSON, nullable=True) @staticmethod def create( name: str, geode_object: str, - input_file: Optional[dict] = None, - additional_files: Optional[List[str]] = None + input_file: str, + additional_files: list[str] | None = None ) -> "Data": input_file = input_file if input_file is not None else {} additional_files = additional_files if additional_files is not None else [] diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 7ba4fc80..8358ac6b 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -3,7 +3,7 @@ import threading import time import zipfile -from typing import List, Dict, Any +from typing import Any # Third party imports import flask @@ -157,9 +157,9 @@ def create_data_folder_from_id(data_id: str) -> str: def save_all_viewables_and_return_info( geode_object: str, data: Any, - input_file: List[str], - additional_files: List[str] = [], -) -> Dict[str, Any]: + input_file: list[str], + additional_files: list[str] = [], +) -> dict[str, Any]: data_entry = Data.create( name=data.name(), geode_object=geode_object, @@ -202,13 +202,13 @@ def save_all_viewables_and_return_info( def generate_native_viewable_and_light_viewable_from_object( geode_object: str, data: Any -) -> Dict[str, Any]: +) -> dict[str, Any]: return save_all_viewables_and_return_info(geode_object, data, input_file=[]) def generate_native_viewable_and_light_viewable_from_file( geode_object: str, input_filename: str -) -> Dict[str, Any]: +) -> dict[str, Any]: temp_data_entry = Data.create( name="temp", geode_object=geode_object, @@ -224,7 +224,7 @@ def generate_native_viewable_and_light_viewable_from_file( ) shutil.copy2(full_input_filename, copied_full_path) - additional_files_copied: List[str] = [] + additional_files_copied: list[str] = [] additional = geode_functions.additional_files(geode_object, full_input_filename) for additional_file in additional.mandatory_files + additional.optional_files: if additional_file.is_missing: From 410580f0df82634f32297eaced526dac9aa173aa Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:30:32 +0000 Subject: [PATCH 37/41] Apply prepare changes --- src/opengeodeweb_back/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index 66ea6147..ece9fdf1 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -24,7 +24,7 @@ def create( name: str, geode_object: str, input_file: str, - additional_files: list[str] | None = None + additional_files: list[str] | None = None, ) -> "Data": input_file = input_file if input_file is not None else {} additional_files = additional_files if additional_files is not None else [] From 4217ed4955722c98b2058baf04aad8728d0898ce Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 14:33:01 +0200 Subject: [PATCH 38/41] input_file: str --- src/opengeodeweb_back/utils_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 8358ac6b..c81736e3 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -157,7 +157,7 @@ def create_data_folder_from_id(data_id: str) -> str: def save_all_viewables_and_return_info( geode_object: str, data: Any, - input_file: list[str], + input_file: str, additional_files: list[str] = [], ) -> dict[str, Any]: data_entry = Data.create( From 9db80dc25e36c4396b16e5f5e53c3e8bed103600 Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 14:36:03 +0200 Subject: [PATCH 39/41] final --- src/opengeodeweb_back/utils_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index c81736e3..bd060578 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -201,9 +201,9 @@ def save_all_viewables_and_return_info( def generate_native_viewable_and_light_viewable_from_object( - geode_object: str, data: Any + geode_object: str, data: Any, input_file: str ) -> dict[str, Any]: - return save_all_viewables_and_return_info(geode_object, data, input_file=[]) + return save_all_viewables_and_return_info(geode_object, data, input_file) def generate_native_viewable_and_light_viewable_from_file( From 5f4673c46e75c9971c295d3ded95c6f0e8c252cb Mon Sep 17 00:00:00 2001 From: MaxNumerique Date: Thu, 4 Sep 2025 14:47:20 +0200 Subject: [PATCH 40/41] input_file as str --- src/opengeodeweb_back/data.py | 4 ++-- src/opengeodeweb_back/utils_functions.py | 13 ++++++++----- tests/test_utils_functions.py | 8 ++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/opengeodeweb_back/data.py b/src/opengeodeweb_back/data.py index ece9fdf1..ce3b6a13 100644 --- a/src/opengeodeweb_back/data.py +++ b/src/opengeodeweb_back/data.py @@ -23,10 +23,10 @@ class Data(Base): def create( name: str, geode_object: str, - input_file: str, + input_file: str | None = None, additional_files: list[str] | None = None, ) -> "Data": - input_file = input_file if input_file is not None else {} + input_file = input_file if input_file is not None else "" additional_files = additional_files if additional_files is not None else [] data_entry = Data( diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index bd060578..f519d567 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -158,8 +158,11 @@ def save_all_viewables_and_return_info( geode_object: str, data: Any, input_file: str, - additional_files: list[str] = [], + additional_files: list[str] | None = None, ) -> dict[str, Any]: + if additional_files is None: + additional_files = [] + data_entry = Data.create( name=data.name(), geode_object=geode_object, @@ -201,9 +204,9 @@ def save_all_viewables_and_return_info( def generate_native_viewable_and_light_viewable_from_object( - geode_object: str, data: Any, input_file: str + geode_object: str, data: Any ) -> dict[str, Any]: - return save_all_viewables_and_return_info(geode_object, data, input_file) + return save_all_viewables_and_return_info(geode_object, data, input_file="") def generate_native_viewable_and_light_viewable_from_file( @@ -247,6 +250,6 @@ def generate_native_viewable_and_light_viewable_from_file( return save_all_viewables_and_return_info( geode_object, data, - input_file=[input_filename], + input_file=input_filename, additional_files=additional_files_copied, - ) + ) \ No newline at end of file diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 270ecb3f..cea87147 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -100,7 +100,7 @@ def test_save_all_viewables_and_return_info(client): geode_object = "BRep" data = geode_functions.load(geode_object, "./tests/data/test.og_brep") - input_file = ["test.og_brep"] + input_file = "test.og_brep" additional_files = ["additional_file.txt"] result = utils_functions.save_all_viewables_and_return_info( @@ -137,7 +137,7 @@ def test_save_all_viewables_commits_to_db_properly(client): with app.app_context(): geode_object = "BRep" data = geode_functions.load(geode_object, "./tests/data/test.og_brep") - input_file = ["test.og_brep"] + input_file = "test.og_brep" result = utils_functions.save_all_viewables_and_return_info( geode_object, data, input_file ) @@ -175,7 +175,7 @@ def test_generate_native_viewable_and_light_viewable_from_object(client): assert re.match(r"[0-9a-f]{32}", result["id"]) assert isinstance(result["object_type"], str) assert isinstance(result["binary_light_viewable"], str) - assert result["input_files"] == [] + assert result["input_files"] == "" def test_generate_native_viewable_and_light_viewable_from_file(client): @@ -198,4 +198,4 @@ def test_generate_native_viewable_and_light_viewable_from_file(client): assert re.match(r"[0-9a-f]{32}", result["id"]) assert isinstance(result["object_type"], str) assert isinstance(result["binary_light_viewable"], str) - assert isinstance(result["input_files"], list) + assert isinstance(result["input_files"], str) From e3ac5298fafd15fe288221a34a60ffccffa62d37 Mon Sep 17 00:00:00 2001 From: MaxNumerique <144453705+MaxNumerique@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:47:45 +0000 Subject: [PATCH 41/41] Apply prepare changes --- src/opengeodeweb_back/utils_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index f519d567..c2403a24 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -162,7 +162,7 @@ def save_all_viewables_and_return_info( ) -> dict[str, Any]: if additional_files is None: additional_files = [] - + data_entry = Data.create( name=data.name(), geode_object=geode_object, @@ -252,4 +252,4 @@ def generate_native_viewable_and_light_viewable_from_file( data, input_file=input_filename, additional_files=additional_files_copied, - ) \ No newline at end of file + )