OpenGeodeWeb-Back 5.10.4__py3-none-any.whl → 5.14.1__py3-none-any.whl

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 (89) hide show
  1. opengeodeweb_back/app.py +20 -14
  2. opengeodeweb_back/geode_functions.py +44 -283
  3. opengeodeweb_back/geode_objects/__init__.py +61 -0
  4. opengeodeweb_back/geode_objects/geode_brep.py +102 -0
  5. opengeodeweb_back/geode_objects/geode_cross_section.py +75 -0
  6. opengeodeweb_back/geode_objects/geode_edged_curve2d.py +107 -0
  7. opengeodeweb_back/geode_objects/geode_edged_curve3d.py +107 -0
  8. opengeodeweb_back/geode_objects/geode_graph.py +76 -0
  9. opengeodeweb_back/geode_objects/geode_grid2d.py +30 -0
  10. opengeodeweb_back/geode_objects/geode_grid3d.py +30 -0
  11. opengeodeweb_back/geode_objects/geode_hybrid_solid3d.py +71 -0
  12. opengeodeweb_back/geode_objects/geode_implicit_cross_section.py +81 -0
  13. opengeodeweb_back/geode_objects/geode_implicit_structural_model.py +85 -0
  14. opengeodeweb_back/geode_objects/geode_light_regular_grid2d.py +72 -0
  15. opengeodeweb_back/geode_objects/geode_light_regular_grid3d.py +72 -0
  16. opengeodeweb_back/geode_objects/geode_mesh.py +19 -0
  17. opengeodeweb_back/geode_objects/geode_model.py +22 -0
  18. opengeodeweb_back/geode_objects/geode_object.py +78 -0
  19. opengeodeweb_back/geode_objects/geode_point_set2d.py +105 -0
  20. opengeodeweb_back/geode_objects/geode_point_set3d.py +105 -0
  21. opengeodeweb_back/geode_objects/geode_polygonal_surface2d.py +73 -0
  22. opengeodeweb_back/geode_objects/geode_polygonal_surface3d.py +73 -0
  23. opengeodeweb_back/geode_objects/geode_polyhedral_solid3d.py +73 -0
  24. opengeodeweb_back/geode_objects/geode_raster_image2d.py +83 -0
  25. opengeodeweb_back/geode_objects/geode_raster_image3d.py +83 -0
  26. opengeodeweb_back/geode_objects/geode_regular_grid2d.py +78 -0
  27. opengeodeweb_back/geode_objects/geode_regular_grid3d.py +78 -0
  28. opengeodeweb_back/geode_objects/geode_section.py +106 -0
  29. opengeodeweb_back/geode_objects/geode_solid_mesh3d.py +61 -0
  30. opengeodeweb_back/geode_objects/geode_structural_model.py +78 -0
  31. opengeodeweb_back/geode_objects/geode_surface_mesh2d.py +66 -0
  32. opengeodeweb_back/geode_objects/geode_surface_mesh3d.py +66 -0
  33. opengeodeweb_back/geode_objects/geode_tetrahedral_solid3d.py +73 -0
  34. opengeodeweb_back/geode_objects/geode_triangulated_surface2d.py +77 -0
  35. opengeodeweb_back/geode_objects/geode_triangulated_surface3d.py +77 -0
  36. opengeodeweb_back/geode_objects/geode_vertex_set.py +81 -0
  37. opengeodeweb_back/geode_objects/types.py +75 -0
  38. opengeodeweb_back/routes/blueprint_routes.py +346 -225
  39. opengeodeweb_back/routes/create/blueprint_create.py +121 -0
  40. opengeodeweb_back/routes/create/schemas/__init__.py +3 -0
  41. opengeodeweb_back/routes/create/schemas/create_aoi.json +45 -0
  42. opengeodeweb_back/routes/create/schemas/create_aoi.py +25 -0
  43. opengeodeweb_back/routes/create/schemas/create_point.json +29 -0
  44. opengeodeweb_back/routes/create/schemas/create_point.py +13 -0
  45. opengeodeweb_back/routes/create/schemas/create_voi.json +36 -0
  46. opengeodeweb_back/routes/create/schemas/create_voi.py +24 -0
  47. opengeodeweb_back/routes/models/blueprint_models.py +27 -27
  48. opengeodeweb_back/routes/models/schemas/__init__.py +2 -0
  49. opengeodeweb_back/routes/models/schemas/mesh_components.py +10 -0
  50. opengeodeweb_back/routes/models/schemas/vtm_component_indices.py +10 -0
  51. opengeodeweb_back/routes/schemas/__init__.py +17 -0
  52. opengeodeweb_back/routes/schemas/allowed_files.json +6 -8
  53. opengeodeweb_back/routes/schemas/allowed_files.py +10 -0
  54. opengeodeweb_back/routes/schemas/allowed_objects.json +1 -8
  55. opengeodeweb_back/routes/schemas/allowed_objects.py +10 -0
  56. opengeodeweb_back/routes/schemas/cell_attribute_names.json +13 -0
  57. opengeodeweb_back/routes/schemas/cell_attribute_names.py +10 -0
  58. opengeodeweb_back/routes/schemas/export_project.json +21 -0
  59. opengeodeweb_back/routes/schemas/export_project.py +12 -0
  60. opengeodeweb_back/routes/schemas/geode_objects_and_output_extensions.json +2 -2
  61. opengeodeweb_back/routes/schemas/geode_objects_and_output_extensions.py +11 -0
  62. opengeodeweb_back/routes/schemas/geographic_coordinate_systems.json +2 -2
  63. opengeodeweb_back/routes/schemas/geographic_coordinate_systems.py +10 -0
  64. opengeodeweb_back/routes/schemas/import_project.json +10 -0
  65. opengeodeweb_back/routes/schemas/import_project.py +10 -0
  66. opengeodeweb_back/routes/schemas/inspect_file.json +2 -2
  67. opengeodeweb_back/routes/schemas/inspect_file.py +11 -0
  68. opengeodeweb_back/routes/schemas/kill.py +10 -0
  69. opengeodeweb_back/routes/schemas/missing_files.json +2 -2
  70. opengeodeweb_back/routes/schemas/missing_files.py +11 -0
  71. opengeodeweb_back/routes/schemas/ping.py +10 -0
  72. opengeodeweb_back/routes/schemas/polygon_attribute_names.py +10 -0
  73. opengeodeweb_back/routes/schemas/polyhedron_attribute_names.py +10 -0
  74. opengeodeweb_back/routes/schemas/save_viewable_file.json +2 -2
  75. opengeodeweb_back/routes/schemas/save_viewable_file.py +11 -0
  76. opengeodeweb_back/routes/schemas/texture_coordinates.py +10 -0
  77. opengeodeweb_back/routes/schemas/upload_file.py +11 -0
  78. opengeodeweb_back/routes/schemas/vertex_attribute_names.py +10 -0
  79. opengeodeweb_back/test_utils.py +9 -3
  80. opengeodeweb_back/utils_functions.py +77 -74
  81. {opengeodeweb_back-5.10.4.dist-info → opengeodeweb_back-5.14.1.dist-info}/METADATA +9 -9
  82. opengeodeweb_back-5.14.1.dist-info/RECORD +98 -0
  83. opengeodeweb_back/geode_objects.py +0 -570
  84. opengeodeweb_back/routes/schemas/create_point.json +0 -29
  85. opengeodeweb_back-5.10.4.dist-info/RECORD +0 -33
  86. {opengeodeweb_back-5.10.4.dist-info → opengeodeweb_back-5.14.1.dist-info}/WHEEL +0 -0
  87. {opengeodeweb_back-5.10.4.dist-info → opengeodeweb_back-5.14.1.dist-info}/entry_points.txt +0 -0
  88. {opengeodeweb_back-5.10.4.dist-info → opengeodeweb_back-5.14.1.dist-info}/licenses/LICENSE +0 -0
  89. {opengeodeweb_back-5.10.4.dist-info → opengeodeweb_back-5.14.1.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,31 @@
1
1
  # Standard library imports
2
- import json
3
2
  import os
4
3
  import time
4
+ import shutil
5
+ from typing import Any
5
6
 
6
7
  # Third party imports
7
8
  import flask
8
- import opengeode
9
9
  import werkzeug
10
+ import zipfile
11
+ import opengeode_geosciences as og_geosciences
12
+ from opengeodeweb_microservice.schemas import get_schemas_dict
13
+ from opengeodeweb_microservice.database.data import Data
14
+ from opengeodeweb_microservice.database.connection import get_session
15
+ from opengeodeweb_microservice.database import connection
10
16
 
11
17
  # Local application imports
12
- from .. import geode_functions, utils_functions
13
-
14
18
  from .models import blueprint_models
19
+ from . import schemas
20
+ from opengeodeweb_back import geode_functions, utils_functions
21
+ from opengeodeweb_back.geode_objects import geode_objects
22
+ from opengeodeweb_back.geode_objects.types import geode_object_type
23
+ from opengeodeweb_back.geode_objects.geode_mesh import GeodeMesh
24
+ from opengeodeweb_back.geode_objects.geode_grid2d import GeodeGrid2D
25
+ from opengeodeweb_back.geode_objects.geode_grid3d import GeodeGrid3D
26
+ from opengeodeweb_back.geode_objects.geode_surface_mesh2d import GeodeSurfaceMesh2D
27
+ from opengeodeweb_back.geode_objects.geode_surface_mesh3d import GeodeSurfaceMesh3D
28
+ from opengeodeweb_back.geode_objects.geode_solid_mesh3d import GeodeSolidMesh3D
15
29
 
16
30
  routes = flask.Blueprint("routes", __name__, url_prefix="/opengeodeweb_back")
17
31
 
@@ -22,108 +36,87 @@ routes.register_blueprint(
22
36
  name=blueprint_models.routes.name,
23
37
  )
24
38
 
25
-
26
- schemas = os.path.join(os.path.dirname(__file__), "schemas")
27
-
28
- with open(
29
- os.path.join(schemas, "allowed_files.json"),
30
- "r",
31
- ) as file:
32
- allowed_files_json = json.load(file)
39
+ schemas_dict = get_schemas_dict(os.path.join(os.path.dirname(__file__), "schemas"))
33
40
 
34
41
 
35
42
  @routes.route(
36
- allowed_files_json["route"],
37
- methods=allowed_files_json["methods"],
43
+ schemas_dict["allowed_files"]["route"],
44
+ methods=schemas_dict["allowed_files"]["methods"],
38
45
  )
39
- def allowed_files():
40
- utils_functions.validate_request(flask.request, allowed_files_json)
41
- extensions = geode_functions.list_input_extensions(
42
- flask.request.get_json()["supported_feature"]
43
- )
44
- return flask.make_response({"extensions": extensions}, 200)
45
-
46
-
47
- with open(
48
- os.path.join(schemas, "upload_file.json"),
49
- "r",
50
- ) as file:
51
- upload_file_json = json.load(file)
46
+ def allowed_files() -> flask.Response:
47
+ utils_functions.validate_request(flask.request, schemas_dict["allowed_files"])
48
+ extensions: set[str] = set()
49
+ for geode_object in geode_objects.values():
50
+ for extension in geode_object.input_extensions():
51
+ extensions.add(extension)
52
+ return flask.make_response({"extensions": list(extensions)}, 200)
52
53
 
53
54
 
54
55
  @routes.route(
55
- upload_file_json["route"],
56
- methods=upload_file_json["methods"],
56
+ schemas_dict["upload_file"]["route"],
57
+ methods=schemas_dict["upload_file"]["methods"],
57
58
  )
58
- def upload_file():
59
- if flask.request.method == "OPTIONS":
60
- return flask.make_response({}, 200)
61
-
59
+ def upload_file() -> flask.Response:
62
60
  UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"]
63
61
  if not os.path.exists(UPLOAD_FOLDER):
64
- os.mkdir(UPLOAD_FOLDER)
62
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
63
+
65
64
  file = flask.request.files["file"]
66
65
  filename = werkzeug.utils.secure_filename(os.path.basename(file.filename))
67
66
  file.save(os.path.join(UPLOAD_FOLDER, filename))
68
67
  return flask.make_response({"message": "File uploaded"}, 201)
69
68
 
70
69
 
71
- with open(
72
- os.path.join(schemas, "allowed_objects.json"),
73
- "r",
74
- ) as file:
75
- allowed_objects_json = json.load(file)
76
-
77
-
78
70
  @routes.route(
79
- allowed_objects_json["route"],
80
- methods=allowed_objects_json["methods"],
71
+ schemas_dict["allowed_objects"]["route"],
72
+ methods=schemas_dict["allowed_objects"]["methods"],
81
73
  )
82
- def allowed_objects():
83
- if flask.request.method == "OPTIONS":
84
- return flask.make_response({}, 200)
85
-
86
- utils_functions.validate_request(flask.request, allowed_objects_json)
87
- file_absolute_path = geode_functions.upload_file_path(
88
- flask.request.get_json()["filename"]
74
+ def allowed_objects() -> flask.Response:
75
+ json_data = utils_functions.validate_request(
76
+ flask.request, schemas_dict["allowed_objects"]
89
77
  )
90
- allowed_objects = geode_functions.list_geode_objects(
91
- file_absolute_path, flask.request.get_json()["supported_feature"]
78
+ params = schemas.AllowedObjects.from_dict(json_data)
79
+ file_absolute_path = geode_functions.upload_file_path(params.filename)
80
+ file_extension = utils_functions.extension_from_filename(
81
+ os.path.basename(file_absolute_path)
92
82
  )
83
+ allowed_objects = {}
84
+ for geode_object_type, geode_object in geode_objects.items():
85
+ if file_extension not in geode_object.input_extensions():
86
+ continue
87
+ loadability_score = geode_object.is_loadable(file_absolute_path)
88
+ priority_score = geode_object.object_priority(file_absolute_path)
89
+ allowed_objects[geode_object_type] = {
90
+ "is_loadable": loadability_score.value(),
91
+ "object_priority": priority_score,
92
+ }
93
93
  return flask.make_response({"allowed_objects": allowed_objects}, 200)
94
94
 
95
95
 
96
- with open(
97
- os.path.join(schemas, "missing_files.json"),
98
- "r",
99
- ) as file:
100
- missing_files_json = json.load(file)
101
-
102
-
103
96
  @routes.route(
104
- missing_files_json["route"],
105
- methods=missing_files_json["methods"],
97
+ schemas_dict["missing_files"]["route"],
98
+ methods=schemas_dict["missing_files"]["methods"],
106
99
  )
107
- def missing_files():
108
- utils_functions.validate_request(flask.request, missing_files_json)
109
- file_path = geode_functions.upload_file_path(flask.request.get_json()["filename"])
110
-
111
- additional_files = geode_functions.additional_files(
112
- flask.request.get_json()["input_geode_object"],
100
+ def missing_files() -> flask.Response:
101
+ json_data = utils_functions.validate_request(
102
+ flask.request, schemas_dict["missing_files"]
103
+ )
104
+ params = schemas.MissingFiles.from_dict(json_data)
105
+ file_path = geode_functions.upload_file_path(params.filename)
106
+ geode_object = geode_functions.geode_object_from_string(params.geode_object_type)
107
+ additional_files = geode_object.additional_files(
113
108
  file_path,
114
109
  )
115
-
116
110
  has_missing_files = any(
117
111
  file.is_missing
118
112
  for file in additional_files.mandatory_files + additional_files.optional_files
119
113
  )
120
-
121
114
  mandatory_files = [
122
115
  os.path.basename(file.filename)
123
116
  for file in additional_files.mandatory_files
124
117
  if file.is_missing
125
118
  ]
126
- additional_files = [
119
+ additional_files_array = [
127
120
  os.path.basename(file.filename)
128
121
  for file in additional_files.optional_files
129
122
  if file.is_missing
@@ -133,27 +126,26 @@ def missing_files():
133
126
  {
134
127
  "has_missing_files": has_missing_files,
135
128
  "mandatory_files": mandatory_files,
136
- "additional_files": additional_files,
129
+ "additional_files": additional_files_array,
137
130
  },
138
131
  200,
139
132
  )
140
133
 
141
134
 
142
- with open(
143
- os.path.join(schemas, "geographic_coordinate_systems.json"),
144
- "r",
145
- ) as file:
146
- geographic_coordinate_systems_json = json.load(file)
147
-
148
-
149
135
  @routes.route(
150
- geographic_coordinate_systems_json["route"],
151
- methods=geographic_coordinate_systems_json["methods"],
136
+ schemas_dict["geographic_coordinate_systems"]["route"],
137
+ methods=schemas_dict["geographic_coordinate_systems"]["methods"],
152
138
  )
153
- def crs_converter_geographic_coordinate_systems():
154
- utils_functions.validate_request(flask.request, geographic_coordinate_systems_json)
155
- infos = geode_functions.geographic_coordinate_systems(
156
- flask.request.get_json()["input_geode_object"]
139
+ def crs_converter_geographic_coordinate_systems() -> flask.Response:
140
+ json_data = utils_functions.validate_request(
141
+ flask.request, schemas_dict["geographic_coordinate_systems"]
142
+ )
143
+ params = schemas.GeographicCoordinateSystems.from_dict(json_data)
144
+ geode_object = geode_functions.geode_object_from_string(params.geode_object_type)
145
+ infos = (
146
+ og_geosciences.GeographicCoordinateSystem3D.geographic_coordinate_systems()
147
+ if geode_object.is_3D()
148
+ else og_geosciences.GeographicCoordinateSystem2D.geographic_coordinate_systems()
157
149
  )
158
150
  crs_list = []
159
151
  for info in infos:
@@ -162,59 +154,70 @@ def crs_converter_geographic_coordinate_systems():
162
154
  crs["code"] = info.code
163
155
  crs["authority"] = info.authority
164
156
  crs_list.append(crs)
165
-
166
157
  return flask.make_response({"crs_list": crs_list}, 200)
167
158
 
168
159
 
169
- with open(
170
- os.path.join(schemas, "inspect_file.json"),
171
- "r",
172
- ) as file:
173
- inspect_file_json = json.load(file)
174
-
175
-
176
160
  @routes.route(
177
- inspect_file_json["route"],
178
- methods=inspect_file_json["methods"],
161
+ schemas_dict["inspect_file"]["route"],
162
+ methods=schemas_dict["inspect_file"]["methods"],
179
163
  )
180
- def inspect_file():
181
- utils_functions.validate_request(flask.request, inspect_file_json)
182
-
183
- file_path = geode_functions.upload_file_path(flask.request.get_json()["filename"])
184
- data = geode_functions.load(
185
- flask.request.get_json()["input_geode_object"], file_path
186
- )
187
- class_inspector = geode_functions.inspect(
188
- flask.request.get_json()["input_geode_object"], data
164
+ def inspect_file() -> flask.Response:
165
+ json_data = utils_functions.validate_request(
166
+ flask.request, schemas_dict["inspect_file"]
189
167
  )
190
- inspection_result = geode_functions.get_inspector_children(class_inspector)
168
+ params = schemas.InspectFile.from_dict(json_data)
169
+ file_path = geode_functions.upload_file_path(params.filename)
170
+ geode_object = geode_functions.geode_object_from_string(
171
+ params.geode_object_type
172
+ ).load(file_path)
173
+ inspection_data = geode_object.inspect()
174
+ inspection_result = extract_inspector_result(inspection_data)
191
175
  return flask.make_response({"inspection_result": inspection_result}, 200)
192
176
 
193
177
 
194
- with open(
195
- os.path.join(schemas, "geode_objects_and_output_extensions.json"),
196
- "r",
197
- ) as file:
198
- geode_objects_and_output_extensions_json = json.load(file)
178
+ def extract_inspector_result(inspection_data: Any) -> object:
179
+ new_object = {}
180
+
181
+ if hasattr(inspection_data, "inspection_type"):
182
+ new_object["title"] = inspection_data.inspection_type()
183
+ new_object["nb_issues"] = 0
184
+ new_object["children"] = []
185
+ for child in dir(inspection_data):
186
+ if child.startswith("__") or child in [
187
+ "inspection_type",
188
+ "string",
189
+ ]:
190
+ continue
191
+ child_instance = getattr(inspection_data, child)
192
+ child_object = extract_inspector_result(child_instance)
193
+ new_object["children"].append(child_object)
194
+ if hasattr(child_object, "nb_issues"):
195
+ new_object["nb_issues"] += child_object.nb_issues()
196
+ else:
197
+ new_object["title"] = inspection_data.description()
198
+ nb_issues = inspection_data.nb_issues()
199
+ new_object["nb_issues"] = nb_issues
200
+ if nb_issues > 0:
201
+ issues = inspection_data.string().split("\n")
202
+ new_object["issues"] = issues
203
+ return new_object
199
204
 
200
205
 
201
206
  @routes.route(
202
- geode_objects_and_output_extensions_json["route"],
203
- methods=geode_objects_and_output_extensions_json["methods"],
207
+ schemas_dict["geode_objects_and_output_extensions"]["route"],
208
+ methods=schemas_dict["geode_objects_and_output_extensions"]["methods"],
204
209
  )
205
- def geode_objects_and_output_extensions():
206
- utils_functions.validate_request(
207
- flask.request, geode_objects_and_output_extensions_json
208
- )
209
- file_path = geode_functions.upload_file_path(flask.request.get_json()["filename"])
210
- data = geode_functions.load(
211
- flask.request.get_json()["input_geode_object"],
212
- file_path,
210
+ def geode_objects_and_output_extensions() -> flask.Response:
211
+ json_data = utils_functions.validate_request(
212
+ flask.request, schemas_dict["geode_objects_and_output_extensions"]
213
213
  )
214
+ params = schemas.GeodeObjectsAndOutputExtensions.from_dict(json_data)
215
+ file_path = geode_functions.upload_file_path(params.filename)
216
+ geode_object = geode_functions.geode_object_from_string(
217
+ params.geode_object_type
218
+ ).load(file_path)
214
219
  geode_objects_and_output_extensions = (
215
- geode_functions.geode_objects_output_extensions(
216
- flask.request.get_json()["input_geode_object"], data
217
- )
220
+ geode_functions.geode_object_output_extensions(geode_object)
218
221
  )
219
222
  return flask.make_response(
220
223
  {"geode_objects_and_output_extensions": geode_objects_and_output_extensions},
@@ -222,82 +225,53 @@ def geode_objects_and_output_extensions():
222
225
  )
223
226
 
224
227
 
225
- with open(
226
- os.path.join(schemas, "save_viewable_file.json"),
227
- "r",
228
- ) as file:
229
- save_viewable_file_json = json.load(file)
230
-
231
-
232
228
  @routes.route(
233
- save_viewable_file_json["route"],
234
- methods=save_viewable_file_json["methods"],
229
+ schemas_dict["save_viewable_file"]["route"],
230
+ methods=schemas_dict["save_viewable_file"]["methods"],
235
231
  )
236
- def save_viewable_file():
237
- utils_functions.validate_request(flask.request, save_viewable_file_json)
238
- return flask.make_response(
239
- utils_functions.generate_native_viewable_and_light_viewable_from_file(
240
- geode_object=flask.request.get_json()["input_geode_object"],
241
- input_filename=flask.request.get_json()["filename"],
242
- ),
243
- 200,
232
+ def save_viewable_file() -> flask.Response:
233
+ json_data = utils_functions.validate_request(
234
+ flask.request, schemas_dict["save_viewable_file"]
244
235
  )
245
-
246
-
247
- with open(os.path.join(schemas, "create_point.json"), "r") as file:
248
- create_point_json = json.load(file)
249
-
250
-
251
- @routes.route(create_point_json["route"], methods=create_point_json["methods"])
252
- def create_point():
253
- utils_functions.validate_request(flask.request, create_point_json)
254
- title = flask.request.get_json()["title"]
255
- x = flask.request.get_json()["x"]
256
- y = flask.request.get_json()["y"]
257
- z = flask.request.get_json()["z"]
258
- class_ = geode_functions.geode_object_class("PointSet3D")
259
- PointSet3D = class_.create()
260
- builder = geode_functions.create_builder("PointSet3D", PointSet3D)
261
- builder.create_point(opengeode.Point3D([x, y, z]))
262
- builder.set_name(title)
236
+ params = schemas.SaveViewableFile.from_dict(json_data)
263
237
  return flask.make_response(
264
- utils_functions.generate_native_viewable_and_light_viewable_from_object(
265
- "PointSet3D", PointSet3D
238
+ utils_functions.generate_native_viewable_and_light_viewable_from_file(
239
+ geode_object_type=geode_object_type(params.geode_object_type),
240
+ input_file=params.filename,
266
241
  ),
267
242
  200,
268
243
  )
269
244
 
270
245
 
271
- with open(os.path.join(schemas, "texture_coordinates.json"), "r") as file:
272
- texture_coordinates_json = json.load(file)
273
-
274
-
275
246
  @routes.route(
276
- texture_coordinates_json["route"],
277
- methods=texture_coordinates_json["methods"],
247
+ schemas_dict["texture_coordinates"]["route"],
248
+ methods=schemas_dict["texture_coordinates"]["methods"],
278
249
  )
279
- def texture_coordinates():
280
- utils_functions.validate_request(flask.request, texture_coordinates_json)
281
- data = geode_functions.load_data(flask.request.get_json().get("id"))
282
- texture_coordinates = data.texture_manager().texture_names()
250
+ def texture_coordinates() -> flask.Response:
251
+ json_data = utils_functions.validate_request(
252
+ flask.request, schemas_dict["texture_coordinates"]
253
+ )
254
+ params = schemas.TextureCoordinates.from_dict(json_data)
255
+ geode_object = geode_functions.load_geode_object(params.id)
256
+ if not isinstance(geode_object, GeodeSurfaceMesh2D | GeodeSurfaceMesh3D):
257
+ flask.abort(400, f"{params.id} is not a GeodeSurfaceMesh")
258
+ texture_coordinates = geode_object.texture_manager().texture_names()
283
259
  return flask.make_response({"texture_coordinates": texture_coordinates}, 200)
284
260
 
285
261
 
286
- with open(
287
- os.path.join(schemas, "vertex_attribute_names.json"),
288
- "r",
289
- ) as file:
290
- vertex_attribute_names_json = json.load(file)
291
-
292
-
293
262
  @routes.route(
294
- vertex_attribute_names_json["route"],
295
- methods=vertex_attribute_names_json["methods"],
263
+ schemas_dict["vertex_attribute_names"]["route"],
264
+ methods=schemas_dict["vertex_attribute_names"]["methods"],
296
265
  )
297
- def vertex_attribute_names():
298
- utils_functions.validate_request(flask.request, vertex_attribute_names_json)
299
- data = geode_functions.load_data(flask.request.get_json().get("id"))
300
- vertex_attribute_names = data.vertex_attribute_manager().attribute_names()
266
+ def vertex_attribute_names() -> flask.Response:
267
+ json_data = utils_functions.validate_request(
268
+ flask.request, schemas_dict["vertex_attribute_names"]
269
+ )
270
+ params = schemas.VertexAttributeNames.from_dict(json_data)
271
+ geode_object = geode_functions.load_geode_object(params.id)
272
+ if not isinstance(geode_object, GeodeMesh):
273
+ flask.abort(400, f"{params.id} is not a GeodeMesh")
274
+ vertex_attribute_names = geode_object.vertex_attribute_manager().attribute_names()
301
275
  return flask.make_response(
302
276
  {
303
277
  "vertex_attribute_names": vertex_attribute_names,
@@ -306,21 +280,40 @@ def vertex_attribute_names():
306
280
  )
307
281
 
308
282
 
309
- with open(
310
- os.path.join(schemas, "polygon_attribute_names.json"),
311
- "r",
312
- ) as file:
313
- polygon_attribute_names_json = json.load(file)
283
+ @routes.route(
284
+ schemas_dict["cell_attribute_names"]["route"],
285
+ methods=schemas_dict["cell_attribute_names"]["methods"],
286
+ )
287
+ def cell_attribute_names() -> flask.Response:
288
+ json_data = utils_functions.validate_request(
289
+ flask.request, schemas_dict["cell_attribute_names"]
290
+ )
291
+ params = schemas.PolygonAttributeNames.from_dict(json_data)
292
+ geode_object = geode_functions.load_geode_object(params.id)
293
+ if not isinstance(geode_object, GeodeGrid2D | GeodeGrid3D):
294
+ flask.abort(400, f"{params.id} is not a GeodeGrid")
295
+ cell_attribute_names = geode_object.cell_attribute_manager().attribute_names()
296
+ return flask.make_response(
297
+ {
298
+ "cell_attribute_names": cell_attribute_names,
299
+ },
300
+ 200,
301
+ )
314
302
 
315
303
 
316
304
  @routes.route(
317
- polygon_attribute_names_json["route"],
318
- methods=polygon_attribute_names_json["methods"],
305
+ schemas_dict["polygon_attribute_names"]["route"],
306
+ methods=schemas_dict["polygon_attribute_names"]["methods"],
319
307
  )
320
- def polygon_attribute_names():
321
- utils_functions.validate_request(flask.request, polygon_attribute_names_json)
322
- data = geode_functions.load_data(flask.request.get_json().get("id"))
323
- polygon_attribute_names = data.polygon_attribute_manager().attribute_names()
308
+ def polygon_attribute_names() -> flask.Response:
309
+ json_data = utils_functions.validate_request(
310
+ flask.request, schemas_dict["polygon_attribute_names"]
311
+ )
312
+ params = schemas.PolygonAttributeNames.from_dict(json_data)
313
+ geode_object = geode_functions.load_geode_object(params.id)
314
+ if not isinstance(geode_object, GeodeSurfaceMesh2D | GeodeSurfaceMesh3D):
315
+ flask.abort(400, f"{params.id} is not a GeodeSurfaceMesh")
316
+ polygon_attribute_names = geode_object.polygon_attribute_manager().attribute_names()
324
317
  return flask.make_response(
325
318
  {
326
319
  "polygon_attribute_names": polygon_attribute_names,
@@ -329,21 +322,21 @@ def polygon_attribute_names():
329
322
  )
330
323
 
331
324
 
332
- with open(
333
- os.path.join(schemas, "polyhedron_attribute_names.json"),
334
- "r",
335
- ) as file:
336
- polyhedron_attribute_names_json = json.load(file)
337
-
338
-
339
325
  @routes.route(
340
- polyhedron_attribute_names_json["route"],
341
- methods=polyhedron_attribute_names_json["methods"],
326
+ schemas_dict["polyhedron_attribute_names"]["route"],
327
+ methods=schemas_dict["polyhedron_attribute_names"]["methods"],
342
328
  )
343
- def polyhedron_attribute_names():
344
- utils_functions.validate_request(flask.request, polyhedron_attribute_names_json)
345
- data = geode_functions.load_data(flask.request.get_json().get("id"))
346
- polyhedron_attribute_names = data.polyhedron_attribute_manager().attribute_names()
329
+ def polyhedron_attribute_names() -> flask.Response:
330
+ json_data = utils_functions.validate_request(
331
+ flask.request, schemas_dict["polyhedron_attribute_names"]
332
+ )
333
+ params = schemas.PolyhedronAttributeNames.from_dict(json_data)
334
+ geode_object = geode_functions.load_geode_object(params.id)
335
+ if not isinstance(geode_object, GeodeSolidMesh3D):
336
+ flask.abort(400, f"{params.id} is not a GeodeSolidMesh")
337
+ polyhedron_attribute_names = (
338
+ geode_object.polyhedron_attribute_manager().attribute_names()
339
+ )
347
340
  return flask.make_response(
348
341
  {
349
342
  "polyhedron_attribute_names": polyhedron_attribute_names,
@@ -352,32 +345,160 @@ def polyhedron_attribute_names():
352
345
  )
353
346
 
354
347
 
355
- with open(
356
- os.path.join(schemas, "ping.json"),
357
- "r",
358
- ) as file:
359
- ping_json = json.load(file)
360
-
361
-
362
348
  @routes.route(
363
- ping_json["route"],
364
- methods=ping_json["methods"],
349
+ schemas_dict["ping"]["route"],
350
+ methods=schemas_dict["ping"]["methods"],
365
351
  )
366
- def ping():
367
- utils_functions.validate_request(flask.request, ping_json)
352
+ def ping() -> flask.Response:
353
+ utils_functions.validate_request(flask.request, schemas_dict["ping"])
368
354
  flask.current_app.config.update(LAST_PING_TIME=time.time())
369
355
  return flask.make_response({"message": "Flask server is running"}, 200)
370
356
 
371
357
 
372
- with open(
373
- os.path.join(schemas, "kill.json"),
374
- "r",
375
- ) as file:
376
- kill_json = json.load(file)
377
-
378
-
379
- @routes.route(kill_json["route"], methods=kill_json["methods"])
358
+ @routes.route(schemas_dict["kill"]["route"], methods=schemas_dict["kill"]["methods"])
380
359
  def kill() -> flask.Response:
381
360
  print("Manual server kill, shutting down...", flush=True)
382
361
  os._exit(0)
383
362
  return flask.make_response({"message": "Flask server is dead"}, 200)
363
+
364
+
365
+ @routes.route(
366
+ schemas_dict["export_project"]["route"],
367
+ methods=schemas_dict["export_project"]["methods"],
368
+ )
369
+ def export_project() -> flask.Response:
370
+ json_data = utils_functions.validate_request(
371
+ flask.request, schemas_dict["export_project"]
372
+ )
373
+ params = schemas.ExportProject.from_dict(json_data)
374
+
375
+ project_folder: str = flask.current_app.config["DATA_FOLDER_PATH"]
376
+ os.makedirs(project_folder, exist_ok=True)
377
+
378
+ filename: str = werkzeug.utils.secure_filename(os.path.basename(params.filename))
379
+ if not filename.lower().endswith(".vease"):
380
+ flask.abort(400, "Requested filename must end with .vease")
381
+ export_vease_path = os.path.join(project_folder, filename)
382
+
383
+ with get_session() as session:
384
+ rows = session.query(Data.id, Data.input_file, Data.additional_files).all()
385
+
386
+ with zipfile.ZipFile(
387
+ export_vease_path, "w", compression=zipfile.ZIP_DEFLATED
388
+ ) as zip_file:
389
+ database_root_path = os.path.join(project_folder, "project.db")
390
+ if os.path.isfile(database_root_path):
391
+ zip_file.write(database_root_path, "project.db")
392
+
393
+ for data_id, input_file, additional_files in rows:
394
+ base_dir = os.path.join(project_folder, data_id)
395
+
396
+ input_path = os.path.join(base_dir, str(input_file))
397
+ if os.path.isfile(input_path):
398
+ zip_file.write(input_path, os.path.join(data_id, str(input_file)))
399
+
400
+ for relative_path in (
401
+ additional_files if isinstance(additional_files, list) else []
402
+ ):
403
+ additional_path = os.path.join(base_dir, relative_path)
404
+ if os.path.isfile(additional_path):
405
+ zip_file.write(
406
+ additional_path, os.path.join(data_id, relative_path)
407
+ )
408
+
409
+ zip_file.writestr("snapshot.json", flask.json.dumps(params.snapshot))
410
+
411
+ return utils_functions.send_file(project_folder, [export_vease_path], filename)
412
+
413
+
414
+ @routes.route(
415
+ schemas_dict["import_project"]["route"],
416
+ methods=schemas_dict["import_project"]["methods"],
417
+ )
418
+ def import_project() -> flask.Response:
419
+ utils_functions.validate_request(flask.request, schemas_dict["import_project"])
420
+ if "file" not in flask.request.files:
421
+ flask.abort(400, "No .vease file provided under 'file'")
422
+ zip_file = flask.request.files["file"]
423
+ assert zip_file.filename is not None
424
+ filename = werkzeug.utils.secure_filename(os.path.basename(zip_file.filename))
425
+ if not filename.lower().endswith(".vease"):
426
+ flask.abort(400, "Uploaded file must be a .vease")
427
+
428
+ data_folder_path: str = flask.current_app.config["DATA_FOLDER_PATH"]
429
+
430
+ # 423 Locked bypass : remove stopped requests
431
+ if connection.scoped_session_registry:
432
+ connection.scoped_session_registry.remove()
433
+ if connection.engine:
434
+ connection.engine.dispose()
435
+ connection.engine = connection.session_factory = (
436
+ connection.scoped_session_registry
437
+ ) = None
438
+
439
+ try:
440
+ if os.path.exists(data_folder_path):
441
+ shutil.rmtree(data_folder_path)
442
+ os.makedirs(data_folder_path, exist_ok=True)
443
+ except PermissionError:
444
+ flask.abort(423, "Project files are locked; cannot overwrite")
445
+
446
+ zip_file.stream.seek(0)
447
+ with zipfile.ZipFile(zip_file.stream) as zip_archive:
448
+ project_folder = os.path.abspath(data_folder_path)
449
+ for member in zip_archive.namelist():
450
+ target = os.path.abspath(
451
+ os.path.normpath(os.path.join(project_folder, member))
452
+ )
453
+ if not (
454
+ target == project_folder or target.startswith(project_folder + os.sep)
455
+ ):
456
+ flask.abort(400, "Vease file contains unsafe paths")
457
+ zip_archive.extractall(project_folder)
458
+
459
+ database_root_path = os.path.join(project_folder, "project.db")
460
+ if not os.path.isfile(database_root_path):
461
+ flask.abort(400, "Missing project.db at project root")
462
+
463
+ connection.init_database(database_root_path, create_tables=False)
464
+
465
+ try:
466
+ with get_session() as session:
467
+ rows = session.query(Data).all()
468
+ except Exception:
469
+ connection.init_database(database_root_path, create_tables=True)
470
+ with get_session() as session:
471
+ rows = session.query(Data).all()
472
+
473
+ with get_session() as session:
474
+ for data in rows:
475
+ data_path = geode_functions.data_file_path(data.id)
476
+ viewable_name = data.viewable_file
477
+ if viewable_name:
478
+ vpath = geode_functions.data_file_path(data.id, viewable_name)
479
+ if os.path.isfile(vpath):
480
+ continue
481
+
482
+ input_file = str(data.input_file or "")
483
+ if not input_file:
484
+ continue
485
+
486
+ input_full = geode_functions.data_file_path(data.id, input_file)
487
+ if not os.path.isfile(input_full):
488
+ continue
489
+
490
+ geode_object = geode_functions.geode_object_from_string(
491
+ data.geode_object
492
+ ).load(input_full)
493
+ utils_functions.save_all_viewables_and_return_info(
494
+ geode_object, data, data_path
495
+ )
496
+ session.commit()
497
+
498
+ snapshot = {}
499
+ try:
500
+ raw = zip_archive.read("snapshot.json").decode("utf-8")
501
+ snapshot = flask.json.loads(raw)
502
+ except KeyError:
503
+ snapshot = {}
504
+ return flask.make_response({"snapshot": snapshot}, 200)