Skip to content

Commit f45d455

Browse files
committed
Add template manager route
1 parent 2834ed9 commit f45d455

File tree

9 files changed

+113
-68
lines changed

9 files changed

+113
-68
lines changed

runtime/app.py

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
1-
import json
21
import logging
3-
import typing
42

53
import chalice.app
64
import chalicelib.config as config_module
75
import chalicelib.logger.slack as slack_logger
86
import chalicelib.route
97
import chalicelib.worker
108

11-
if typing.TYPE_CHECKING:
12-
import mypy_boto3_sqs.type_defs
13-
14-
SQSEventType = typing.TypedDict("SQSEventType", {"Records": list["mypy_boto3_sqs.type_defs.MessageTypeDef"]})
15-
else:
16-
SQSEventType = dict[str, typing.Any]
17-
18-
199
config = config_module.config
2010
app = chalice.app.Chalice(app_name="notico")
2111
app.log.setLevel(logging.INFO)
@@ -26,23 +16,5 @@
2616
app.log.setLevel(logging.DEBUG)
2717
app.log.warning("Slack logger is not configured")
2818

29-
chalicelib.route.register_route(app)
30-
31-
32-
@app.on_sqs_message(queue=config.infra.queue_name)
33-
def sqs_handler(event: chalice.app.SQSEvent) -> list[dict[str, typing.Any]]:
34-
parsed_event: SQSEventType = event.to_dict()
35-
app.log.info(f"{parsed_event=}")
36-
37-
results: list[dict[str, typing.Any]] = []
38-
for record in parsed_event["Records"]:
39-
try:
40-
worker_name = json.loads(record["body"])["worker"]
41-
result = chalicelib.worker.workers[worker_name](app=app, record=record)
42-
results.append(result)
43-
except Exception as e:
44-
app.log.error(f"Failed to handle event: {record}", exc_info=e)
45-
results.append({"error": "Failed to handle event"})
46-
47-
app.log.info(f"{results=}")
48-
return results
19+
chalicelib.route.register_blueprints(app)
20+
chalicelib.worker.register_worker(app)

runtime/chalicelib/route/__init__.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,23 @@
33

44
import chalice.app
55
import chalicelib.util.import_util as import_util
6-
import chalicelib.util.type_util as type_util
76

87

9-
def register_route(app: chalice.app.Chalice) -> None:
10-
registered_routes: dict[str, set[type_util.HttpMethodType]] = {}
8+
def register_blueprints(app: chalice.app.Chalice) -> None:
9+
blueprints: dict[str, chalice.app.Blueprint] = {}
1110

1211
for path in pathlib.Path(__file__).parent.glob("*.py"):
1312
if path.stem.startswith("__") or not (
1413
patterns := typing.cast(
15-
type_util.RouteCollectionType,
16-
getattr(
17-
import_util.load_module(path),
18-
"route_patterns",
19-
None,
20-
),
14+
dict[str, chalice.app.Blueprint],
15+
getattr(import_util.load_module(path), "blueprints", None),
2116
)
2217
):
2318
continue
2419

25-
for pattern, methods, handler in patterns:
26-
if pattern in registered_routes and (_mtd := registered_routes[pattern] & set(methods)):
27-
raise ValueError(f"Route {pattern} with methods {_mtd} is already registered")
20+
for url_prefix, blueprint in patterns.items():
21+
if url_prefix in blueprints:
22+
raise ValueError(f"URL Prefix {url_prefix} is already registered")
2823

29-
app.route(path=pattern, methods=methods)(handler)
30-
registered_routes.setdefault(pattern, set()).update(methods)
24+
app.register_blueprint(blueprint=blueprint, url_prefix=url_prefix)
25+
blueprints[url_prefix] = blueprint
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import chalicelib.util.type_util as type_util
1+
import chalice.app
22

3+
health_check_api = chalice.app.Blueprint(__name__)
34

5+
6+
@health_check_api.route("/readyz", methods=["GET"])
47
def readyz() -> dict[str, str]:
58
return {"status": "ok"}
69

710

11+
@health_check_api.route("/livez", methods=["GET"])
812
def livez() -> dict[str, str]:
913
return {"status": "ok"}
1014

1115

12-
route_patterns: type_util.RouteInfoType = [
13-
("/readyz", ["GET"], readyz),
14-
("/livez", ["GET"], livez),
15-
]
16+
blueprints: dict[str, chalice.app.Blueprint] = {"/health": health_check_api}

runtime/chalicelib/route/index.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import chalicelib.util.type_util as type_util
1+
import chalice.app
22

3+
index_api = chalice.app.Blueprint(__name__)
34

5+
6+
@index_api.route("/", methods=["GET"])
47
def index() -> dict[str, str]:
58
return {"service": "notico"}
69

710

8-
route_patterns: type_util.RouteInfoType = [
9-
("/", ["GET"], index),
10-
]
11+
blueprints: dict[str, chalice.app.Blueprint] = {"/": index_api}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import chalice
2+
import chalice.app
3+
import chalicelib.template_manager as template_manager
4+
import chalicelib.util.type_util as type_util
5+
6+
template_manager_api = chalice.app.Blueprint(__name__)
7+
8+
9+
@template_manager_api.route("/", methods=["GET"])
10+
def list_template_manager_services() -> dict[str, list[str]]:
11+
return {"template_managers": list(template_manager.template_managers.keys())}
12+
13+
14+
@template_manager_api.route("/{service_name}", methods=["GET"])
15+
def list_templates(service_name: str) -> dict[str, list[dict[str, str]]]:
16+
if service_name not in template_manager.template_managers:
17+
raise chalice.NotFoundError(f"Service {service_name} not found")
18+
return {"templates": [t.model_dump(mode="json") for t in template_manager.template_managers[service_name].list()]}
19+
20+
21+
@template_manager_api.route("/{service_name}/{code}", methods=["GET", "POST", "PUT", "DELETE"])
22+
def crud_template(service_name: str, code: str) -> dict[str, str]:
23+
request: chalice.app.Request = template_manager_api.current_request
24+
method: type_util.HttpMethodType = request.method.upper()
25+
payload: dict[str, str] | None = request.json_body if method in {"POST", "PUT"} else None
26+
27+
if not (template_mgr := template_manager.template_managers.get(service_name, None)):
28+
raise chalice.NotFoundError(f"Service {service_name} not found")
29+
30+
if method == "GET":
31+
if template_info := template_mgr.retrieve(code):
32+
return template_info.model_dump(mode="json")
33+
raise chalice.NotFoundError(f"Template {code} not found")
34+
elif method == "POST":
35+
if not payload:
36+
raise chalice.BadRequestError("Payload is required")
37+
return template_mgr.create(code, payload).model_dump(mode="json")
38+
elif method == "PUT":
39+
if not payload:
40+
raise chalice.BadRequestError("Payload is required")
41+
return template_mgr.update(code, payload).model_dump(mode="json")
42+
elif method == "DELETE":
43+
template_mgr.delete(code)
44+
return {"code": code}
45+
46+
raise chalice.NotFoundError(f"Method {method} is not allowed")
47+
48+
49+
blueprints: dict[str, chalice.app.Blueprint] = {"/template-manager": template_manager_api}

runtime/chalicelib/template_manager/__interface__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import typing
77

8+
import botocore.exceptions
89
import chalicelib.aws_resource as aws_resource
910
import chalicelib.template_manager.__interface__ as template_mgr_interface
1011
import chalicelib.util.type_util as type_util
@@ -62,9 +63,12 @@ def list(self) -> list[template_mgr_interface.TemplateInformation]:
6263
return [self.retrieve(code=f.split(sep=".")[0]) for f in self.resource.list_objects(filter_by_extension=True)]
6364

6465
@functools.lru_cache # noqa: B019
65-
def retrieve(self, code: str) -> template_mgr_interface.TemplateInformation:
66-
template_body: str = self.resource.download(code=code).decode(encoding="utf-8")
67-
return template_mgr_interface.TemplateInformation(code=code, template=template_body)
66+
def retrieve(self, code: str) -> template_mgr_interface.TemplateInformation | None:
67+
try:
68+
template_body: str = self.resource.download(code=code).decode(encoding="utf-8")
69+
return template_mgr_interface.TemplateInformation(code=code, template=template_body)
70+
except botocore.exceptions.ClientError:
71+
return None
6872

6973
def create(self, code: str, template_data: type_util.TemplateType) -> template_mgr_interface.TemplateInformation:
7074
self.check_template_valid(template_data=template_data)

runtime/chalicelib/template_manager/toast_alimtalk.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ def list(self) -> list[template_mgr_interface.TemplateInformation]:
2323
for t in self.client.get_template_list().templateListResponse.templates
2424
]
2525

26-
def retrieve(self, code: str) -> template_mgr_interface.TemplateInformation:
26+
def retrieve(self, code: str) -> template_mgr_interface.TemplateInformation | None:
2727
query_params = toast_alimtalk_client.TemplateListQueryRequest(templateCode=code)
28-
if not (t := self.client.get_template_list(query_params=query_params).templateListResponse.templates):
29-
raise FileNotFoundError
30-
return template_mgr_interface.TemplateInformation(code=t[0].templateCode, template=t[0].templateContent)
28+
if t := self.client.get_template_list(query_params=query_params).templateListResponse.templates:
29+
return template_mgr_interface.TemplateInformation(code=t[0].templateCode, template=t[0].templateContent)
30+
return None
3131

3232
def create(self, code: str, template_data: str) -> template_mgr_interface.TemplateInformation:
3333
raise NotImplementedError("Toast 콘솔에서 직접 템플릿을 생성해주세요.")

runtime/chalicelib/util/type_util.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import pydantic
44

55
HttpMethodType = typing.Literal["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"]
6-
RouteInfoType = tuple[str, list[HttpMethodType], typing.Callable]
7-
RouteCollectionType = list[RouteInfoType]
86

97
AllowedBasicValueTypes = str | int | float | bool | None
108
AllowedValueTypes = AllowedBasicValueTypes | list[AllowedBasicValueTypes] | dict[str, AllowedBasicValueTypes] | None
Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
1+
import json
12
import pathlib
23
import typing
34

45
import chalice.app
6+
import chalicelib.config as config_module
57
import chalicelib.util.import_util as import_util
68

7-
_WorkerCollectionType = dict[str, typing.Callable[[chalice.app.SQSEvent], dict[str, typing.Any]]]
9+
if typing.TYPE_CHECKING:
10+
import mypy_boto3_sqs.type_defs
11+
12+
SQSEventType = typing.TypedDict("SQSEventType", {"Records": list["mypy_boto3_sqs.type_defs.MessageTypeDef"]})
13+
else:
14+
SQSEventType = dict[str, typing.Any]
15+
16+
_WorkerCollectionType = dict[str, typing.Callable[[chalice.app.Chalice, chalice.app.SQSEvent], dict[str, typing.Any]]]
817
workers: _WorkerCollectionType = {}
918

1019
for _path in pathlib.Path(__file__).parent.glob("*.py"):
1120
if _path.stem.startswith("__") or not (
1221
_patterns := typing.cast(
1322
_WorkerCollectionType,
14-
getattr(
15-
import_util.load_module(_path),
16-
"worker_patterns",
17-
None,
18-
),
23+
getattr(import_util.load_module(_path), "worker_patterns", None),
1924
)
2025
):
2126
continue
2227

2328
if _duplicated := workers.keys() & _patterns.keys():
2429
raise ValueError(f"Worker {_duplicated} is already registered")
2530
workers.update(_patterns)
31+
32+
33+
def register_worker(app: chalice.app.Chalice) -> None:
34+
@app.on_sqs_message(queue=config_module.config.infra.queue_name)
35+
def sqs_handler(event: chalice.app.SQSEvent) -> list[dict[str, typing.Any]]:
36+
parsed_event: SQSEventType = event.to_dict()
37+
app.log.info(f"{parsed_event=}")
38+
39+
results: list[dict[str, typing.Any]] = []
40+
for record in parsed_event["Records"]:
41+
try:
42+
worker_name = json.loads(record["body"])["worker"]
43+
result = workers[worker_name](app, record)
44+
results.append(result)
45+
except Exception as e:
46+
app.log.error(f"Failed to handle event: {record}", exc_info=e)
47+
results.append({"error": "Failed to handle event"})
48+
49+
app.log.info(f"{results=}")
50+
return results

0 commit comments

Comments
 (0)