OpenGeodeWeb-Back 6.13.0rc2__tar.gz → 6.13.0rc4__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 (114) hide show
  1. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/PKG-INFO +1 -1
  2. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/pyproject.toml +1 -1
  3. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/OpenGeodeWeb_Back.egg-info/PKG-INFO +1 -1
  4. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/blueprint_routes.py +75 -31
  5. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/tests/test_routes.py +100 -0
  6. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/LICENSE +0 -0
  7. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/README.md +0 -0
  8. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/requirements.txt +0 -0
  9. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/setup.cfg +0 -0
  10. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/OpenGeodeWeb_Back.egg-info/SOURCES.txt +0 -0
  11. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/OpenGeodeWeb_Back.egg-info/dependency_links.txt +0 -0
  12. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/OpenGeodeWeb_Back.egg-info/entry_points.txt +0 -0
  13. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/OpenGeodeWeb_Back.egg-info/requires.txt +0 -0
  14. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/OpenGeodeWeb_Back.egg-info/top_level.txt +0 -0
  15. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/__init__.py +0 -0
  16. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/app.py +0 -0
  17. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/app_config.py +0 -0
  18. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_functions.py +0 -0
  19. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/__init__.py +0 -0
  20. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_brep.py +0 -0
  21. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_cross_section.py +0 -0
  22. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_edged_curve2d.py +0 -0
  23. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_edged_curve3d.py +0 -0
  24. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_graph.py +0 -0
  25. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_grid2d.py +0 -0
  26. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_grid3d.py +0 -0
  27. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_horizon_stack3d.py +0 -0
  28. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_hybrid_solid3d.py +0 -0
  29. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_implicit_cross_section.py +0 -0
  30. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_implicit_structural_model.py +0 -0
  31. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_light_regular_grid2d.py +0 -0
  32. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_light_regular_grid3d.py +0 -0
  33. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_mesh.py +0 -0
  34. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_model.py +0 -0
  35. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_object.py +0 -0
  36. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_point_set2d.py +0 -0
  37. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_point_set3d.py +0 -0
  38. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_polygonal_surface2d.py +0 -0
  39. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_polygonal_surface3d.py +0 -0
  40. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_polyhedral_solid3d.py +0 -0
  41. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_raster_image2d.py +0 -0
  42. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_raster_image3d.py +0 -0
  43. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_regular_grid2d.py +0 -0
  44. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_regular_grid3d.py +0 -0
  45. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_section.py +0 -0
  46. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_solid_mesh3d.py +0 -0
  47. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_structural_model.py +0 -0
  48. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_surface_mesh2d.py +0 -0
  49. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_surface_mesh3d.py +0 -0
  50. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_tetrahedral_solid3d.py +0 -0
  51. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_triangulated_surface2d.py +0 -0
  52. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_triangulated_surface3d.py +0 -0
  53. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/geode_objects/geode_vertex_set.py +0 -0
  54. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/py.typed +0 -0
  55. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/blueprint_create.py +0 -0
  56. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/schemas/__init__.py +0 -0
  57. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/schemas/edged_curve.json +0 -0
  58. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/schemas/edged_curve.py +0 -0
  59. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/schemas/point_set.json +0 -0
  60. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/schemas/point_set.py +0 -0
  61. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.json +0 -0
  62. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/create/schemas/polygonal_surface.py +0 -0
  63. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/__init__.py +0 -0
  64. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/allowed_files.json +0 -0
  65. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/allowed_files.py +0 -0
  66. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/allowed_objects.json +0 -0
  67. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/allowed_objects.py +0 -0
  68. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/cell_attribute_names.json +0 -0
  69. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/cell_attribute_names.py +0 -0
  70. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/edge_attribute_names.json +0 -0
  71. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/edge_attribute_names.py +0 -0
  72. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/export_project.json +0 -0
  73. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/export_project.py +0 -0
  74. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/geode_object_inheritance.json +0 -0
  75. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/geode_object_inheritance.py +0 -0
  76. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/geode_objects_and_output_extensions.json +0 -0
  77. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/geode_objects_and_output_extensions.py +0 -0
  78. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.json +0 -0
  79. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/geographic_coordinate_systems.py +0 -0
  80. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/import_project.json +0 -0
  81. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/import_project.py +0 -0
  82. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/kill.json +0 -0
  83. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/kill.py +0 -0
  84. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/missing_files.json +0 -0
  85. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/missing_files.py +0 -0
  86. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.json +0 -0
  87. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.py +0 -0
  88. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.json +0 -0
  89. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.py +0 -0
  90. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.json +0 -0
  91. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.py +0 -0
  92. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.json +0 -0
  93. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.py +0 -0
  94. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/ping.json +0 -0
  95. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/ping.py +0 -0
  96. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json +0 -0
  97. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.py +0 -0
  98. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/polyhedron_attribute_names.json +0 -0
  99. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/polyhedron_attribute_names.py +0 -0
  100. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/save_viewable_file.json +0 -0
  101. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/save_viewable_file.py +0 -0
  102. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/texture_coordinates.json +0 -0
  103. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/texture_coordinates.py +0 -0
  104. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/upload_file.json +0 -0
  105. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/upload_file.py +0 -0
  106. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/validate.json +0 -0
  107. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/validate.py +0 -0
  108. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json +0 -0
  109. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.py +0 -0
  110. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/test_utils.py +0 -0
  111. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/src/opengeodeweb_back/utils_functions.py +0 -0
  112. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/tests/test_create_routes.py +0 -0
  113. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/tests/test_geode_functions.py +0 -0
  114. {opengeodeweb_back-6.13.0rc2 → opengeodeweb_back-6.13.0rc4}/tests/test_utils_functions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenGeodeWeb-Back
3
- Version: 6.13.0rc2
3
+ Version: 6.13.0rc4
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "OpenGeodeWeb-Back"
7
- version = "6.13.0-rc.2"
7
+ version = "6.13.0-rc.4"
8
8
  dynamic = ["dependencies"]
9
9
  authors = [{ name = "Geode-solutions", email = "team-web@geode-solutions.com" }]
10
10
  description = "OpenGeodeWeb-Back is an open source framework that proposes handy python functions and wrappers for the OpenGeode ecosystem"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenGeodeWeb-Back
3
- Version: 6.13.0rc2
3
+ Version: 6.13.0rc4
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
@@ -4,6 +4,7 @@ import os
4
4
  import time
5
5
  import shutil
6
6
  import math
7
+ import typing
7
8
  from threading import Timer
8
9
 
9
10
  # Third party imports
@@ -63,6 +64,18 @@ def allowed_files() -> flask.Response:
63
64
  return flask.make_response({"extensions": list(extensions)}, 200)
64
65
 
65
66
 
67
+ def _write_stream(path: str, stream: typing.IO[bytes], mode: str = "wb") -> None:
68
+ read_buffer_size = 1024 * 1024
69
+ with open(path, mode) as destination:
70
+ while chunk := stream.read(read_buffer_size):
71
+ destination.write(chunk)
72
+
73
+
74
+ def _finalize_upload(filename: str) -> flask.Response:
75
+ print(f"{filename=}", flush=True)
76
+ return flask.make_response({"message": "File uploaded"}, 201)
77
+
78
+
66
79
  @routes.route(
67
80
  schemas_dict["upload_file"]["route"],
68
81
  methods=schemas_dict["upload_file"]["methods"],
@@ -73,8 +86,12 @@ def upload_file() -> flask.Response:
73
86
  if not os.path.exists(UPLOAD_FOLDER_PATH):
74
87
  os.makedirs(UPLOAD_FOLDER_PATH, exist_ok=True)
75
88
 
76
- # Multipart callers (e.g. Vease) still send the file as a "file" form part;
77
- # streaming callers PUT the raw bytes as the body with ?filename= as a query param.
89
+ # Multipart callers (e.g. Vease) still send the whole file as a "file" form
90
+ # part. Everything else PUTs raw bytes with ?filename= as a query param:
91
+ # either the whole file in one request, or one of several chunks (when
92
+ # ?chunk_index=/?total_chunks= are also present) that get appended in
93
+ # order and assembled into the final file once the last one arrives. This
94
+ # keeps every request under cloud hosting's hard request-size limit.
78
95
  if flask.request.mimetype == "multipart/form-data":
79
96
  file = flask.request.files["file"]
80
97
  if file.filename is None:
@@ -82,22 +99,30 @@ def upload_file() -> flask.Response:
82
99
  filename = werkzeug.utils.secure_filename(os.path.basename(file.filename))
83
100
  file_path = os.path.join(UPLOAD_FOLDER_PATH, filename)
84
101
  file.save(file_path)
85
- else:
86
- raw_filename = flask.request.args.get("filename")
87
- if not raw_filename:
88
- flask.abort(400, "Filename is required")
89
- filename = werkzeug.utils.secure_filename(os.path.basename(raw_filename))
90
- file_path = os.path.join(UPLOAD_FOLDER_PATH, filename)
91
- chunk_size = 1024 * 1024
92
- with open(file_path, "wb") as destination:
93
- while chunk := flask.request.stream.read(chunk_size):
94
- destination.write(chunk)
95
- print(f"{filename=}", flush=True)
96
- if filename.lower().endswith(".csv.json"):
97
- shutil.copyfile(
98
- file_path, os.path.join(UPLOAD_FOLDER_PATH, filename[:-9] + ".json")
99
- )
100
- return flask.make_response({"message": "File uploaded"}, 201)
102
+ return _finalize_upload(filename)
103
+
104
+ raw_filename = flask.request.args.get("filename")
105
+ if not raw_filename:
106
+ flask.abort(400, "Filename is required")
107
+ filename = werkzeug.utils.secure_filename(os.path.basename(raw_filename))
108
+ file_path = os.path.join(UPLOAD_FOLDER_PATH, filename)
109
+
110
+ total_chunks = flask.request.args.get("total_chunks", type=int)
111
+ if total_chunks is None:
112
+ _write_stream(file_path, flask.request.stream)
113
+ return _finalize_upload(filename)
114
+
115
+ chunk_index = flask.request.args.get("chunk_index", type=int)
116
+ if chunk_index is None or not 0 <= chunk_index < total_chunks:
117
+ flask.abort(400, "Invalid chunk_index")
118
+
119
+ part_path = f"{file_path}.part"
120
+ _write_stream(part_path, flask.request.stream, "wb" if chunk_index == 0 else "ab")
121
+ if chunk_index < total_chunks - 1:
122
+ return flask.make_response({"message": "Chunk received"}, 200)
123
+
124
+ os.replace(part_path, file_path)
125
+ return _finalize_upload(filename)
101
126
 
102
127
 
103
128
  @routes.route(
@@ -270,23 +295,42 @@ def extract_valid_attribute_values(
270
295
  attribute_name: str,
271
296
  item_index: int,
272
297
  ) -> tuple[list[float], bool]:
273
- attribute_ids_matching_name = attribute_manager.attribute_ids_matching_name(
274
- attribute_name
275
- )
276
- if not isinstance(attribute_ids_matching_name, list):
298
+ attribute_ids = attribute_manager.attribute_ids_matching_name(attribute_name)
299
+ if not isinstance(attribute_ids, list):
277
300
  return [], False
278
- component_attribute = attribute_manager.find_generic_attribute(
279
- attribute_ids_matching_name[0]
280
- )
281
- if component_attribute is None:
282
- return [], False
283
- if not component_attribute.is_genericable():
301
+ attribute = attribute_manager.find_generic_attribute(attribute_ids[0])
302
+ if (
303
+ attribute is None
304
+ or not attribute.is_genericable()
305
+ or not attribute.properties().transferable
306
+ ):
284
307
  return [], False
308
+
309
+ nb_items = attribute.nb_items()
310
+ default_values = getattr(attribute, "default_values", None)
311
+ no_value = default_values().no_value if default_values else None
312
+ if no_value is None and (
313
+ "Point" in attribute.type() or "Vector" in attribute.type()
314
+ ):
315
+ no_value = [0.0] * nb_items
316
+
317
+ value_getter = getattr(
318
+ attribute,
319
+ "value",
320
+ lambda element_index: [
321
+ attribute.generic_item_value(element_index, i) for i in range(nb_items)
322
+ ],
323
+ )
324
+
285
325
  valid_values: list[float] = []
286
326
  has_nan = False
287
327
  for element_index in range(attribute_manager.nb_elements()):
288
- value = component_attribute.generic_item_value(element_index, item_index)
289
- if value is None or math.isnan(value):
328
+ value = attribute.generic_item_value(element_index, item_index)
329
+ if (
330
+ value is None
331
+ or math.isnan(value)
332
+ or (no_value is not None and value_getter(element_index) == no_value)
333
+ ):
290
334
  has_nan = True
291
335
  else:
292
336
  valid_values.append(value)
@@ -306,7 +350,7 @@ def attributes_metadata(
306
350
  attribute_name = attribute.name()
307
351
  if attribute_name is None:
308
352
  continue
309
- if not attribute.is_genericable():
353
+ if not attribute.is_genericable() or not attribute.properties().transferable:
310
354
  continue
311
355
  nb_items = attribute.nb_items()
312
356
  min_values, max_values = [], []
@@ -2,6 +2,7 @@
2
2
  import os
3
3
 
4
4
  # Third party imports
5
+ import opengeode as og
5
6
  from werkzeug.datastructures import FileStorage
6
7
  from flask.testing import FlaskClient
7
8
  from werkzeug.test import TestResponse
@@ -13,6 +14,7 @@ import zipfile
13
14
  from opengeodeweb_microservice.database.data import Data
14
15
  from opengeodeweb_microservice.database.connection import get_session
15
16
  from opengeodeweb_back import geode_functions, test_utils
17
+ from opengeodeweb_back.routes.blueprint_routes import extract_valid_attribute_values
16
18
  from opengeodeweb_back.geode_objects.geode_polygonal_surface3d import (
17
19
  GeodePolygonalSurface3D,
18
20
  )
@@ -109,6 +111,60 @@ def test_upload_file_raw_missing_filename(client: FlaskClient) -> None:
109
111
  assert response.status_code == 400
110
112
 
111
113
 
114
+ def test_upload_file_chunked(
115
+ client: FlaskClient, filename: str = "test.og_brep"
116
+ ) -> None:
117
+ file = os.path.join(data_dir, filename)
118
+ with open(file, "rb") as opened_file:
119
+ file_bytes = opened_file.read()
120
+
121
+ chunk_filename = "chunked_upload_test.og_brep"
122
+ chunk_size = max(1, len(file_bytes) // 3)
123
+ chunks = [
124
+ file_bytes[index : index + chunk_size]
125
+ for index in range(0, len(file_bytes), chunk_size)
126
+ ]
127
+ total_chunks = len(chunks)
128
+
129
+ uploaded_path = os.path.join(data_dir, chunk_filename)
130
+ try:
131
+ for chunk_index, chunk in enumerate(chunks):
132
+ response = client.put(
133
+ f"/opengeodeweb_back/upload_file"
134
+ f"?filename={chunk_filename}"
135
+ f"&chunk_index={chunk_index}"
136
+ f"&total_chunks={total_chunks}",
137
+ data=chunk,
138
+ content_type="application/octet-stream",
139
+ )
140
+ if chunk_index < total_chunks - 1:
141
+ assert response.status_code == 200
142
+ assert not os.path.exists(uploaded_path)
143
+ else:
144
+ assert response.status_code == 201
145
+
146
+ with open(uploaded_path, "rb") as uploaded_file:
147
+ assert uploaded_file.read() == file_bytes
148
+ finally:
149
+ if os.path.exists(uploaded_path):
150
+ os.remove(uploaded_path)
151
+ part_path = f"{uploaded_path}.part"
152
+ if os.path.exists(part_path):
153
+ os.remove(part_path)
154
+
155
+
156
+ def test_upload_file_chunked_invalid_chunk_index(client: FlaskClient) -> None:
157
+ response = client.put(
158
+ "/opengeodeweb_back/upload_file"
159
+ "?filename=invalid_chunk_index.og_brep"
160
+ "&chunk_index=2"
161
+ "&total_chunks=2",
162
+ data=b"some raw bytes",
163
+ content_type="application/octet-stream",
164
+ )
165
+ assert response.status_code == 400
166
+
167
+
112
168
  def test_missing_files(client: FlaskClient) -> None:
113
169
  route = f"/opengeodeweb_back/missing_files"
114
170
 
@@ -777,3 +833,47 @@ def test_model_component_polyhedron_attribute_names(client: FlaskClient) -> None
777
833
  return {"id": model_id, "component_ids": block_ids}
778
834
 
779
835
  test_utils.test_route_wrong_params(client, route, get_full_data)
836
+
837
+
838
+ def test_extract_valid_attribute_values_with_sentinel_no_value() -> None:
839
+ mesh = og.TriangulatedSurface3D.create()
840
+ builder = og.TriangulatedSurfaceBuilder3D.create(mesh)
841
+ vertex_0 = builder.create_point(og.Point3D([0, 0, 0]))
842
+ vertex_1 = builder.create_point(og.Point3D([1, 0, 0]))
843
+ builder.create_triangle([vertex_0, vertex_1, vertex_0])
844
+
845
+ attribute_manager = mesh.vertex_attribute_manager()
846
+ values_config = og.AttributeValuesDouble()
847
+ values_config.no_value = -999.0
848
+ attribute_id = attribute_manager.create_attribute_variable_double(
849
+ "variable_double", values_config, og.AttributeProperties()
850
+ )
851
+ attribute = attribute_manager.find_attribute_variable_double(attribute_id)
852
+ attribute.set_value(vertex_0, -999.0)
853
+ attribute.set_value(vertex_1, 42.0)
854
+
855
+ valid_values, has_nan = extract_valid_attribute_values(
856
+ attribute_manager, "variable_double", 0
857
+ )
858
+ assert has_nan is True
859
+ assert valid_values == [42.0]
860
+
861
+
862
+ def test_extract_valid_attribute_values_with_non_transferable_attribute() -> None:
863
+ mesh = og.TriangulatedSurface3D.create()
864
+ builder = og.TriangulatedSurfaceBuilder3D.create(mesh)
865
+ vertex_0 = builder.create_point(og.Point3D([0, 0, 0]))
866
+ builder.create_triangle([vertex_0, vertex_0, vertex_0])
867
+
868
+ attribute_manager = mesh.vertex_attribute_manager()
869
+ properties = og.AttributeProperties()
870
+ properties.transferable = False
871
+ attribute_manager.create_attribute_variable_double(
872
+ "internal_attribute", og.AttributeValuesDouble(), properties
873
+ )
874
+
875
+ valid_values, has_nan = extract_valid_attribute_values(
876
+ attribute_manager, "internal_attribute", 0
877
+ )
878
+ assert has_nan is False
879
+ assert valid_values == []