OpenGeodeWeb-Back 5.10.0rc5__tar.gz → 5.10.0rc8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {opengeodeweb_back-5.10.0rc5/src/OpenGeodeWeb_Back.egg-info → opengeodeweb_back-5.10.0rc8}/PKG-INFO +5 -1
  2. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/pyproject.toml +1 -1
  3. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/requirements.txt +9 -0
  4. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8/src/OpenGeodeWeb_Back.egg-info}/PKG-INFO +5 -1
  5. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/OpenGeodeWeb_Back.egg-info/SOURCES.txt +2 -0
  6. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/OpenGeodeWeb_Back.egg-info/requires.txt +4 -0
  7. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/app_config.py +11 -2
  8. opengeodeweb_back-5.10.0rc8/src/opengeodeweb_back/data.py +44 -0
  9. opengeodeweb_back-5.10.0rc8/src/opengeodeweb_back/database.py +19 -0
  10. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/geode_functions.py +2 -2
  11. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/geode_objects.py +6 -6
  12. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/utils_functions.py +91 -55
  13. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/tests/test_routes.py +12 -0
  14. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/tests/test_utils_functions.py +63 -18
  15. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/LICENSE +0 -0
  16. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/README.md +0 -0
  17. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/setup.cfg +0 -0
  18. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/OpenGeodeWeb_Back.egg-info/dependency_links.txt +0 -0
  19. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/OpenGeodeWeb_Back.egg-info/top_level.txt +0 -0
  20. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/__init__.py +0 -0
  21. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/blueprint_routes.py +0 -0
  22. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/models/blueprint_models.py +0 -0
  23. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/models/schemas/mesh_components.json +0 -0
  24. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/models/schemas/vtm_component_indices.json +0 -0
  25. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/allowed_files.json +0 -0
  26. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/allowed_objects.json +0 -0
  27. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/create_point.json +0 -0
  28. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/geode_objects_and_output_extensions.json +0 -0
  29. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.json +0 -0
  30. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/inspect_file.json +0 -0
  31. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/missing_files.json +0 -0
  32. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/ping.json +0 -0
  33. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json +0 -0
  34. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/polyhedron_attribute_names.json +0 -0
  35. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/save_viewable_file.json +0 -0
  36. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/texture_coordinates.json +0 -0
  37. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/upload_file.json +0 -0
  38. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json +0 -0
  39. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/src/opengeodeweb_back/test_utils.py +0 -0
  40. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/tests/test_geode_functions.py +0 -0
  41. {opengeodeweb_back-5.10.0rc5 → opengeodeweb_back-5.10.0rc8}/tests/test_models_routes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenGeodeWeb-Back
3
- Version: 5.10.0rc5
3
+ Version: 5.10.0rc8
4
4
  Summary: OpenGeodeWeb-Back is an open source framework that proposes handy python functions and wrappers for the OpenGeode ecosystem
5
5
  Author-email: Geode-solutions <team-web@geode-solutions.com>
6
6
  Project-URL: Homepage, https://github.com/Geode-solutions/OpenGeodeWeb-Back
@@ -17,8 +17,10 @@ Requires-Dist: click==8.2.1
17
17
  Requires-Dist: fastjsonschema==2.16.2
18
18
  Requires-Dist: flask[async]==3.0.3
19
19
  Requires-Dist: flask-cors==6.0.1
20
+ Requires-Dist: flask-sqlalchemy==3.1.1
20
21
  Requires-Dist: geode-common==33.9.0
21
22
  Requires-Dist: geode-viewables==3.2.0
23
+ Requires-Dist: greenlet==3.2.4
22
24
  Requires-Dist: itsdangerous==2.2.0
23
25
  Requires-Dist: jinja2==3.1.6
24
26
  Requires-Dist: markupsafe==3.0.2
@@ -27,6 +29,8 @@ Requires-Dist: opengeode-geosciences==9.2.2
27
29
  Requires-Dist: opengeode-geosciencesio==5.7.2
28
30
  Requires-Dist: opengeode-inspector==6.7.0
29
31
  Requires-Dist: opengeode-io==7.3.2
32
+ Requires-Dist: sqlalchemy==2.0.43
33
+ Requires-Dist: typing-extensions==4.15.0
30
34
  Requires-Dist: werkzeug==3.0.3
31
35
  Dynamic: license-file
32
36
 
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "OpenGeodeWeb-Back"
8
- version = "5.10.0-rc.5"
8
+ version = "5.10.0-rc.8"
9
9
  dynamic = ["dependencies"]
10
10
  authors = [
11
11
  { name="Geode-solutions", email="team-web@geode-solutions.com" },
@@ -17,12 +17,17 @@ flask[async]==3.0.3
17
17
  # -r requirements.in
18
18
  # flask
19
19
  # flask-cors
20
+ # flask-sqlalchemy
20
21
  flask-cors==6.0.1
21
22
  # via -r requirements.in
23
+ flask-sqlalchemy==3.1.1
24
+ # via -r requirements.in
22
25
  geode-common==33.9.0
23
26
  # via geode-viewables
24
27
  geode-viewables==3.2.0
25
28
  # via -r requirements.in
29
+ greenlet==3.2.4
30
+ # via sqlalchemy
26
31
  itsdangerous==2.2.0
27
32
  # via flask
28
33
  jinja2==3.1.6
@@ -54,6 +59,10 @@ opengeode-io==7.3.2
54
59
  # -r requirements.in
55
60
  # geode-viewables
56
61
  # opengeode-geosciencesio
62
+ sqlalchemy==2.0.43
63
+ # via flask-sqlalchemy
64
+ typing-extensions==4.15.0
65
+ # via sqlalchemy
57
66
  werkzeug==3.0.3
58
67
  # via
59
68
  # -r requirements.in
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenGeodeWeb-Back
3
- Version: 5.10.0rc5
3
+ Version: 5.10.0rc8
4
4
  Summary: OpenGeodeWeb-Back is an open source framework that proposes handy python functions and wrappers for the OpenGeode ecosystem
5
5
  Author-email: Geode-solutions <team-web@geode-solutions.com>
6
6
  Project-URL: Homepage, https://github.com/Geode-solutions/OpenGeodeWeb-Back
@@ -17,8 +17,10 @@ Requires-Dist: click==8.2.1
17
17
  Requires-Dist: fastjsonschema==2.16.2
18
18
  Requires-Dist: flask[async]==3.0.3
19
19
  Requires-Dist: flask-cors==6.0.1
20
+ Requires-Dist: flask-sqlalchemy==3.1.1
20
21
  Requires-Dist: geode-common==33.9.0
21
22
  Requires-Dist: geode-viewables==3.2.0
23
+ Requires-Dist: greenlet==3.2.4
22
24
  Requires-Dist: itsdangerous==2.2.0
23
25
  Requires-Dist: jinja2==3.1.6
24
26
  Requires-Dist: markupsafe==3.0.2
@@ -27,6 +29,8 @@ Requires-Dist: opengeode-geosciences==9.2.2
27
29
  Requires-Dist: opengeode-geosciencesio==5.7.2
28
30
  Requires-Dist: opengeode-inspector==6.7.0
29
31
  Requires-Dist: opengeode-io==7.3.2
32
+ Requires-Dist: sqlalchemy==2.0.43
33
+ Requires-Dist: typing-extensions==4.15.0
30
34
  Requires-Dist: werkzeug==3.0.3
31
35
  Dynamic: license-file
32
36
 
@@ -9,6 +9,8 @@ src/OpenGeodeWeb_Back.egg-info/requires.txt
9
9
  src/OpenGeodeWeb_Back.egg-info/top_level.txt
10
10
  src/opengeodeweb_back/__init__.py
11
11
  src/opengeodeweb_back/app_config.py
12
+ src/opengeodeweb_back/data.py
13
+ src/opengeodeweb_back/database.py
12
14
  src/opengeodeweb_back/geode_functions.py
13
15
  src/opengeodeweb_back/geode_objects.py
14
16
  src/opengeodeweb_back/test_utils.py
@@ -4,8 +4,10 @@ click==8.2.1
4
4
  fastjsonschema==2.16.2
5
5
  flask[async]==3.0.3
6
6
  flask-cors==6.0.1
7
+ flask-sqlalchemy==3.1.1
7
8
  geode-common==33.9.0
8
9
  geode-viewables==3.2.0
10
+ greenlet==3.2.4
9
11
  itsdangerous==2.2.0
10
12
  jinja2==3.1.6
11
13
  markupsafe==3.0.2
@@ -14,4 +16,6 @@ opengeode-geosciences==9.2.2
14
16
  opengeode-geosciencesio==5.7.2
15
17
  opengeode-inspector==6.7.0
16
18
  opengeode-io==7.3.2
19
+ sqlalchemy==2.0.43
20
+ typing-extensions==4.15.0
17
21
  werkzeug==3.0.3
@@ -4,6 +4,7 @@ import time
4
4
 
5
5
  # Third party imports
6
6
  # Local application imports
7
+ from .database import DATABASE_FILENAME
7
8
 
8
9
 
9
10
  class Config(object):
@@ -15,6 +16,7 @@ class Config(object):
15
16
  REQUEST_COUNTER = 0
16
17
  LAST_REQUEST_TIME = time.time()
17
18
  LAST_PING_TIME = time.time()
19
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
18
20
 
19
21
 
20
22
  class ProdConfig(Config):
@@ -22,7 +24,10 @@ class ProdConfig(Config):
22
24
  ORIGINS = ""
23
25
  MINUTES_BEFORE_TIMEOUT = "1"
24
26
  SECONDS_BETWEEN_SHUTDOWNS = "10"
25
- DATA_FOLDER_PATH = "/data/"
27
+ DATA_FOLDER_PATH = "/data"
28
+ SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.abspath(
29
+ os.path.join(DATA_FOLDER_PATH, DATABASE_FILENAME)
30
+ )}"
26
31
 
27
32
 
28
33
  class DevConfig(Config):
@@ -30,4 +35,8 @@ class DevConfig(Config):
30
35
  ORIGINS = "*"
31
36
  MINUTES_BEFORE_TIMEOUT = "1"
32
37
  SECONDS_BETWEEN_SHUTDOWNS = "10"
33
- DATA_FOLDER_PATH = "./data/"
38
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
39
+ DATA_FOLDER_PATH = os.path.join(BASE_DIR, "data")
40
+ SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(
41
+ BASE_DIR, DATA_FOLDER_PATH, DATABASE_FILENAME
42
+ )}"
@@ -0,0 +1,44 @@
1
+ from sqlalchemy import String, JSON
2
+ from sqlalchemy.orm import Mapped, mapped_column
3
+ from .database import database, Base
4
+ import uuid
5
+
6
+
7
+ class Data(Base):
8
+ __tablename__ = "datas"
9
+
10
+ id: Mapped[str] = mapped_column(
11
+ String, primary_key=True, default=lambda: str(uuid.uuid4()).replace("-", "")
12
+ )
13
+ name: Mapped[str] = mapped_column(String, nullable=False)
14
+ native_file_name: Mapped[str] = mapped_column(String, nullable=False)
15
+ viewable_file_name: Mapped[str] = mapped_column(String, nullable=False)
16
+ geode_object: Mapped[str] = mapped_column(String, nullable=False)
17
+
18
+ light_viewable: Mapped[str | None] = mapped_column(String, nullable=True)
19
+ input_file: Mapped[str | None] = mapped_column(String, nullable=True)
20
+ additional_files: Mapped[list[str] | None] = mapped_column(JSON, nullable=True)
21
+
22
+ @staticmethod
23
+ def create(
24
+ name: str,
25
+ geode_object: str,
26
+ input_file: str | None = None,
27
+ additional_files: list[str] | None = None,
28
+ ) -> "Data":
29
+ input_file = input_file if input_file is not None else ""
30
+ additional_files = additional_files if additional_files is not None else []
31
+
32
+ data_entry = Data(
33
+ name=name,
34
+ geode_object=geode_object,
35
+ input_file=input_file,
36
+ additional_files=additional_files,
37
+ native_file_name="",
38
+ viewable_file_name="",
39
+ light_viewable=None,
40
+ )
41
+
42
+ database.session.add(data_entry)
43
+ database.session.flush()
44
+ return data_entry
@@ -0,0 +1,19 @@
1
+ from flask import Flask
2
+ from flask_sqlalchemy import SQLAlchemy
3
+ from sqlalchemy.orm import DeclarativeBase
4
+
5
+ DATABASE_FILENAME = "project.db"
6
+
7
+
8
+ class Base(DeclarativeBase):
9
+ pass
10
+
11
+
12
+ database = SQLAlchemy(model_class=Base)
13
+
14
+
15
+ def initialize_database(app: Flask) -> SQLAlchemy:
16
+ database.init_app(app)
17
+ with app.app_context():
18
+ database.create_all()
19
+ return database
@@ -2,8 +2,8 @@
2
2
  import os
3
3
 
4
4
  # Third party imports
5
- import opengeode_geosciences as og_gs
6
- import opengeode as og
5
+ import opengeode_geosciences as og_gs # type: ignore
6
+ import opengeode as og # type: ignore
7
7
  import werkzeug
8
8
  import flask
9
9
 
@@ -1,12 +1,12 @@
1
1
  # Standard library imports
2
2
 
3
3
  # Third party imports
4
- import opengeode as og
5
- import opengeode_io as og_io
6
- import opengeode_inspector as og_inspector
7
- import opengeode_geosciences as og_gs
8
- import opengeode_geosciencesio as og_gs_io
9
- import geode_viewables as g_v
4
+ import opengeode as og # type: ignore
5
+ import opengeode_io as og_io # type: ignore
6
+ import opengeode_inspector as og_inspector # type: ignore
7
+ import opengeode_geosciences as og_gs # type: ignore
8
+ import opengeode_geosciencesio as og_gs_io # type: ignore
9
+ import geode_viewables as g_v # type: ignore
10
10
 
11
11
  # Local application imports
12
12
 
@@ -2,54 +2,58 @@
2
2
  import os
3
3
  import threading
4
4
  import time
5
- import uuid
6
5
  import zipfile
6
+ from collections.abc import Callable
7
+ from typing import Any
7
8
 
8
9
  # Third party imports
9
10
  import flask
10
- import fastjsonschema
11
+ import fastjsonschema # type: ignore
11
12
  import importlib.metadata as metadata
12
13
  import shutil
14
+ from werkzeug.exceptions import HTTPException
13
15
  import werkzeug
14
16
 
15
17
  # Local application imports
16
18
  from . import geode_functions
19
+ from .data import Data
20
+ from .database import database
17
21
 
18
22
 
19
- def increment_request_counter(current_app):
23
+ def increment_request_counter(current_app: flask.Flask) -> None:
20
24
  if "REQUEST_COUNTER" in current_app.config:
21
- REQUEST_COUNTER = int(current_app.config.get("REQUEST_COUNTER"))
25
+ REQUEST_COUNTER = int(current_app.config.get("REQUEST_COUNTER", 0))
22
26
  REQUEST_COUNTER += 1
23
27
  current_app.config.update(REQUEST_COUNTER=REQUEST_COUNTER)
24
28
 
25
29
 
26
- def decrement_request_counter(current_app):
30
+ def decrement_request_counter(current_app: flask.Flask) -> None:
27
31
  if "REQUEST_COUNTER" in current_app.config:
28
- REQUEST_COUNTER = int(current_app.config.get("REQUEST_COUNTER"))
32
+ REQUEST_COUNTER = int(current_app.config.get("REQUEST_COUNTER", 0))
29
33
  REQUEST_COUNTER -= 1
30
34
  current_app.config.update(REQUEST_COUNTER=REQUEST_COUNTER)
31
35
 
32
36
 
33
- def update_last_request_time(current_app):
37
+ def update_last_request_time(current_app: flask.Flask) -> None:
34
38
  if "LAST_REQUEST_TIME" in current_app.config:
35
39
  LAST_REQUEST_TIME = time.time()
36
40
  current_app.config.update(LAST_REQUEST_TIME=LAST_REQUEST_TIME)
37
41
 
38
42
 
39
- def before_request(current_app):
43
+ def before_request(current_app: flask.Flask) -> None:
40
44
  increment_request_counter(current_app)
41
45
 
42
46
 
43
- def teardown_request(current_app):
47
+ def teardown_request(current_app: flask.Flask) -> None:
44
48
  decrement_request_counter(current_app)
45
49
  update_last_request_time(current_app)
46
50
 
47
51
 
48
- def kill_task(current_app):
49
- REQUEST_COUNTER = int(current_app.config.get("REQUEST_COUNTER"))
50
- LAST_PING_TIME = float(current_app.config.get("LAST_PING_TIME"))
51
- LAST_REQUEST_TIME = float(current_app.config.get("LAST_REQUEST_TIME"))
52
- MINUTES_BEFORE_TIMEOUT = float(current_app.config.get("MINUTES_BEFORE_TIMEOUT"))
52
+ def kill_task(current_app: flask.Flask) -> None:
53
+ REQUEST_COUNTER = int(current_app.config.get("REQUEST_COUNTER", 0))
54
+ LAST_PING_TIME = float(current_app.config.get("LAST_PING_TIME", 0))
55
+ LAST_REQUEST_TIME = float(current_app.config.get("LAST_REQUEST_TIME", 0))
56
+ MINUTES_BEFORE_TIMEOUT = float(current_app.config.get("MINUTES_BEFORE_TIMEOUT", 0))
53
57
  current_time = time.time()
54
58
  minutes_since_last_request = (current_time - LAST_REQUEST_TIME) / 60
55
59
  minutes_since_last_ping = (current_time - LAST_PING_TIME) / 60
@@ -64,12 +68,12 @@ def kill_task(current_app):
64
68
  kill_server()
65
69
 
66
70
 
67
- def kill_server():
71
+ def kill_server() -> None:
68
72
  print("Server timed out due to inactivity, shutting down...", flush=True)
69
73
  os._exit(0)
70
74
 
71
75
 
72
- def versions(list_packages: list):
76
+ def versions(list_packages: list[str]) -> list[dict[str, str]]:
73
77
  list_with_versions = []
74
78
  for package in list_packages:
75
79
  list_with_versions.append(
@@ -78,7 +82,7 @@ def versions(list_packages: list):
78
82
  return list_with_versions
79
83
 
80
84
 
81
- def validate_request(request, schema):
85
+ def validate_request(request: flask.Request, schema: dict[str, str]) -> None:
82
86
  json_data = request.get_json(force=True, silent=True)
83
87
 
84
88
  if json_data is None:
@@ -92,22 +96,26 @@ def validate_request(request, schema):
92
96
  flask.abort(400, error_msg)
93
97
 
94
98
 
95
- def set_interval(func, sec, args=None):
96
- def func_wrapper():
97
- set_interval(func, sec, args)
98
- func(args)
99
+ def set_interval(
100
+ function: Callable[[Any], None], seconds: float, args: Any
101
+ ) -> threading.Timer:
102
+ def function_wrapper() -> None:
103
+ set_interval(function, seconds, args)
104
+ function(args)
99
105
 
100
- t = threading.Timer(sec, func_wrapper)
101
- t.daemon = True
102
- t.start()
103
- return t
106
+ timer = threading.Timer(seconds, function_wrapper)
107
+ timer.daemon = True
108
+ timer.start()
109
+ return timer
104
110
 
105
111
 
106
- def extension_from_filename(filename):
112
+ def extension_from_filename(filename: str) -> str:
107
113
  return os.path.splitext(filename)[1][1:]
108
114
 
109
115
 
110
- def send_file(upload_folder, saved_files, new_file_name):
116
+ def send_file(
117
+ upload_folder: str, saved_files: str, new_file_name: str
118
+ ) -> flask.Response:
111
119
  if len(saved_files) == 1:
112
120
  mimetype = "application/octet-binary"
113
121
  else:
@@ -132,30 +140,42 @@ def send_file(upload_folder, saved_files, new_file_name):
132
140
  return response
133
141
 
134
142
 
135
- def handle_exception(e):
136
- response = e.get_response()
143
+ def handle_exception(exception: HTTPException) -> flask.Response:
144
+ response = exception.get_response()
137
145
  response.data = flask.json.dumps(
138
146
  {
139
- "code": e.code,
140
- "name": e.name,
141
- "description": e.description,
147
+ "code": exception.code,
148
+ "name": exception.name,
149
+ "description": exception.description,
142
150
  }
143
151
  )
144
152
  response.content_type = "application/json"
145
153
  return response
146
154
 
147
155
 
148
- def create_unique_data_folder() -> tuple[str, str]:
156
+ def create_data_folder_from_id(data_id: str) -> str:
149
157
  base_data_folder = flask.current_app.config["DATA_FOLDER_PATH"]
150
- generated_id = str(uuid.uuid4()).replace("-", "")
151
- data_path = os.path.join(base_data_folder, generated_id)
158
+ data_path = os.path.join(base_data_folder, data_id)
152
159
  os.makedirs(data_path, exist_ok=True)
153
- return generated_id, data_path
160
+ return data_path
154
161
 
155
162
 
156
163
  def save_all_viewables_and_return_info(
157
- geode_object, data, generated_id, data_path, additional_files=None
158
- ):
164
+ geode_object: str,
165
+ data: Any,
166
+ input_file: str,
167
+ additional_files: list[str] | None = None,
168
+ ) -> dict[str, Any]:
169
+ if additional_files is None:
170
+ additional_files = []
171
+
172
+ data_entry = Data.create(
173
+ name=data.name(),
174
+ geode_object=geode_object,
175
+ input_file=input_file,
176
+ additional_files=additional_files,
177
+ )
178
+ data_path = create_data_folder_from_id(data_entry.id)
159
179
  saved_native_file_path = geode_functions.save(
160
180
  geode_object,
161
181
  data,
@@ -170,28 +190,42 @@ def save_all_viewables_and_return_info(
170
190
  )
171
191
  with open(saved_light_viewable_file_path, "rb") as f:
172
192
  binary_light_viewable = f.read()
193
+ data_entry.native_file_name = os.path.basename(saved_native_file_path[0])
194
+ data_entry.viewable_file_name = os.path.basename(saved_viewable_file_path)
195
+ data_entry.light_viewable = os.path.basename(saved_light_viewable_file_path)
196
+
197
+ database.session.commit()
173
198
 
174
199
  return {
175
- "name": data.name(),
176
- "native_file_name": os.path.basename(saved_native_file_path[0]),
177
- "viewable_file_name": os.path.basename(saved_viewable_file_path),
178
- "id": generated_id,
200
+ "name": data_entry.name,
201
+ "native_file_name": data_entry.native_file_name,
202
+ "viewable_file_name": data_entry.viewable_file_name,
203
+ "id": data_entry.id,
179
204
  "object_type": geode_functions.get_object_type(geode_object),
180
205
  "binary_light_viewable": binary_light_viewable.decode("utf-8"),
181
- "geode_object": geode_object,
182
- "input_files": additional_files or [],
206
+ "geode_object": data_entry.geode_object,
207
+ "input_files": data_entry.input_file,
208
+ "additional_files": data_entry.additional_files,
183
209
  }
184
210
 
185
211
 
186
- def generate_native_viewable_and_light_viewable_from_object(geode_object, data):
187
- generated_id, data_path = create_unique_data_folder()
188
- return save_all_viewables_and_return_info(
189
- geode_object, data, generated_id, data_path
190
- )
212
+ def generate_native_viewable_and_light_viewable_from_object(
213
+ geode_object: str, data: Any
214
+ ) -> dict[str, Any]:
215
+ return save_all_viewables_and_return_info(geode_object, data, input_file="")
191
216
 
192
217
 
193
- def generate_native_viewable_and_light_viewable_from_file(geode_object, input_filename):
194
- generated_id, data_path = create_unique_data_folder()
218
+ def generate_native_viewable_and_light_viewable_from_file(
219
+ geode_object: str, input_filename: str
220
+ ) -> dict[str, Any]:
221
+ temp_data_entry = Data.create(
222
+ name="temp",
223
+ geode_object=geode_object,
224
+ input_file=input_filename,
225
+ additional_files=[],
226
+ )
227
+
228
+ data_path = create_data_folder_from_id(temp_data_entry.id)
195
229
 
196
230
  full_input_filename = geode_functions.upload_file_path(input_filename)
197
231
  copied_full_path = os.path.join(
@@ -199,7 +233,7 @@ def generate_native_viewable_and_light_viewable_from_file(geode_object, input_fi
199
233
  )
200
234
  shutil.copy2(full_input_filename, copied_full_path)
201
235
 
202
- additional_files_copied = []
236
+ additional_files_copied: list[str] = []
203
237
  additional = geode_functions.additional_files(geode_object, full_input_filename)
204
238
  for additional_file in additional.mandatory_files + additional.optional_files:
205
239
  if additional_file.is_missing:
@@ -214,12 +248,14 @@ def generate_native_viewable_and_light_viewable_from_file(geode_object, input_fi
214
248
  shutil.copy2(source_path, dest_path)
215
249
  additional_files_copied.append(additional_file.filename)
216
250
 
217
- data = geode_functions.load_data(geode_object, generated_id, input_filename)
251
+ data = geode_functions.load_data(geode_object, temp_data_entry.id, input_filename)
252
+
253
+ database.session.delete(temp_data_entry)
254
+ database.session.flush()
218
255
 
219
256
  return save_all_viewables_and_return_info(
220
257
  geode_object,
221
258
  data,
222
- generated_id,
223
- data_path,
259
+ input_file=input_filename,
224
260
  additional_files=additional_files_copied,
225
261
  )
@@ -321,3 +321,15 @@ def test_create_point(client):
321
321
 
322
322
  # Test all params
323
323
  test_utils.test_route_wrong_params(client, route, get_full_data)
324
+
325
+
326
+ def test_database_uri_path(client):
327
+ app = client.application
328
+ with app.app_context():
329
+ base_dir = os.path.abspath(os.path.dirname(__file__))
330
+ expected_db_path = os.path.join(base_dir, "data", "project.db")
331
+ expected_uri = f"sqlite:///{expected_db_path}"
332
+
333
+ assert app.config["SQLALCHEMY_DATABASE_URI"] == expected_uri
334
+
335
+ assert os.path.exists(expected_db_path)
@@ -5,8 +5,11 @@ import os
5
5
  # Third party imports
6
6
  import flask
7
7
  import shutil
8
+ import uuid
8
9
 
9
10
  # Local application imports
11
+ from src.opengeodeweb_back.database import database
12
+ from src.opengeodeweb_back.data import Data
10
13
  from src.opengeodeweb_back import geode_functions, utils_functions
11
14
 
12
15
 
@@ -72,15 +75,15 @@ def test_handle_exception(client):
72
75
  assert type(data["code"]) is int
73
76
 
74
77
 
75
- def test_create_unique_data_folder(client):
78
+ def test_create_data_folder_from_id(client):
76
79
  app = client.application
77
80
  with app.app_context():
78
- generated_id, data_path = utils_functions.create_unique_data_folder()
79
- assert isinstance(generated_id, str)
80
- assert re.fullmatch(r"[0-9a-f]{32}", generated_id)
81
+ test_id = str(uuid.uuid4()).replace("-", "")
82
+ data_path = utils_functions.create_data_folder_from_id(test_id)
83
+ assert isinstance(data_path, str)
81
84
  assert os.path.exists(data_path)
82
85
  assert data_path.startswith(flask.current_app.config["DATA_FOLDER_PATH"])
83
- assert generated_id in data_path
86
+ assert test_id in data_path
84
87
  shutil.rmtree(data_path, ignore_errors=True)
85
88
  assert not os.path.exists(data_path)
86
89
 
@@ -88,24 +91,66 @@ def test_create_unique_data_folder(client):
88
91
  def test_save_all_viewables_and_return_info(client):
89
92
  app = client.application
90
93
  with app.app_context():
94
+ base_dir = os.path.abspath(os.path.dirname(__file__))
95
+ expected_db_path = os.path.join(base_dir, "data", "project.db")
96
+ expected_uri = f"sqlite:///{expected_db_path}"
97
+
98
+ assert app.config["SQLALCHEMY_DATABASE_URI"] == expected_uri
99
+ assert os.path.exists(expected_db_path)
100
+
91
101
  geode_object = "BRep"
92
102
  data = geode_functions.load(geode_object, "./tests/data/test.og_brep")
93
- generated_id, data_path = utils_functions.create_unique_data_folder()
103
+ input_file = "test.og_brep"
94
104
  additional_files = ["additional_file.txt"]
95
105
 
96
106
  result = utils_functions.save_all_viewables_and_return_info(
97
- geode_object, data, generated_id, data_path, additional_files
107
+ geode_object, data, input_file, additional_files
98
108
  )
99
109
 
100
- assert isinstance(result, dict)
101
- assert result["name"] == data.name()
102
- assert result["native_file_name"].startswith("native.")
103
- assert result["viewable_file_name"].endswith(".vtm")
104
- assert re.match(r"[0-9a-f]{32}", result["id"])
105
- assert isinstance(result["object_type"], str)
106
- assert isinstance(result["binary_light_viewable"], str)
107
- assert result["geode_object"] == geode_object
108
- assert result["input_files"] == additional_files
110
+ assert isinstance(result, dict)
111
+ assert result["name"] == data.name()
112
+ assert result["native_file_name"].startswith("native.")
113
+ assert result["viewable_file_name"].endswith(".vtm")
114
+ assert isinstance(result["id"], str)
115
+ assert len(result["id"]) == 32
116
+ assert re.match(r"[0-9a-f]{32}", result["id"])
117
+ assert isinstance(result["object_type"], str)
118
+ assert isinstance(result["binary_light_viewable"], str)
119
+ assert result["geode_object"] == geode_object
120
+ assert result["input_files"] == input_file
121
+
122
+ db_entry = database.session.get(Data, result["id"])
123
+ assert db_entry is not None
124
+ assert db_entry.name == data.name()
125
+ assert db_entry.native_file_name == result["native_file_name"]
126
+ assert db_entry.viewable_file_name == result["viewable_file_name"]
127
+ assert db_entry.geode_object == geode_object
128
+ assert db_entry.input_file == input_file
129
+ assert db_entry.additional_files == additional_files
130
+
131
+ expected_data_path = os.path.join(app.config["DATA_FOLDER_PATH"], result["id"])
132
+ assert os.path.exists(expected_data_path)
133
+
134
+
135
+ def test_save_all_viewables_commits_to_db_properly(client):
136
+ app = client.application
137
+ with app.app_context():
138
+ geode_object = "BRep"
139
+ data = geode_functions.load(geode_object, "./tests/data/test.og_brep")
140
+ input_file = "test.og_brep"
141
+ result = utils_functions.save_all_viewables_and_return_info(
142
+ geode_object, data, input_file
143
+ )
144
+ data_id = result["id"]
145
+ db_entry_before = database.session.get(Data, data_id)
146
+ assert db_entry_before is not None
147
+ assert db_entry_before.native_file_name == result["native_file_name"]
148
+ database.session.rollback()
149
+ db_entry_after = database.session.get(Data, data_id)
150
+ assert (
151
+ db_entry_after is not None
152
+ ), "database.session.commit() was not called - entry missing after rollback"
153
+ assert db_entry_after.native_file_name == result["native_file_name"]
109
154
 
110
155
 
111
156
  def test_generate_native_viewable_and_light_viewable_from_object(client):
@@ -130,7 +175,7 @@ def test_generate_native_viewable_and_light_viewable_from_object(client):
130
175
  assert re.match(r"[0-9a-f]{32}", result["id"])
131
176
  assert isinstance(result["object_type"], str)
132
177
  assert isinstance(result["binary_light_viewable"], str)
133
- assert result["input_files"] == []
178
+ assert result["input_files"] == ""
134
179
 
135
180
 
136
181
  def test_generate_native_viewable_and_light_viewable_from_file(client):
@@ -153,4 +198,4 @@ def test_generate_native_viewable_and_light_viewable_from_file(client):
153
198
  assert re.match(r"[0-9a-f]{32}", result["id"])
154
199
  assert isinstance(result["object_type"], str)
155
200
  assert isinstance(result["binary_light_viewable"], str)
156
- assert isinstance(result["input_files"], list)
201
+ assert isinstance(result["input_files"], str)