aletrn commited on
Commit
dcc25fd
·
1 Parent(s): 4d26ef2

[feat] add first working use of pydantic input validation

Browse files
events/payload_point2.json CHANGED
@@ -9,5 +9,6 @@
9
  "label": 0
10
  }],
11
  "zoom": 10,
12
- "source_type": "Satellite"
 
13
  }
 
9
  "label": 0
10
  }],
11
  "zoom": 10,
12
+ "source_type": "Satellite",
13
+ "debug": true
14
  }
requirements_dev.txt CHANGED
@@ -7,5 +7,6 @@ numpy
7
  onnxruntime
8
  opencv-python
9
  pillow
 
10
  rasterio
11
  requests
 
7
  onnxruntime
8
  opencv-python
9
  pillow
10
+ pydantic>=2.0.3
11
  rasterio
12
  requests
src/__init__.py CHANGED
@@ -2,7 +2,9 @@ from aws_lambda_powertools import Logger
2
  import os
3
  from pathlib import Path
4
 
 
 
5
 
6
  PROJECT_ROOT_FOLDER = Path(globals().get("__file__", "./_")).absolute().parent.parent
7
  MODEL_FOLDER = Path(os.path.join(PROJECT_ROOT_FOLDER, "models"))
8
- app_logger = Logger()
 
2
  import os
3
  from pathlib import Path
4
 
5
+ from src.utilities.constants import SERVICE_NAME
6
+
7
 
8
  PROJECT_ROOT_FOLDER = Path(globals().get("__file__", "./_")).absolute().parent.parent
9
  MODEL_FOLDER = Path(os.path.join(PROJECT_ROOT_FOLDER, "models"))
10
+ app_logger = Logger(service=SERVICE_NAME)
src/app.py CHANGED
@@ -1,18 +1,58 @@
1
  import json
2
  import time
3
  from http import HTTPStatus
4
- from typing import Dict
5
 
6
  from aws_lambda_powertools.event_handler import content_types
7
  from aws_lambda_powertools.utilities.typing import LambdaContext
 
8
 
9
  from src import app_logger
10
  from src.io.coordinates_pixel_conversion import get_latlng_to_pixel_coordinates
11
  from src.prediction_api.predictors import samexporter_predict
12
- from src.utilities.constants import CUSTOM_RESPONSE_MESSAGES
13
  from src.utilities.utilities import base64_decode
14
 
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  def get_response(status: int, start_time: float, request_id: str, response_body: Dict = None) -> str:
17
  """
18
  Return a response for frontend clients.
@@ -42,7 +82,7 @@ def get_response(status: int, start_time: float, request_id: str, response_body:
42
  return json.dumps(response)
43
 
44
 
45
- def get_parsed_bbox_points(request_input: Dict) -> Dict:
46
  app_logger.info(f"try to parsing input request {request_input}...")
47
  bbox = request_input["bbox"]
48
  app_logger.debug(f"request bbox: {type(bbox)}, value:{bbox}.")
@@ -67,7 +107,7 @@ def get_parsed_bbox_points(request_input: Dict) -> Dict:
67
  raise ValueError("valid prompt type is only 'point'")
68
 
69
  app_logger.debug(f"bbox => {bbox}.")
70
- app_logger.debug(f'## request_input-prompt updated => {request_input["prompt"]}.')
71
 
72
  app_logger.info(f"unpacking elaborated {request_input}...")
73
  return {
@@ -85,23 +125,7 @@ def lambda_handler(event: dict, context: LambdaContext):
85
  app_logger.info(f"event version: {event['version']}.")
86
 
87
  try:
88
- app_logger.info(f"event:{json.dumps(event)}...")
89
- app_logger.info(f"context:{context}...")
90
-
91
- try:
92
- body = event["body"]
93
- except Exception as e_constants1:
94
- app_logger.error(f"e_constants1:{e_constants1}.")
95
- body = event
96
-
97
- app_logger.debug(f"body, #1: {type(body)}, {body}...")
98
-
99
- if isinstance(body, str):
100
- body_decoded_str = base64_decode(body)
101
- app_logger.debug(f"body_decoded_str: {type(body_decoded_str)}, {body_decoded_str}...")
102
- body = json.loads(body_decoded_str)
103
-
104
- app_logger.info(f"body, #2: {type(body)}, {body}...")
105
 
106
  try:
107
  prompt_latlng = body["prompt"]
@@ -120,3 +144,28 @@ def lambda_handler(event: dict, context: LambdaContext):
120
 
121
  app_logger.info(f"response_dumped:{response}...")
122
  return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
  import time
3
  from http import HTTPStatus
4
+ from typing import Dict, List
5
 
6
  from aws_lambda_powertools.event_handler import content_types
7
  from aws_lambda_powertools.utilities.typing import LambdaContext
8
+ from aws_lambda_powertools.utilities.parser import BaseModel
9
 
10
  from src import app_logger
11
  from src.io.coordinates_pixel_conversion import get_latlng_to_pixel_coordinates
12
  from src.prediction_api.predictors import samexporter_predict
13
+ from src.utilities.constants import CUSTOM_RESPONSE_MESSAGES, DEFAULT_LOG_LEVEL
14
  from src.utilities.utilities import base64_decode
15
 
16
 
17
+ list_float = List[float]
18
+ llist_float = List[list_float]
19
+
20
+
21
+ class LatLngDict(BaseModel):
22
+ lat: float
23
+ lng: float
24
+
25
+
26
+ class RawBBox(BaseModel):
27
+ ne: LatLngDict
28
+ sw: LatLngDict
29
+
30
+
31
+ class RawPrompt(BaseModel):
32
+ type: str
33
+ data: LatLngDict
34
+ label: int = 0
35
+
36
+
37
+ class RawRequestInput(BaseModel):
38
+ bbox: RawBBox
39
+ prompt: RawPrompt
40
+ zoom: int | float
41
+ source_type: str = "Satellite"
42
+
43
+
44
+ class ParsedPrompt(BaseModel):
45
+ type: str
46
+ data: llist_float
47
+ label: int = 0
48
+
49
+
50
+ class ParsedRequestInput(BaseModel):
51
+ bbox: llist_float
52
+ prompt: ParsedPrompt
53
+ zoom: int | float
54
+
55
+
56
  def get_response(status: int, start_time: float, request_id: str, response_body: Dict = None) -> str:
57
  """
58
  Return a response for frontend clients.
 
82
  return json.dumps(response)
83
 
84
 
85
+ def get_parsed_bbox_points(request_input: RawRequestInput) -> Dict:
86
  app_logger.info(f"try to parsing input request {request_input}...")
87
  bbox = request_input["bbox"]
88
  app_logger.debug(f"request bbox: {type(bbox)}, value:{bbox}.")
 
107
  raise ValueError("valid prompt type is only 'point'")
108
 
109
  app_logger.debug(f"bbox => {bbox}.")
110
+ app_logger.debug(f'request_input-prompt updated => {request_input["prompt"]}.')
111
 
112
  app_logger.info(f"unpacking elaborated {request_input}...")
113
  return {
 
125
  app_logger.info(f"event version: {event['version']}.")
126
 
127
  try:
128
+ body = get_parsed_request_body(context, event)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
  try:
131
  prompt_latlng = body["prompt"]
 
144
 
145
  app_logger.info(f"response_dumped:{response}...")
146
  return response
147
+
148
+
149
+ def get_parsed_request_body(context, event):
150
+ app_logger.info(f"event:{json.dumps(event)}...")
151
+ app_logger.info(f"context:{context}...")
152
+ try:
153
+ body = event["body"]
154
+ except Exception as e_constants1:
155
+ app_logger.error(f"e_constants1:{e_constants1}.")
156
+ body = event
157
+ app_logger.debug(f"body, #1: {type(body)}, {body}...")
158
+ if isinstance(body, str):
159
+ body_decoded_str = base64_decode(body)
160
+ app_logger.debug(f"body_decoded_str: {type(body_decoded_str)}, {body_decoded_str}...")
161
+ body = json.loads(body_decoded_str)
162
+ app_logger.info(f"body, #2: {type(body)}, {body}...")
163
+ try:
164
+ log_level = 'DEBUG' if body['debug'] else DEFAULT_LOG_LEVEL
165
+ app_logger.warning(f"set logger level to DEBUG")
166
+ app_logger.setLevel(log_level)
167
+ except KeyError:
168
+ app_logger.warning(f"can't set log level, reset it...")
169
+ app_logger.setLevel(DEFAULT_LOG_LEVEL)
170
+ app_logger.warning(f"logger level is {app_logger.log_level}.")
171
+ return body
src/utilities/constants.py CHANGED
@@ -16,3 +16,5 @@ WKT_3857 = 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",S
16
  WKT_3857 += 'AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],'
17
  WKT_3857 += 'PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],EXTENSION["PROJ4",'
18
  WKT_3857 += '"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs"],AUTHORITY["EPSG","3857"]]'
 
 
 
16
  WKT_3857 += 'AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],'
17
  WKT_3857 += 'PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],EXTENSION["PROJ4",'
18
  WKT_3857 += '"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs"],AUTHORITY["EPSG","3857"]]'
19
+ SERVICE_NAME = "sam-gis"
20
+ DEFAULT_LOG_LEVEL = 'INFO'
src/utilities/type_hints.py CHANGED
@@ -1,3 +1,13 @@
1
  """custom type hints"""
 
 
 
2
  ts_dict_str2 = dict[str, str]
3
  ts_dict_str3 = dict[str, str, any]
 
 
 
 
 
 
 
 
1
  """custom type hints"""
2
+ from typing import TypedDict
3
+
4
+
5
  ts_dict_str2 = dict[str, str]
6
  ts_dict_str3 = dict[str, str, any]
7
+ ts_ddict1 = dict[str, dict[str, any], dict, dict, any]
8
+ # ts_list_float = list[float, float]
9
+
10
+
11
+ class PixelCoordinate(TypedDict):
12
+ x: int
13
+ y: int