From 7dbb3fc4d1901d4d050249203d3bd84bc2d6b5c1 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:12:37 +0100 Subject: [PATCH 1/9] feat(test_utils): automatic schema validation closes #23 --- src/opengeodeweb_back/test_utils.py | 18 +++++ tests/test_routes.py | 109 ++++++++++++++-------------- 2 files changed, 71 insertions(+), 56 deletions(-) create mode 100644 src/opengeodeweb_back/test_utils.py diff --git a/src/opengeodeweb_back/test_utils.py b/src/opengeodeweb_back/test_utils.py new file mode 100644 index 00000000..36d10b61 --- /dev/null +++ b/src/opengeodeweb_back/test_utils.py @@ -0,0 +1,18 @@ + + +def test_route_wrong_params(client, route, get_full_data): + for key, value in get_full_data().items(): + json = get_full_data() + json.pop(key) + response = client.post(route, json=json) + assert response.status_code == 400 + error_description = response.json["description"] + assert error_description == f"Validation error: '{key}' is a required property" + + json = get_full_data() + json["dumb_key"] = "dumb_value" + response = client.post(route, json=json) + assert response.status_code == 400 + error_description = response.json["description"] + assert error_description == "Validation error: Additional properties are not allowed ('dumb_key' was unexpected)" + \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index 0661f025..bfb752b6 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1,17 +1,28 @@ -import os +# Standard library imports import base64 +import os + +# Third party imports from werkzeug.datastructures import FileStorage +# Local application imports +from src.opengeodeweb_back import test_utils + def test_allowed_files(client): route = f"/allowed_files" - response = client.post(route, json={"supported_feature": None}) + get_full_data = lambda: {"supported_feature": "None"} + json = get_full_data() + response = client.post(route, json=json) assert response.status_code == 200 extensions = response.json["extensions"] assert type(extensions) is list for extension in extensions: assert type(extension) is str + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) + def test_allowed_objects(client): route = f"/allowed_objects" @@ -30,14 +41,8 @@ def get_full_data(): for allowed_object in allowed_objects: assert type(allowed_object) is str - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" - + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_upload_file(client): response = client.put( @@ -58,11 +63,7 @@ def get_full_data(): } json = get_full_data() - response = client.post( - route, - json=json, - ) - + response = client.post(route,json=json) assert response.status_code == 200 has_missing_files = response.json["has_missing_files"] mandatory_files = response.json["mandatory_files"] @@ -71,37 +72,25 @@ def get_full_data(): assert type(mandatory_files) is list assert type(additional_files) is list - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" - + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_geographic_coordinate_systems(client): route = f"/geographic_coordinate_systems" - + get_full_data = lambda: {"input_geode_object": "BRep"} # Normal test with geode_object 'BRep' - response = client.post(route, json={"input_geode_object": "BRep"}) + response = client.post(route, json=get_full_data()) assert response.status_code == 200 crs_list = response.json["crs_list"] assert type(crs_list) is list for crs in crs_list: assert type(crs) is dict - # Test without geode_object - response = client.post(route, json={}) - assert response.status_code == 400 - error_message = response.json["description"] - assert ( - error_message == "Validation error: 'input_geode_object' is a required property" - ) - + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_inspect_file(client): - route = f"/inspect_file" + route = f"/inspect_file" def get_full_data(): return { @@ -117,14 +106,8 @@ def get_full_data(): inspection_result = response.json["inspection_result"] assert type(inspection_result) is dict - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" - + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_geode_objects_and_output_extensions(client): route = "/geode_objects_and_output_extensions" @@ -148,13 +131,8 @@ def get_full_data(): assert type(value) is dict assert type(value["is_saveable"]) is bool - # Test without input_geode_object - response = client.post(route, json={}) - assert response.status_code == 400 - error_message = response.json["description"] - assert ( - error_message == "Validation error: 'input_geode_object' is a required property" - ) + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) def test_save_viewable_file(client): @@ -178,10 +156,29 @@ def get_full_data(): id = response.json["id"] assert type(id) is str - for key, value in get_full_data().items(): - json = get_full_data() - json.pop(key) - response = client.post(route, json=json) - assert response.status_code == 400 - error_description = response.json["description"] - assert error_description == f"Validation error: '{key}' is a required property" + test_utils.test_route_wrong_params(client, route, get_full_data) + +def test_vertex_attribute_names(client): + response = client.put( + f"/upload_file", + data={"file": FileStorage(open("./tests/vertex_attribute.vtp", "rb"))}, + ) + assert response.status_code == 201 + + route = f"/vertex_attribute_names" + + def get_full_data(): + return { + "input_geode_object": "PolygonalSurface3D", + "filename": "vertex_attribute.vtp", + } + + # Normal test with filename 'vertex_attribute.vtp' + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + vertex_attribute_names = response.json["vertex_attribute_names"] + assert type(vertex_attribute_names) is list + for vertex_attribute_name in vertex_attribute_names: + assert type(vertex_attribute_name) is str + + test_utils.test_route_wrong_params(client, route, get_full_data) \ No newline at end of file From fd2c248664e2829645c5c65b47c2e891bfb47016 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:13:03 +0100 Subject: [PATCH 2/9] ignore files in python repo --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..b5a5096b --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = --ignore=src/opengeodeweb_back/ \ No newline at end of file From b7c8291c3b2e12609ade444aefd3672919b9aeb6 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:13:13 +0100 Subject: [PATCH 3/9] ignore data --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a9134f20..96807427 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .pytest_cache -./data +/data dist venv output From c3715f171a9b533a1b401863c7b2bba2e05a5c8c Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:13:24 +0100 Subject: [PATCH 4/9] update requirements --- requirements.txt | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/requirements.txt b/requirements.txt index edae6731..eba5af2a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,22 +10,22 @@ attrs==24.2.0 # via # jsonschema # referencing -blinker==1.8.2 +blinker==1.9.0 # via flask click==8.1.7 # via flask -flask[async]==3.0.3 +flask[async]==3.1.0 # via # -r requirements.in # flask-cors flask-cors==5.0.0 # via -r requirements.in -geode-background==8.7.0 +geode-background==8.7.5 # via # geode-explicit # geode-implicit # geode-simplex -geode-common==33.0.9 +geode-common==33.1.3 # via # -r requirements.in # geode-background @@ -35,26 +35,26 @@ geode-common==33.0.9 # geode-numerics # geode-simplex # geode-viewables -geode-conversion==6.0.14 +geode-conversion==6.0.18 # via # geode-explicit # geode-implicit -geode-explicit==6.1.14 +geode-explicit==6.1.17 # via # -r requirements.in # geode-implicit -geode-implicit==3.3.2 +geode-implicit==3.3.5 # via -r requirements.in -geode-numerics==5.1.8 +geode-numerics==5.2.0 # via # -r requirements.in # geode-implicit # geode-simplex -geode-simplex==8.2.15 +geode-simplex==8.2.18 # via # -r requirements.in # geode-implicit -geode-viewables==3.0.6 +geode-viewables==3.0.8 # via -r requirements.in itsdangerous==2.2.0 # via flask @@ -68,7 +68,7 @@ markupsafe==3.0.2 # via # jinja2 # werkzeug -opengeode-core==15.6.3 +opengeode-core==15.6.7 # via # -r requirements.in # geode-background @@ -83,23 +83,23 @@ opengeode-core==15.6.3 # opengeode-geosciencesio # opengeode-inspector # opengeode-io -opengeode-geosciences==8.1.1 +opengeode-geosciences==8.2.0 # via # -r requirements.in # geode-implicit # geode-viewables # opengeode-geosciencesio -opengeode-geosciencesio==5.2.3 +opengeode-geosciencesio==5.3.2 # via # -r requirements.in # geode-implicit -opengeode-inspector==6.1.12 +opengeode-inspector==6.1.15 # via # -r requirements.in # geode-explicit # geode-implicit # geode-simplex -opengeode-io==7.0.5 +opengeode-io==7.0.7 # via # -r requirements.in # geode-implicit @@ -115,7 +115,7 @@ rpds-py==0.21.0 # referencing typing-extensions==4.12.2 # via asgiref -werkzeug==3.1.2 +werkzeug==3.1.3 # via # -r requirements.in # flask From 11865dd747b8e5501f17dbf5b70ad8052cbdef05 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:14:05 +0100 Subject: [PATCH 5/9] feat(vertex attribute): new route/schemas/test/data --- .../routes/blueprint_routes.py | 26 +++++++++++++++++++ .../schemas/vertex_attribute_names.json | 15 +++++++++++ tests/vertex_attribute.vtp | 25 ++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json create mode 100644 tests/vertex_attribute.vtp diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 5fa558e9..c3a36bfe 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -271,6 +271,32 @@ def save_viewable_file(): ) +with open( + os.path.join(schemas, "vertex_attribute_names.json"), + "r", +) as file: + vertex_attribute_names_json = json.load(file) + +@routes.route( + vertex_attribute_names_json["route"], + methods=vertex_attribute_names_json["methods"], +) +def vertex_attribute_names(): + + UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] + utils_functions.validate_request(flask.request, vertex_attribute_names_json) + file_absolute_path = os.path.join(UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"])) + data = geode_functions.load(flask.request.json["input_geode_object"], file_absolute_path) + vertex_attribute_names = data.vertex_attribute_manager().attribute_names() + print(f"vertex_attribute_names: {vertex_attribute_names}", flush=True) + return flask.make_response( + { + "vertex_attribute_names": vertex_attribute_names, + }, + 200, + ) + + with open( os.path.join(schemas, "ping.json"), "r", diff --git a/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json b/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json new file mode 100644 index 00000000..963597af --- /dev/null +++ b/src/opengeodeweb_back/routes/schemas/vertex_attribute_names.json @@ -0,0 +1,15 @@ +{ + "route": "/vertex_attribute_names", + "methods": ["POST"], + "type": "object", + "properties": { + "input_geode_object": { + "type": "string" + }, + "filename": { + "type": "string" + } + }, + "required": ["input_geode_object", "filename"], + "additionalProperties": false +} diff --git a/tests/vertex_attribute.vtp b/tests/vertex_attribute.vtp new file mode 100644 index 00000000..dfe9f191 --- /dev/null +++ b/tests/vertex_attribute.vtp @@ -0,0 +1,25 @@ + + + + + + -4.81986 -4.24426 -3.08307 -2.67472 -3.78427 -1.72952 -3.51395 -1.54156 -3.26163 -1.37705 1.49282 0.476446 3.07847 1.81965 0.15148 -0.00219692 -2.03758 -1.21643 -0.392793 1.04446 -0.159125 -1.41509 -1.36066 + 1.1 1.5 4.5 1.4 2.9 3 4.9 3.1 6.4 1.7 6.3 4 8.2 1.9 8.5 4 11 2 10.9 4 10.7 6.9 9.4 6 10.6 8.5 8 7.5 7 5.95 5.8 5.8 1.1 4.5 3 5 5 5.5 6.1 7 5.8 5.8 5.35 4.45 5.35 4.45 + 0.212913 0.898437 0 0.17411 0.88882 0 0.188674 0.91656 0 0.129386 0.902004 0 0.0522603 0.892251 0 0.0913028 0.902984 0 0.0494319 0.925332 0 0.0790874 0.944317 0 0.0630148 0.926847 0 0.0606951 0.95661 0 0.0916888 0.990159 0 0.102275 0.986827 0 0.0910002 0.997784 0 0.149679 1.01122 0 0.129961 0.95844 0 nan nan nan 0.18944 0.925768 0 0.188704 0.925194 0 0.172618 0.985506 0 0.149488 0.967933 0 0.14023 0.965619 0 0.0974158 0.875532 0 nan nan nan + 50 51 55 56 52 57 53 58 54 59 62 61 47 46 60 35 32 33 34 45 35 63 63 + + + 1.1 1.5 0 4.5 1.4 0 2.9 3 3 4.9 3.1 0 6.4 1.7 0 6.3 4 0 8.2 1.9 0 8.5 4 0 11 2 0 10.9 4 3 10.7 6.9 0 9.4 6 0 10.6 8.5 0 8 7.5 0 7 5.95 0 5.8 5.8 0 1.1 4.5 0 3 5 0 5 5.5 0 6.1 7 3 5.8 5.8 0 5.35 4.45 0 5.35 4.45 0 + + + -4.04907 -3.33402 -3.56775 -2.7295 -3.00925 -2.26168 -2.77238 -2.06008 0.197405 1.68258 1.79152 -0.814056 0.815858 -0.304546 -1.03987 -0.579057 -3.3135 -2.11236 -2.32474 -1.42798 0.345606 1.0052 -1.10125 -1.93978 -1.47606 -0.585217 + 4.29497e+09 1 16 2 18 0 4.29497e+09 3 1 4 23 2 4.29497e+09 5 3 6 14 4 4.29497e+09 7 5 4.29497e+09 11 6 4.29497e+09 9 11 4.29497e+09 10 8 9 4.29497e+09 12 7 8 13 13 10 21 11 12 14 5 13 15 14 20 22 0 17 4.29497e+09 16 18 4.29497e+09 1 19 17 24 4.29497e+09 18 15 21 4.29497e+09 12 4.29497e+09 20 4.29497e+09 23 15 4.29497e+09 3 22 4.29497e+09 25 19 4.29497e+09 4.29497e+09 24 + 0 1 2 1 3 2 1 4 3 4 5 3 4 6 5 6 7 5 6 8 7 8 9 7 9 10 11 10 12 11 11 12 13 7 9 11 14 11 13 7 11 14 5 7 14 5 14 20 0 2 16 16 2 17 2 3 17 3 18 17 20 14 19 14 13 19 20 21 5 21 3 5 3 22 18 22 15 18 + + + 0 1 2 1 3 2 1 4 3 4 5 3 4 6 5 6 7 5 6 8 7 8 9 7 9 10 11 10 12 11 11 12 13 7 9 11 14 11 13 7 11 14 5 7 14 5 14 20 0 2 16 16 2 17 2 3 17 3 18 17 20 14 19 14 13 19 20 21 5 21 3 5 3 22 18 22 15 18 + 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 + + + + From ced3b8db5bd0c941fd3a0525d4124cf6b24fdf1c Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:16:45 +0100 Subject: [PATCH 6/9] clean imports --- src/opengeodeweb_back/geode_objects.py | 4 ++++ src/opengeodeweb_back/test_utils.py | 5 +++++ tests/conftest.py | 7 ++++++- tests/test_geode_functions.py | 5 +++++ tests/test_utils_functions.py | 5 +++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/opengeodeweb_back/geode_objects.py b/src/opengeodeweb_back/geode_objects.py index 228c42dd..d0f6f1b6 100644 --- a/src/opengeodeweb_back/geode_objects.py +++ b/src/opengeodeweb_back/geode_objects.py @@ -1,3 +1,6 @@ +# Standard library imports + +# Third party imports import opengeode as og import opengeode_io as og_io import opengeode_inspector as og_inspector @@ -5,6 +8,7 @@ import opengeode_geosciencesio as og_gs_io import geode_viewables as g_v +# Local application imports def geode_objects_dict(): return { diff --git a/src/opengeodeweb_back/test_utils.py b/src/opengeodeweb_back/test_utils.py index 36d10b61..75329ea6 100644 --- a/src/opengeodeweb_back/test_utils.py +++ b/src/opengeodeweb_back/test_utils.py @@ -1,3 +1,8 @@ +# Standard library imports + +# Third party imports + +# Local application imports def test_route_wrong_params(client, route, get_full_data): diff --git a/tests/conftest.py b/tests/conftest.py index 5642ba2c..efe7e6c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,11 @@ +# Standard library imports +import time + +# Third party imports import pytest + +# Local application imports from app import app -import time @pytest.fixture diff --git a/tests/test_geode_functions.py b/tests/test_geode_functions.py index 2155acf2..ea0ecb64 100644 --- a/tests/test_geode_functions.py +++ b/tests/test_geode_functions.py @@ -1,5 +1,10 @@ +# Standard library imports import os import uuid + +# Third party imports + +# Local application imports from src.opengeodeweb_back import geode_functions, geode_objects diff --git a/tests/test_utils_functions.py b/tests/test_utils_functions.py index 8416539d..59fbfcc9 100644 --- a/tests/test_utils_functions.py +++ b/tests/test_utils_functions.py @@ -1,4 +1,9 @@ +# Standard library imports + +# Third party imports import flask + +# Local application imports from src.opengeodeweb_back import utils_functions From c8142ad85130b1d3853b0970ebf1629bf1fb5ac3 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:23:30 +0100 Subject: [PATCH 7/9] merge next into branch --- src/opengeodeweb_back/geode_functions.py | 4 ++ src/opengeodeweb_back/geode_objects.py | 25 +++++++++++++ .../routes/blueprint_routes.py | 37 ++++++++++++++++++- .../routes/schemas/create_point.json | 24 ++++++++++++ tests/test_routes.py | 17 +++++++++ 5 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/opengeodeweb_back/routes/schemas/create_point.json diff --git a/src/opengeodeweb_back/geode_functions.py b/src/opengeodeweb_back/geode_functions.py index ca29c35c..40868a6c 100644 --- a/src/opengeodeweb_back/geode_functions.py +++ b/src/opengeodeweb_back/geode_functions.py @@ -15,6 +15,10 @@ def geode_object_value(geode_object: str): return geode_objects_dict()[geode_object] +def geode_object_class(geode_object: str): + return geode_object_value(geode_object)["class"] + + def input_factory(geode_object: str): return geode_object_value(geode_object)["input_factory"] diff --git a/src/opengeodeweb_back/geode_objects.py b/src/opengeodeweb_back/geode_objects.py index d0f6f1b6..03007cad 100644 --- a/src/opengeodeweb_back/geode_objects.py +++ b/src/opengeodeweb_back/geode_objects.py @@ -13,6 +13,7 @@ def geode_objects_dict(): return { "BRep": { + "class": og.BRep, "input_factory": og.BRepInputFactory, "output_factory": og.BRepOutputFactory, "missing_files": og.check_brep_missing_files, @@ -34,6 +35,7 @@ def geode_objects_dict(): }, "CrossSection": { "parent": "Section", + "class": og_gs.CrossSection, "input_factory": og_gs.CrossSectionInputFactory, "output_factory": og_gs.CrossSectionOutputFactory, "missing_files": og_gs.check_cross_section_missing_files, @@ -54,6 +56,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_section, }, "EdgedCurve2D": { + "class": og.EdgedCurve2D, "input_factory": og.EdgedCurveInputFactory2D, "output_factory": og.EdgedCurveOutputFactory2D, "missing_files": og.check_edged_curve_missing_files2D, @@ -74,6 +77,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_edged_curve2D, }, "EdgedCurve3D": { + "class": og.EdgedCurve3D, "input_factory": og.EdgedCurveInputFactory3D, "output_factory": og.EdgedCurveOutputFactory3D, "missing_files": og.check_edged_curve_missing_files3D, @@ -94,6 +98,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_edged_curve3D, }, "Graph": { + "class": og.Graph, "input_factory": og.GraphInputFactory, "output_factory": og.GraphOutputFactory, "missing_files": og.check_graph_missing_files, @@ -107,6 +112,7 @@ def geode_objects_dict(): "is_viewable": True, }, "HybridSolid3D": { + "class": og.HybridSolid3D, "input_factory": og.HybridSolidInputFactory3D, "output_factory": og.HybridSolidOutputFactory3D, "missing_files": og.check_hybrid_solid_missing_files3D, @@ -128,6 +134,7 @@ def geode_objects_dict(): }, "ImplicitCrossSection": { "parent": "CrossSection", + "class": og_gs.ImplicitCrossSection, "input_factory": og_gs.ImplicitCrossSectionInputFactory, "output_factory": og_gs.ImplicitCrossSectionOutputFactory, "missing_files": og_gs.check_implicit_cross_section_missing_files, @@ -149,6 +156,7 @@ def geode_objects_dict(): }, "ImplicitStructuralModel": { "parent": "StructuralModel", + "class": og_gs.ImplicitStructuralModel, "input_factory": og_gs.ImplicitStructuralModelInputFactory, "output_factory": og_gs.ImplicitStructuralModelOutputFactory, "missing_files": og_gs.check_implicit_structural_model_missing_files, @@ -169,6 +177,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_brep, }, "LightRegularGrid2D": { + "class": og.LightRegularGrid2D, "input_factory": og.LightRegularGridInputFactory2D, "output_factory": og.LightRegularGridOutputFactory2D, "missing_files": og.check_light_regular_grid_missing_files2D, @@ -182,6 +191,7 @@ def geode_objects_dict(): "save_viewable": g_v.save_viewable_light_regular_grid2D, }, "LightRegularGrid3D": { + "class": og.LightRegularGrid3D, "input_factory": og.LightRegularGridInputFactory3D, "output_factory": og.LightRegularGridOutputFactory3D, "missing_files": og.check_light_regular_grid_missing_files3D, @@ -195,6 +205,7 @@ def geode_objects_dict(): "save_viewable": g_v.save_viewable_light_regular_grid3D, }, "PointSet2D": { + "class": og.PointSet2D, "input_factory": og.PointSetInputFactory2D, "output_factory": og.PointSetOutputFactory2D, "missing_files": og.check_point_set_missing_files2D, @@ -215,6 +226,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_point_set2D, }, "PointSet3D": { + "class": og.PointSet3D, "input_factory": og.PointSetInputFactory3D, "output_factory": og.PointSetOutputFactory3D, "missing_files": og.check_point_set_missing_files3D, @@ -235,6 +247,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_point_set3D, }, "PolygonalSurface2D": { + "class": og.PolygonalSurface2D, "input_factory": og.PolygonalSurfaceInputFactory2D, "output_factory": og.PolygonalSurfaceOutputFactory2D, "missing_files": og.check_polygonal_surface_missing_files2D, @@ -255,6 +268,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_surface2D, }, "PolygonalSurface3D": { + "class": og.PolygonalSurface3D, "input_factory": og.PolygonalSurfaceInputFactory3D, "output_factory": og.PolygonalSurfaceOutputFactory3D, "missing_files": og.check_polygonal_surface_missing_files3D, @@ -275,6 +289,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_surface3D, }, "PolyhedralSolid3D": { + "class": og.PolyhedralSolid3D, "input_factory": og.PolyhedralSolidInputFactory3D, "output_factory": og.PolyhedralSolidOutputFactory3D, "missing_files": og.check_polyhedral_solid_missing_files3D, @@ -295,6 +310,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_solid3D, }, "RasterImage2D": { + "class": og.RasterImage2D, "input_factory": og.RasterImageInputFactory2D, "output_factory": og.RasterImageOutputFactory2D, "missing_files": og.check_raster_image_missing_files2D, @@ -308,6 +324,7 @@ def geode_objects_dict(): "save_viewable": g_v.save_viewable_raster_image2D, }, "RasterImage3D": { + "class": og.RasterImage3D, "input_factory": og.RasterImageInputFactory3D, "output_factory": og.RasterImageOutputFactory3D, "missing_files": og.check_raster_image_missing_files3D, @@ -321,6 +338,7 @@ def geode_objects_dict(): "save_viewable": g_v.save_viewable_raster_image3D, }, "RegularGrid2D": { + "class": og.RegularGrid2D, "input_factory": og.RegularGridInputFactory2D, "output_factory": og.RegularGridOutputFactory2D, "missing_files": og.check_regular_grid_missing_files2D, @@ -340,6 +358,7 @@ def geode_objects_dict(): "save_viewable": g_v.save_viewable_regular_grid2D, }, "RegularGrid3D": { + "class": og.RegularGrid3D, "input_factory": og.RegularGridInputFactory3D, "output_factory": og.RegularGridOutputFactory3D, "missing_files": og.check_regular_grid_missing_files3D, @@ -359,6 +378,7 @@ def geode_objects_dict(): "save_viewable": g_v.save_viewable_regular_grid3D, }, "Section": { + "class": og.Section, "input_factory": og.SectionInputFactory, "output_factory": og.SectionOutputFactory, "missing_files": og.check_section_missing_files, @@ -380,6 +400,7 @@ def geode_objects_dict(): }, "StructuralModel": { "parent": "BRep", + "class": og_gs.StructuralModel, "input_factory": og_gs.StructuralModelInputFactory, "output_factory": og_gs.StructuralModelOutputFactory, "missing_files": og_gs.check_structural_model_missing_files, @@ -400,6 +421,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_brep, }, "TetrahedralSolid3D": { + "class": og.TetrahedralSolid3D, "input_factory": og.TetrahedralSolidInputFactory3D, "output_factory": og.TetrahedralSolidOutputFactory3D, "missing_files": og.check_tetrahedral_solid_missing_files3D, @@ -420,6 +442,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_solid3D, }, "TriangulatedSurface2D": { + "class": og.TriangulatedSurface2D, "input_factory": og.TriangulatedSurfaceInputFactory2D, "output_factory": og.TriangulatedSurfaceOutputFactory2D, "missing_files": og.check_triangulated_surface_missing_files2D, @@ -440,6 +463,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_surface2D, }, "TriangulatedSurface3D": { + "class": og.TriangulatedSurface3D, "input_factory": og.TriangulatedSurfaceInputFactory3D, "output_factory": og.TriangulatedSurfaceOutputFactory3D, "missing_files": og.check_triangulated_surface_missing_files3D, @@ -460,6 +484,7 @@ def geode_objects_dict(): "inspector": og_inspector.inspect_surface3D, }, "VertexSet": { + "class": og.VertexSet, "input_factory": og.VertexSetInputFactory, "output_factory": og.VertexSetOutputFactory, "missing_files": og.check_vertex_set_missing_files, diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index c3a36bfe..a49993de 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -5,9 +5,12 @@ # Third party imports import flask -from .. import geode_functions, utils_functions -import werkzeug +import opengeode import uuid +import werkzeug + +# Local application imports +from .. import geode_functions, utils_functions routes = flask.Blueprint("routes", __name__) @@ -271,6 +274,36 @@ def save_viewable_file(): ) +with open(os.path.join(schemas, "create_point.json"), "r") as file: + create_point_json = json.load(file) + + +@routes.route(create_point_json["route"], methods=create_point_json["methods"]) +def create_point(): + utils_functions.validate_request(flask.request, create_point_json) + DATA_FOLDER_PATH = flask.current_app.config["DATA_FOLDER_PATH"] + x = flask.request.json["x"] + y = flask.request.json["y"] + z = flask.request.json["z"] + class_ = geode_functions.geode_object_class("PointSet3D") + PointSet3D = class_.create() + builder = geode_functions.create_builder("PointSet3D", PointSet3D) + builder.create_point(opengeode.Point3D([x, y, z])) + + generated_id = str(uuid.uuid4()).replace("-", "") + saved_viewable_file_path = geode_functions.save_viewable( + "PointSet3D", PointSet3D, DATA_FOLDER_PATH, generated_id + ) + + return flask.make_response( + { + "viewable_file_name": os.path.basename(saved_viewable_file_path), + "id": generated_id, + }, + 200, + ) + + with open( os.path.join(schemas, "vertex_attribute_names.json"), "r", diff --git a/src/opengeodeweb_back/routes/schemas/create_point.json b/src/opengeodeweb_back/routes/schemas/create_point.json new file mode 100644 index 00000000..6f77eebf --- /dev/null +++ b/src/opengeodeweb_back/routes/schemas/create_point.json @@ -0,0 +1,24 @@ +{ + "route": "/create_point", + "methods": [ + "POST" + ], + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "z": { + "type": "number" + } + }, + "required": [ + "x", + "y", + "z" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/tests/test_routes.py b/tests/test_routes.py index bfb752b6..07c33b96 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -156,6 +156,7 @@ def get_full_data(): id = response.json["id"] assert type(id) is str + # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) def test_vertex_attribute_names(client): @@ -181,4 +182,20 @@ def get_full_data(): for vertex_attribute_name in vertex_attribute_names: assert type(vertex_attribute_name) is str + test_utils.test_route_wrong_params(client, route, get_full_data) + + +def test_create_point(client): + route = f"/create_point" + get_full_data = lambda: {"x": 1, "y": 2, "z": 3} + + # Normal test with all keys + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + viewable_file_name = response.json["viewable_file_name"] + assert type(viewable_file_name) is str + id = response.json["id"] + assert type(id) is str + + # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) \ No newline at end of file From e50f3ffeb7706e7b3a8fea49c303cd91b6cc1bce Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 13:57:35 +0100 Subject: [PATCH 8/9] feat(polygon attribute): new route/schemas/test/data --- .../routes/blueprint_routes.py | 29 ++++++++++++++++++- .../schemas/polygon_attribute_names.json | 15 ++++++++++ tests/polygon_attribute.vtp | 25 ++++++++++++++++ tests/test_routes.py | 28 ++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json create mode 100644 tests/polygon_attribute.vtp diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index a49993de..111cbd75 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -321,7 +321,7 @@ def vertex_attribute_names(): file_absolute_path = os.path.join(UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"])) data = geode_functions.load(flask.request.json["input_geode_object"], file_absolute_path) vertex_attribute_names = data.vertex_attribute_manager().attribute_names() - print(f"vertex_attribute_names: {vertex_attribute_names}", flush=True) + return flask.make_response( { "vertex_attribute_names": vertex_attribute_names, @@ -330,6 +330,33 @@ def vertex_attribute_names(): ) + +with open( + os.path.join(schemas, "polygon_attribute_names.json"), + "r", +) as file: + polygon_attribute_names_json = json.load(file) + +@routes.route( + polygon_attribute_names_json["route"], + methods=polygon_attribute_names_json["methods"], +) +def polygon_attribute_names(): + + UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] + utils_functions.validate_request(flask.request, vertex_attribute_names_json) + file_absolute_path = os.path.join(UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"])) + data = geode_functions.load(flask.request.json["input_geode_object"], file_absolute_path) + polygon_attribute_names = data.polygon_attribute_manager().attribute_names() + + return flask.make_response( + { + "polygon_attribute_names": polygon_attribute_names, + }, + 200, + ) + + with open( os.path.join(schemas, "ping.json"), "r", diff --git a/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json b/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json new file mode 100644 index 00000000..6f1f8b2c --- /dev/null +++ b/src/opengeodeweb_back/routes/schemas/polygon_attribute_names.json @@ -0,0 +1,15 @@ +{ + "route": "/polygon_attribute_names", + "methods": ["POST"], + "type": "object", + "properties": { + "input_geode_object": { + "type": "string" + }, + "filename": { + "type": "string" + } + }, + "required": ["input_geode_object", "filename"], + "additionalProperties": false +} diff --git a/tests/polygon_attribute.vtp b/tests/polygon_attribute.vtp new file mode 100644 index 00000000..09e313a5 --- /dev/null +++ b/tests/polygon_attribute.vtp @@ -0,0 +1,25 @@ + + + + + + -5.03873 -3.56819 -3.29213 -0.379219 0.386424 -1.97966 -0.310684 1.50807 1.80048 2.25409 2.56229 0.235826 -0.162775 -1.4339 + 1.1 4.5 3 5 1.2 6 3 8 5 8.2 5 5.5 1.1 8.3 2 10 4 10 6 10 7 10 5.9 7.7 6.1 7 5.8 5.8 + 32 33 36 37 42 34 38 39 40 41 43 44 45 35 + 0.441452 1.06497 0 0.509268 1.09401 0 0.451134 1.22395 0 0.244789 1.03019 0 0.306431 0.898241 0 0.520398 0.854144 0 0.0558145 0.936744 0 0.17577 1.03026 0 0.179188 0.94981 0 0.242737 0.863881 0 0.312089 0.886676 0 0.328556 0.855054 0 nan nan nan 0.353402 0.897993 0 + + + 1.1 4.5 0 3 5 0 1.2 6 0 3 8 0 5 8.2 0 5 5.5 0 1.1 8.3 0 2 10 0 4 10 0 6 10 0 7 10 0 5.9 7.7 0 6.1 7 0 5.8 5.8 0 + + + 0 1 2 1 3 2 1 4 3 1 5 4 2 3 6 3 7 6 3 8 7 3 4 8 4 9 8 4 10 9 4 11 10 4 12 11 5 12 4 5 13 12 + 4.29497e+09 1 4.29497e+09 2 4 0 3 7 1 4.29497e+09 12 2 1 5 4.29497e+09 6 4.29497e+09 4 7 4.29497e+09 5 2 8 6 9 4.29497e+09 7 10 4.29497e+09 8 11 4.29497e+09 9 12 4.29497e+09 10 13 11 3 4.29497e+09 4.29497e+09 12 + -3.96635 -2.41318 -1.18699 -1.72048 -1.32735 0.272721 0.976443 0.602562 1.48033 1.73427 1.06151 0.153159 -0.585337 -1.19211 + + + 0 1 2 1 3 2 1 4 3 1 5 4 2 3 6 3 7 6 3 8 7 3 4 8 4 9 8 4 10 9 4 11 10 4 12 11 5 12 4 5 13 12 + 3 6 9 12 15 18 21 24 27 30 33 36 39 42 + + + + diff --git a/tests/test_routes.py b/tests/test_routes.py index 07c33b96..d92f802b 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -185,6 +185,34 @@ def get_full_data(): test_utils.test_route_wrong_params(client, route, get_full_data) + +def test_polygon_attribute_names(client): + response = client.put( + f"/upload_file", + data={"file": FileStorage(open("./tests/polygon_attribute.vtp", "rb"))}, + ) + assert response.status_code == 201 + + route = f"/polygon_attribute_names" + + def get_full_data(): + return { + "input_geode_object": "PolygonalSurface3D", + "filename": "polygon_attribute.vtp", + } + + # Normal test with filename 'vertex_attribute.vtp' + response = client.post(route, json=get_full_data()) + assert response.status_code == 200 + polygon_attribute_names = response.json["polygon_attribute_names"] + assert type(polygon_attribute_names) is list + for polygon_attribute_name in polygon_attribute_names: + assert type(polygon_attribute_name) is str + + # Test all params + test_utils.test_route_wrong_params(client, route, get_full_data) + + def test_create_point(client): route = f"/create_point" get_full_data = lambda: {"x": 1, "y": 2, "z": 3} From bbb48cd81da26cd2354ebc17e26871bf0cffb309 Mon Sep 17 00:00:00 2001 From: JulienChampagnol Date: Wed, 27 Nov 2024 12:59:34 +0000 Subject: [PATCH 9/9] Apply prepare changes --- src/opengeodeweb_back/geode_objects.py | 1 + .../routes/blueprint_routes.py | 21 +++++++++++++------ src/opengeodeweb_back/test_utils.py | 8 ++++--- tests/test_routes.py | 12 +++++++---- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/opengeodeweb_back/geode_objects.py b/src/opengeodeweb_back/geode_objects.py index 03007cad..77d2ac30 100644 --- a/src/opengeodeweb_back/geode_objects.py +++ b/src/opengeodeweb_back/geode_objects.py @@ -10,6 +10,7 @@ # Local application imports + def geode_objects_dict(): return { "BRep": { diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 111cbd75..47bb4ab9 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -310,6 +310,7 @@ def create_point(): ) as file: vertex_attribute_names_json = json.load(file) + @routes.route( vertex_attribute_names_json["route"], methods=vertex_attribute_names_json["methods"], @@ -318,8 +319,12 @@ def vertex_attribute_names(): UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] utils_functions.validate_request(flask.request, vertex_attribute_names_json) - file_absolute_path = os.path.join(UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"])) - data = geode_functions.load(flask.request.json["input_geode_object"], file_absolute_path) + file_absolute_path = os.path.join( + UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"]) + ) + data = geode_functions.load( + flask.request.json["input_geode_object"], file_absolute_path + ) vertex_attribute_names = data.vertex_attribute_manager().attribute_names() return flask.make_response( @@ -330,13 +335,13 @@ def vertex_attribute_names(): ) - with open( os.path.join(schemas, "polygon_attribute_names.json"), "r", ) as file: polygon_attribute_names_json = json.load(file) + @routes.route( polygon_attribute_names_json["route"], methods=polygon_attribute_names_json["methods"], @@ -345,10 +350,14 @@ def polygon_attribute_names(): UPLOAD_FOLDER = flask.current_app.config["UPLOAD_FOLDER"] utils_functions.validate_request(flask.request, vertex_attribute_names_json) - file_absolute_path = os.path.join(UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"])) - data = geode_functions.load(flask.request.json["input_geode_object"], file_absolute_path) + file_absolute_path = os.path.join( + UPLOAD_FOLDER, werkzeug.utils.secure_filename(flask.request.json["filename"]) + ) + data = geode_functions.load( + flask.request.json["input_geode_object"], file_absolute_path + ) polygon_attribute_names = data.polygon_attribute_manager().attribute_names() - + return flask.make_response( { "polygon_attribute_names": polygon_attribute_names, diff --git a/src/opengeodeweb_back/test_utils.py b/src/opengeodeweb_back/test_utils.py index 75329ea6..0d4c6176 100644 --- a/src/opengeodeweb_back/test_utils.py +++ b/src/opengeodeweb_back/test_utils.py @@ -13,11 +13,13 @@ def test_route_wrong_params(client, route, get_full_data): assert response.status_code == 400 error_description = response.json["description"] assert error_description == f"Validation error: '{key}' is a required property" - + json = get_full_data() json["dumb_key"] = "dumb_value" response = client.post(route, json=json) assert response.status_code == 400 error_description = response.json["description"] - assert error_description == "Validation error: Additional properties are not allowed ('dumb_key' was unexpected)" - \ No newline at end of file + assert ( + error_description + == "Validation error: Additional properties are not allowed ('dumb_key' was unexpected)" + ) diff --git a/tests/test_routes.py b/tests/test_routes.py index 05e8e965..138cd435 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -44,6 +44,7 @@ def get_full_data(): # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) + def test_upload_file(client): response = client.put( f"/upload_file", @@ -63,7 +64,7 @@ def get_full_data(): } json = get_full_data() - response = client.post(route,json=json) + response = client.post(route, json=json) assert response.status_code == 200 has_missing_files = response.json["has_missing_files"] mandatory_files = response.json["mandatory_files"] @@ -75,6 +76,7 @@ def get_full_data(): # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) + def test_geographic_coordinate_systems(client): route = f"/geographic_coordinate_systems" get_full_data = lambda: {"input_geode_object": "BRep"} @@ -89,8 +91,9 @@ def test_geographic_coordinate_systems(client): # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) + def test_inspect_file(client): - route = f"/inspect_file" + route = f"/inspect_file" def get_full_data(): return { @@ -109,6 +112,7 @@ def get_full_data(): # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) + def test_geode_objects_and_output_extensions(client): route = "/geode_objects_and_output_extensions" @@ -159,6 +163,7 @@ def get_full_data(): # Test all params test_utils.test_route_wrong_params(client, route, get_full_data) + def test_vertex_attribute_names(client): response = client.put( f"/upload_file", @@ -185,7 +190,6 @@ def get_full_data(): test_utils.test_route_wrong_params(client, route, get_full_data) - def test_polygon_attribute_names(client): response = client.put( f"/upload_file", @@ -225,5 +229,5 @@ def test_create_point(client): id = response.json["id"] assert type(id) is str - # Test all params + # Test all params test_utils.test_route_wrong_params(client, route, get_full_data)