[feat] handle better error responses
Browse files- README.md +7 -4
- dockerfiles/{dockerfile-base-webserver → dockerfile-lambda-gdal-runner} +0 -2
- dockerfiles/dockerfile-samgeo-api +6 -9
- requirements_dev.txt +2 -2
- src/app.py +29 -13
README.md
CHANGED
@@ -13,21 +13,24 @@ Build the docker image:
|
|
13 |
# clean any old active containers
|
14 |
docker stop $(docker ps -a -q); docker rm $(docker ps -a -q)
|
15 |
|
16 |
-
# build the image
|
17 |
-
docker build . --tag
|
|
|
|
|
|
|
18 |
```
|
19 |
|
20 |
Run the container (keep it on background) and show logs
|
21 |
|
22 |
```bash
|
23 |
-
docker run -d --name
|
24 |
```
|
25 |
|
26 |
Test it with curl:
|
27 |
|
28 |
```bash
|
29 |
curl -X 'POST' \
|
30 |
-
'http://localhost:
|
31 |
-H 'accept: application/json' \
|
32 |
-d '{}'
|
33 |
```
|
|
|
13 |
# clean any old active containers
|
14 |
docker stop $(docker ps -a -q); docker rm $(docker ps -a -q)
|
15 |
|
16 |
+
# build the base docker image with the docker aws repository tag
|
17 |
+
docker build . -f dockerfiles/dockerfile-lambda-gdal-runner --tag 686901913580.dkr.ecr.eu-west-1.amazonaws.com/lambda-gdal-runner
|
18 |
+
|
19 |
+
# build the final docker image
|
20 |
+
docker build . -f dockerfiles/dockerfile-samgeo-api --tag lambda-samgeo-api --progress=plain
|
21 |
```
|
22 |
|
23 |
Run the container (keep it on background) and show logs
|
24 |
|
25 |
```bash
|
26 |
+
docker run -d --name lambda-samgeo-api -p 8080:8080 lambda-samgeo-api; docker logs -f lambda-samgeo-api
|
27 |
```
|
28 |
|
29 |
Test it with curl:
|
30 |
|
31 |
```bash
|
32 |
curl -X 'POST' \
|
33 |
+
'http://localhost:8080/infer_samgeo' \
|
34 |
-H 'accept: application/json' \
|
35 |
-d '{}'
|
36 |
```
|
dockerfiles/{dockerfile-base-webserver → dockerfile-lambda-gdal-runner}
RENAMED
@@ -8,7 +8,6 @@ ARG RIE="https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/l
|
|
8 |
# Set working directory to function root directory
|
9 |
WORKDIR ${LAMBDA_TASK_ROOT}
|
10 |
COPY requirements_dev.txt ${LAMBDA_TASK_ROOT}/requirements_dev.txt
|
11 |
-
COPY ./src ${LAMBDA_TASK_ROOT}/src
|
12 |
|
13 |
RUN apt update && apt install -y curl python3-pip
|
14 |
RUN python -m pip install -r ${LAMBDA_TASK_ROOT}/requirements_dev.txt --target ${LAMBDA_TASK_ROOT}
|
@@ -21,4 +20,3 @@ RUN chmod +x /lambda-entrypoint.sh
|
|
21 |
RUN ls -l /lambda-entrypoint.sh
|
22 |
|
23 |
ENTRYPOINT ["/lambda-entrypoint.sh"]
|
24 |
-
CMD [ "src.app.lambda_handler" ]
|
|
|
8 |
# Set working directory to function root directory
|
9 |
WORKDIR ${LAMBDA_TASK_ROOT}
|
10 |
COPY requirements_dev.txt ${LAMBDA_TASK_ROOT}/requirements_dev.txt
|
|
|
11 |
|
12 |
RUN apt update && apt install -y curl python3-pip
|
13 |
RUN python -m pip install -r ${LAMBDA_TASK_ROOT}/requirements_dev.txt --target ${LAMBDA_TASK_ROOT}
|
|
|
20 |
RUN ls -l /lambda-entrypoint.sh
|
21 |
|
22 |
ENTRYPOINT ["/lambda-entrypoint.sh"]
|
|
dockerfiles/dockerfile-samgeo-api
CHANGED
@@ -1,14 +1,13 @@
|
|
1 |
FROM 686901913580.dkr.ecr.eu-west-1.amazonaws.com/lambda-gdal-runner:latest
|
2 |
|
|
|
3 |
ARG LAMBDA_TASK_ROOT="/var/task"
|
4 |
ARG PYTHONPATH="${LAMBDA_TASK_ROOT}:${PYTHONPATH}:/usr/local/lib/python3/dist-packages"
|
5 |
|
|
|
|
|
6 |
COPY ./src ${LAMBDA_TASK_ROOT}/src
|
7 |
|
8 |
-
COPY .env ${LAMBDA_TASK_ROOT}
|
9 |
-
RUN ls -l ${LAMBDA_TASK_ROOT}/.env
|
10 |
-
RUN . ${LAMBDA_TASK_ROOT}/.env
|
11 |
-
|
12 |
RUN ls -l /usr/bin/which
|
13 |
RUN /usr/bin/which python
|
14 |
RUN python -v
|
@@ -19,13 +18,11 @@ RUN ls -l ${LAMBDA_TASK_ROOT}
|
|
19 |
RUN ls -ld ${LAMBDA_TASK_ROOT}
|
20 |
RUN python -c "import sys; print(sys.path)"
|
21 |
RUN python -c "import osgeo"
|
22 |
-
RUN python -c "import rasterio"
|
23 |
RUN python -c "import awslambdaric"
|
24 |
RUN python -m pip list
|
25 |
RUN python -m pip freeze
|
26 |
RUN df -h
|
27 |
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
CMD [ "src.surferdtm_prediction_api.app.lambda_handler" ]
|
|
|
1 |
FROM 686901913580.dkr.ecr.eu-west-1.amazonaws.com/lambda-gdal-runner:latest
|
2 |
|
3 |
+
# Include global arg in this stage of the build
|
4 |
ARG LAMBDA_TASK_ROOT="/var/task"
|
5 |
ARG PYTHONPATH="${LAMBDA_TASK_ROOT}:${PYTHONPATH}:/usr/local/lib/python3/dist-packages"
|
6 |
|
7 |
+
# Set working directory to function root directory
|
8 |
+
WORKDIR ${LAMBDA_TASK_ROOT}
|
9 |
COPY ./src ${LAMBDA_TASK_ROOT}/src
|
10 |
|
|
|
|
|
|
|
|
|
11 |
RUN ls -l /usr/bin/which
|
12 |
RUN /usr/bin/which python
|
13 |
RUN python -v
|
|
|
18 |
RUN ls -ld ${LAMBDA_TASK_ROOT}
|
19 |
RUN python -c "import sys; print(sys.path)"
|
20 |
RUN python -c "import osgeo"
|
21 |
+
# RUN python -c "import rasterio"
|
22 |
RUN python -c "import awslambdaric"
|
23 |
RUN python -m pip list
|
24 |
RUN python -m pip freeze
|
25 |
RUN df -h
|
26 |
|
27 |
+
ENTRYPOINT ["/lambda-entrypoint.sh"]
|
28 |
+
CMD [ "src.app.lambda_handler" ]
|
|
|
|
requirements_dev.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
awslambdaric
|
2 |
aws_lambda_powertools
|
3 |
fastjsonschema
|
4 |
-
pydantic
|
5 |
jmespath
|
6 |
-
|
|
|
|
1 |
awslambdaric
|
2 |
aws_lambda_powertools
|
3 |
fastjsonschema
|
|
|
4 |
jmespath
|
5 |
+
pydantic
|
6 |
+
requests
|
src/app.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1 |
import json
|
2 |
import time
|
|
|
3 |
from aws_lambda_powertools import Logger
|
|
|
4 |
from aws_lambda_powertools.utilities.typing import LambdaContext
|
5 |
from pydantic import BaseModel, ValidationError
|
6 |
|
@@ -10,18 +12,27 @@ from src.utilities.type_hints import input_floatlist, input_floatlist2
|
|
10 |
logger = Logger()
|
11 |
|
12 |
|
|
|
|
|
|
|
|
|
|
|
13 |
class BBoxWithPointInput(BaseModel):
|
14 |
bbox: input_floatlist
|
15 |
points: input_floatlist2
|
|
|
|
|
|
|
16 |
|
17 |
|
18 |
-
def get_response(status: int, start_time: float, output:
|
19 |
"""
|
20 |
Return a response for frontend clients.
|
21 |
|
22 |
Args:
|
23 |
status: status response
|
24 |
start_time: request start time (float)
|
|
|
25 |
output: dict we embed into our response
|
26 |
|
27 |
Returns:
|
@@ -29,17 +40,22 @@ def get_response(status: int, start_time: float, output: BaseModel = None) -> di
|
|
29 |
|
30 |
"""
|
31 |
messages = {200: "ok", 422: "validation error", 500: "internal server error"}
|
32 |
-
body = {
|
33 |
if status == 200:
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
40 |
|
41 |
|
42 |
-
def lambda_handler(event: dict, context: LambdaContext)
|
43 |
logger.info(f"start with aws_request_id:{context.aws_request_id}.")
|
44 |
start_time = time.time()
|
45 |
try:
|
@@ -49,13 +65,13 @@ def lambda_handler(event: dict, context: LambdaContext) -> dict[str]:
|
|
49 |
try:
|
50 |
bbox_points = BBoxWithPointInput(bbox=event["bbox"], points=event["points"])
|
51 |
logger.info(f"validation ok, bbox_points:{bbox_points}...")
|
52 |
-
response = get_response(
|
53 |
except ValidationError as ve:
|
54 |
logger.error(f"validation error:{ve}.")
|
55 |
-
response = get_response(422, start_time)
|
56 |
except Exception as e:
|
57 |
logger.error(f"exception:{e}.")
|
58 |
-
response = get_response(500, start_time)
|
59 |
|
60 |
-
logger.info(f"
|
61 |
return response
|
|
|
1 |
import json
|
2 |
import time
|
3 |
+
from http import HTTPStatus
|
4 |
from aws_lambda_powertools import Logger
|
5 |
+
from aws_lambda_powertools.event_handler import content_types, Response
|
6 |
from aws_lambda_powertools.utilities.typing import LambdaContext
|
7 |
from pydantic import BaseModel, ValidationError
|
8 |
|
|
|
12 |
logger = Logger()
|
13 |
|
14 |
|
15 |
+
class JSONResponse(Response):
|
16 |
+
def response_to_json(self):
|
17 |
+
return json.dumps(self.__dict__)
|
18 |
+
|
19 |
+
|
20 |
class BBoxWithPointInput(BaseModel):
|
21 |
bbox: input_floatlist
|
22 |
points: input_floatlist2
|
23 |
+
duration_run: float = 0
|
24 |
+
message: str = ""
|
25 |
+
request_id: str = ""
|
26 |
|
27 |
|
28 |
+
def get_response(status: int, start_time: float, request_id: str, output: BBoxWithPointInput = None) -> str:
|
29 |
"""
|
30 |
Return a response for frontend clients.
|
31 |
|
32 |
Args:
|
33 |
status: status response
|
34 |
start_time: request start time (float)
|
35 |
+
request_id: str
|
36 |
output: dict we embed into our response
|
37 |
|
38 |
Returns:
|
|
|
40 |
|
41 |
"""
|
42 |
messages = {200: "ok", 422: "validation error", 500: "internal server error"}
|
43 |
+
body = f"{messages[status]}, request_id: {request_id}."
|
44 |
if status == 200:
|
45 |
+
output.duration_run = time.time() - start_time
|
46 |
+
output.message = messages[status]
|
47 |
+
output.request_id = request_id
|
48 |
+
body = output.model_dump_json()
|
49 |
+
response = JSONResponse(
|
50 |
+
status_code=status,
|
51 |
+
content_type=content_types.APPLICATION_JSON if status == 200 else content_types.TEXT_PLAIN,
|
52 |
+
body=body
|
53 |
+
)
|
54 |
+
logger.info(f"response type:{type(response)} => {response}.")
|
55 |
+
return response.response_to_json()
|
56 |
|
57 |
|
58 |
+
def lambda_handler(event: dict, context: LambdaContext):
|
59 |
logger.info(f"start with aws_request_id:{context.aws_request_id}.")
|
60 |
start_time = time.time()
|
61 |
try:
|
|
|
65 |
try:
|
66 |
bbox_points = BBoxWithPointInput(bbox=event["bbox"], points=event["points"])
|
67 |
logger.info(f"validation ok, bbox_points:{bbox_points}...")
|
68 |
+
response = get_response(HTTPStatus.OK.value, start_time, context.aws_request_id, bbox_points)
|
69 |
except ValidationError as ve:
|
70 |
logger.error(f"validation error:{ve}.")
|
71 |
+
response = get_response(422, start_time, context.aws_request_id)
|
72 |
except Exception as e:
|
73 |
logger.error(f"exception:{e}.")
|
74 |
+
response = get_response(500, start_time, context.aws_request_id)
|
75 |
|
76 |
+
logger.info(f"response_dumped:{response}...")
|
77 |
return response
|