Skip to content

Commit 9a1cade

Browse files
committed
feat(api): global error handling & direct response usage in create endpoints
1 parent d2a53df commit 9a1cade

File tree

3 files changed

+66
-118
lines changed

3 files changed

+66
-118
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ markupsafe~=3.0
3636
# flask
3737
# jinja2
3838
# werkzeug
39-
opengeode-core==15.27.4
39+
opengeode-core==15.29.0
4040
# via
4141
# -r requirements.in
4242
# geode-common

src/opengeodeweb_back/app.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,28 @@
11
"""Packages"""
2-
32
import argparse
43
import os
54
import time
65
from typing import Any
7-
86
import flask
97
import flask_cors # type: ignore
108
from flask import Flask, Response
119
from flask_cors import cross_origin
1210
from werkzeug.exceptions import HTTPException
13-
1411
from opengeodeweb_back import utils_functions, app_config
1512
from opengeodeweb_back.routes import blueprint_routes
1613
from opengeodeweb_back.routes.models import blueprint_models
1714
from opengeodeweb_back.routes.create import blueprint_create
1815
from opengeodeweb_microservice.database.connection import init_database
1916

20-
2117
""" Global config """
2218
app: Flask = flask.Flask(__name__)
2319

2420
""" Config variables """
2521
FLASK_DEBUG = True if os.environ.get("FLASK_DEBUG", default=None) == "True" else False
26-
2722
if FLASK_DEBUG == False:
2823
app.config.from_object(app_config.ProdConfig)
2924
else:
3025
app.config.from_object(app_config.DevConfig)
31-
3226
DEFAULT_HOST: str = app.config.get("DEFAULT_HOST") or "localhost"
3327
DEFAULT_PORT: int = int(app.config.get("DEFAULT_PORT") or 5000)
3428
DEFAULT_DATA_FOLDER_PATH: str = app.config.get("DEFAULT_DATA_FOLDER_PATH") or "./data"
@@ -39,19 +33,16 @@
3933
app.config.get("SECONDS_BETWEEN_SHUTDOWNS") or 60.0
4034
)
4135

42-
4336
app.register_blueprint(
4437
blueprint_routes.routes,
4538
url_prefix="/opengeodeweb_back",
4639
name="opengeodeweb_back",
4740
)
48-
4941
app.register_blueprint(
5042
blueprint_models.routes,
5143
url_prefix="/opengeodeweb_back/models",
5244
name="opengeodeweb_models",
5345
)
54-
5546
app.register_blueprint(
5647
blueprint_create.routes,
5748
url_prefix="/opengeodeweb_back/create",
@@ -63,11 +54,13 @@
6354
utils_functions.kill_task, SECONDS_BETWEEN_SHUTDOWNS, app
6455
)
6556

66-
6757
@app.errorhandler(HTTPException)
6858
def errorhandler(e: HTTPException) -> tuple[dict[str, Any], int] | Response:
6959
return utils_functions.handle_exception(e)
7060

61+
@app.errorhandler(Exception)
62+
def handle_generic_exception(e: Exception) -> Response:
63+
return flask.make_response({"error": str(e)}, 500)
7164

7265
@app.route(
7366
"/error",
@@ -77,20 +70,17 @@ def return_error() -> Response:
7770
flask.abort(500, f"Test")
7871
return flask.make_response({}, 500)
7972

80-
8173
@app.route("/", methods=["POST"])
8274
@cross_origin()
8375
def root() -> Response:
8476
return flask.make_response({}, 200)
8577

86-
8778
@app.route("/kill", methods=["POST"])
8879
@cross_origin()
8980
def kill() -> None:
9081
print("Manual server kill, shutting down...", flush=True)
9182
os._exit(0)
9283

93-
9484
def run_server() -> None:
9585
parser = argparse.ArgumentParser(
9686
prog="OpenGeodeWeb-Back", description="Backend server for OpenGeodeWeb"
@@ -133,31 +123,25 @@ def run_server() -> None:
133123
help="Number of minutes before the server times out",
134124
)
135125
args = parser.parse_args()
136-
137126
app.config.update(DATA_FOLDER_PATH=args.data_folder_path)
138127
app.config.update(UPLOAD_FOLDER=args.upload_folder_path)
139128
app.config.update(MINUTES_BEFORE_TIMEOUT=args.timeout)
140-
141129
flask_cors.CORS(app, origins=args.allowed_origins)
142-
143130
print(
144131
f"Host: {args.host}, Port: {args.port}, Debug: {args.debug}, "
145132
f"Data folder path: {args.data_folder_path}, Timeout: {args.timeout}, "
146133
f"Origins: {args.allowed_origins}",
147134
flush=True,
148135
)
149-
150136
db_filename: str = app.config.get("DATABASE_FILENAME") or "database.db"
151137
db_path = os.path.join(args.data_folder_path, db_filename)
152138
os.makedirs(os.path.dirname(db_path), exist_ok=True)
153139
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}"
154140
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
155141
init_database(app, db_filename)
156142
print(f"Database initialized at: {db_path}", flush=True)
157-
158143
app.run(debug=args.debug, host=args.host, port=args.port, ssl_context=SSL)
159144

160-
161145
# ''' Main '''
162146
if __name__ == "__main__":
163147
run_server()
Lines changed: 62 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
# Standard library imports
22
import json
33
import os
4-
from typing import Dict, Any, List, TypedDict, cast
5-
4+
from typing import Any, TypedDict, cast
65
# Third party imports
76
import flask
87
import opengeode
98
import werkzeug
10-
119
# Local application imports
1210
from opengeodeweb_back import geode_functions, utils_functions
1311
from opengeodeweb_microservice.database.data import Data
@@ -30,12 +28,12 @@ class CreatePointRequest(TypedDict):
3028

3129
class CreateAOIRequest(TypedDict):
3230
name: str
33-
points: List[Point]
31+
points: list[Point]
3432
z: float
3533

3634
# Load schema for point creation
3735
with open(os.path.join(schemas, "create_point.json"), "r") as file:
38-
create_point_json = cast(Dict[str, Any], json.load(file))
36+
create_point_json = cast(dict[str, Any], json.load(file))
3937

4038
@routes.route(
4139
create_point_json["route"],
@@ -46,50 +44,33 @@ def create_point() -> flask.Response:
4644
print(f"create_point : {flask.request=}", flush=True)
4745
utils_functions.validate_request(flask.request, create_point_json)
4846

49-
try:
50-
# Extract and validate data from request
51-
request_data = cast(CreatePointRequest, flask.request.json)
52-
name: str = request_data["name"]
53-
x: float = request_data["x"]
54-
y: float = request_data["y"]
55-
z: float = request_data["z"]
56-
57-
# Create the point set
58-
class_ = geode_functions.geode_object_class("PointSet3D")
59-
point_set = class_.create()
60-
builder = geode_functions.create_builder("PointSet3D", point_set)
61-
builder.create_point(opengeode.Point3D([x, y, z])) # type: ignore
62-
builder.set_name(name)
63-
64-
# Save and get info
65-
result = save_all_viewables_and_return_info(
66-
geode_object="PointSet3D",
67-
data=point_set,
68-
)
69-
70-
# Vérifier que binary_light_viewable est présent
71-
if "binary_light_viewable" not in result:
72-
raise ValueError("binary_light_viewable is missing in the result")
73-
74-
# Prepare response
75-
response: Dict[str, Any] = {
76-
"viewable_file_name": result["viewable_file_name"],
77-
"id": result["id"],
78-
"name": name,
79-
"native_file_name": result["native_file_name"],
80-
"object_type": result["object_type"],
81-
"geode_object": result["geode_object"],
82-
"binary_light_viewable": result["binary_light_viewable"],
83-
}
84-
85-
return flask.make_response(response, 200)
86-
87-
except Exception as e:
88-
return flask.make_response({"error": str(e)}, 500)
47+
# Extract and validate data from request
48+
request_data = cast(CreatePointRequest, flask.request.json)
49+
name: str = request_data["name"]
50+
x: float = request_data["x"]
51+
y: float = request_data["y"]
52+
z: float = request_data["z"]
53+
54+
# Create the point set
55+
class_ = geode_functions.geode_object_class("PointSet3D")
56+
point_set = class_.create()
57+
builder = geode_functions.create_builder("PointSet3D", point_set)
58+
builder.create_point(opengeode.Point3D([x, y, z]))
59+
builder.set_name(name)
60+
61+
# Save and get info
62+
result = save_all_viewables_and_return_info(
63+
geode_object="PointSet3D",
64+
data=point_set,
65+
)
66+
result["name"] = name
67+
if "binary_light_viewable" not in result:
68+
raise ValueError("binary_light_viewable is missing in the result")
69+
return flask.make_response(result, 200)
8970

9071
# Load schema for AOI creation
9172
with open(os.path.join(schemas, "create_aoi.json"), "r") as file:
92-
create_aoi_json = cast(Dict[str, Any], json.load(file))
73+
create_aoi_json = cast(dict[str, Any], json.load(file))
9374

9475
@routes.route(
9576
create_aoi_json["route"],
@@ -100,55 +81,38 @@ def create_aoi() -> flask.Response:
10081
print(f"create_aoi : {flask.request=}", flush=True)
10182
utils_functions.validate_request(flask.request, create_aoi_json)
10283

103-
try:
104-
# Extract and validate data from request
105-
request_data = cast(CreateAOIRequest, flask.request.json)
106-
name: str = request_data["name"]
107-
points: List[Point] = request_data["points"]
108-
z: float = request_data["z"]
109-
110-
# Create the edged curve
111-
class_ = geode_functions.geode_object_class("EdgedCurve3D")
112-
edged_curve = class_.create()
113-
builder = geode_functions.create_builder("EdgedCurve3D", edged_curve)
114-
builder.set_name(name)
115-
116-
# Create vertices first
117-
vertex_indices: List[int] = []
118-
for point in points:
119-
vertex_id = builder.create_point(opengeode.Point3D([point["x"], point["y"], z])) # type: ignore
120-
vertex_indices.append(vertex_id)
121-
122-
# Create edges between consecutive vertices and close the loop
123-
num_vertices = len(vertex_indices)
124-
for i in range(num_vertices):
125-
next_i = (i + 1) % num_vertices
126-
edge_id = builder.create_edge()
127-
builder.set_edge_vertex(opengeode.EdgeVertex(edge_id, 0), vertex_indices[i]) # type: ignore
128-
builder.set_edge_vertex(opengeode.EdgeVertex(edge_id, 1), vertex_indices[next_i]) # type: ignore
129-
130-
# Save and get info
131-
result = save_all_viewables_and_return_info(
132-
geode_object="EdgedCurve3D",
133-
data=edged_curve,
134-
)
135-
136-
# Vérifier que binary_light_viewable est présent
137-
if "binary_light_viewable" not in result:
138-
raise ValueError("binary_light_viewable is missing in the result")
139-
140-
# Prepare response
141-
response: Dict[str, Any] = {
142-
"viewable_file_name": result["viewable_file_name"],
143-
"id": result["id"],
144-
"name": name,
145-
"native_file_name": result["native_file_name"],
146-
"object_type": result["object_type"],
147-
"geode_object": result["geode_object"],
148-
"binary_light_viewable": result["binary_light_viewable"],
149-
}
150-
151-
return flask.make_response(response, 200)
152-
153-
except Exception as e:
154-
return flask.make_response({"error": str(e)}, 500)
84+
# Extract and validate data from request
85+
request_data = cast(CreateAOIRequest, flask.request.json)
86+
name: str = request_data["name"]
87+
points: list[Point] = request_data["points"]
88+
z: float = request_data["z"]
89+
90+
# Create the edged curve
91+
class_ = geode_functions.geode_object_class("EdgedCurve3D")
92+
edged_curve = class_.create()
93+
builder = geode_functions.create_builder("EdgedCurve3D", edged_curve)
94+
builder.set_name(name)
95+
96+
# Create vertices first
97+
vertex_indices: list[int] = []
98+
for point in points:
99+
vertex_id = builder.create_point(opengeode.Point3D([point["x"], point["y"], z]))
100+
vertex_indices.append(vertex_id)
101+
102+
# Create edges between consecutive vertices and close the loop
103+
num_vertices = len(vertex_indices)
104+
for i in range(num_vertices):
105+
next_i = (i + 1) % num_vertices
106+
edge_id = builder.create_edge()
107+
builder.set_edge_vertex(opengeode.EdgeVertex(edge_id, 0), vertex_indices[i])
108+
builder.set_edge_vertex(opengeode.EdgeVertex(edge_id, 1), vertex_indices[next_i])
109+
110+
# Save and get info
111+
result = save_all_viewables_and_return_info(
112+
geode_object="EdgedCurve3D",
113+
data=edged_curve,
114+
)
115+
result["name"] = name
116+
if "binary_light_viewable" not in result:
117+
raise ValueError("binary_light_viewable is missing in the result")
118+
return flask.make_response(result, 200)

0 commit comments

Comments
 (0)