| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | import json | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | import re | 
					
						
							|  |  |  | from enum import Enum | 
					
						
							|  |  |  | from itertools import groupby | 
					
						
							|  |  |  | from pathlib import Path | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | from typing import Optional | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | from fastapi import FastAPI | 
					
						
							|  |  |  | from humps import camelize | 
					
						
							|  |  |  | from jinja2 import Template | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | from pydantic import BaseModel, Field | 
					
						
							|  |  |  | from slugify import slugify | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-28 14:27:56 -08:00
										 |  |  | from mealie.app import app | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | CWD = Path(__file__).parent | 
					
						
							|  |  |  | OUT_DIR = CWD / "output" | 
					
						
							|  |  |  | TEMPLATES_DIR = CWD / "templates" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | JS_DIR = OUT_DIR / "javascriptAPI" | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | JS_DIR.mkdir(exist_ok=True, parents=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RouteObject: | 
					
						
							|  |  |  |     def __init__(self, route_string) -> None: | 
					
						
							|  |  |  |         self.prefix = "/" + route_string.split("/")[1] | 
					
						
							| 
									
										
										
										
											2021-06-16 20:31:09 +02:00
										 |  |  |         self.route = "/" + route_string.split("/", 2)[2] | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |         self.js_route = self.route.replace("{", "${") | 
					
						
							|  |  |  |         self.parts = route_string.split("/")[1:] | 
					
						
							|  |  |  |         self.var = re.findall(r"\{(.*?)\}", route_string) | 
					
						
							|  |  |  |         self.is_function = "{" in self.route | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |         self.router_slug = slugify("_".join(self.parts[1:]), separator="_") | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |         self.router_camel = camelize(self.router_slug) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RequestType(str, Enum): | 
					
						
							|  |  |  |     get = "get" | 
					
						
							|  |  |  |     put = "put" | 
					
						
							|  |  |  |     post = "post" | 
					
						
							|  |  |  |     patch = "patch" | 
					
						
							|  |  |  |     delete = "delete" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | class ParameterIn(str, Enum): | 
					
						
							|  |  |  |     query = "query" | 
					
						
							|  |  |  |     path = "path" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RouterParameter(BaseModel): | 
					
						
							|  |  |  |     required: bool = False | 
					
						
							|  |  |  |     name: str | 
					
						
							|  |  |  |     location: ParameterIn = Field(..., alias="in") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RequestBody(BaseModel): | 
					
						
							|  |  |  |     required: bool = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | class HTTPRequest(BaseModel): | 
					
						
							|  |  |  |     request_type: RequestType | 
					
						
							|  |  |  |     description: str = "" | 
					
						
							|  |  |  |     summary: str | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |     requestBody: Optional[RequestBody] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     parameters: list[RouterParameter] = [] | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |     tags: list[str] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |     def list_as_js_object_string(self, parameters, braces=True): | 
					
						
							|  |  |  |         if len(parameters) == 0: | 
					
						
							|  |  |  |             return "" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if braces: | 
					
						
							|  |  |  |             return "{" + ", ".join(parameters) + "}" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             return ", ".join(parameters) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def payload(self): | 
					
						
							|  |  |  |         return "payload" if self.requestBody else "" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def function_args(self): | 
					
						
							|  |  |  |         all_params = [p.name for p in self.parameters] | 
					
						
							|  |  |  |         if self.requestBody: | 
					
						
							|  |  |  |             all_params.append("payload") | 
					
						
							|  |  |  |         return self.list_as_js_object_string(all_params) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def query_params(self): | 
					
						
							|  |  |  |         params = [param.name for param in self.parameters if param.location == ParameterIn.query] | 
					
						
							|  |  |  |         return self.list_as_js_object_string(params) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def path_params(self): | 
					
						
							|  |  |  |         params = [param.name for param in self.parameters if param.location == ParameterIn.path] | 
					
						
							|  |  |  |         return self.list_as_js_object_string(parameters=params, braces=False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |     @property | 
					
						
							|  |  |  |     def summary_camel(self): | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |         return camelize(slugify(self.summary)) | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def js_docs(self): | 
					
						
							|  |  |  |         return self.description.replace("\n", "  \n  * ") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class PathObject(BaseModel): | 
					
						
							|  |  |  |     route_object: RouteObject | 
					
						
							|  |  |  |     http_verbs: list[HTTPRequest] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     class Config: | 
					
						
							|  |  |  |         arbitrary_types_allowed = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def get_path_objects(app: FastAPI): | 
					
						
							|  |  |  |     paths = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for key, value in app.openapi().items(): | 
					
						
							|  |  |  |         if key == "paths": | 
					
						
							|  |  |  |             for key, value in value.items(): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 paths.append( | 
					
						
							|  |  |  |                     PathObject( | 
					
						
							|  |  |  |                         route_object=RouteObject(key), | 
					
						
							|  |  |  |                         http_verbs=[HTTPRequest(request_type=k, **v) for k, v in value.items()], | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return paths | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | def dump_open_api(app: FastAPI): | 
					
						
							|  |  |  |     """ Writes the Open API as JSON to a json file""" | 
					
						
							|  |  |  |     OPEN_API_FILE = CWD / "openapi.json" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with open(OPEN_API_FILE, "w") as f: | 
					
						
							|  |  |  |         f.write(json.dumps(app.openapi())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | def read_template(file: Path): | 
					
						
							|  |  |  |     with open(file, "r") as f: | 
					
						
							|  |  |  |         return f.read() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | def generate_python_templates(static_paths: list[PathObject], function_paths: list[PathObject]): | 
					
						
							|  |  |  |     PYTEST_TEMPLATE = TEMPLATES_DIR / "pytest_routes.j2" | 
					
						
							|  |  |  |     PYTHON_OUT_FILE = OUT_DIR / "app_routes.py" | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     template = Template(read_template(PYTEST_TEMPLATE)) | 
					
						
							|  |  |  |     content = template.render( | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |         paths={ | 
					
						
							|  |  |  |             "prefix": "/api", | 
					
						
							|  |  |  |             "static_paths": static_paths, | 
					
						
							|  |  |  |             "function_paths": function_paths, | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |     ) | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |     with open(PYTHON_OUT_FILE, "w") as f: | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |         f.write(content) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |     return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def generate_js_templates(paths: list[PathObject]): | 
					
						
							|  |  |  |     # Template Path | 
					
						
							|  |  |  |     JS_API_INTERFACE = TEMPLATES_DIR / "js_api_interface.j2" | 
					
						
							|  |  |  |     JS_INDEX = TEMPLATES_DIR / "js_index.j2" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     INTERFACES_DIR = JS_DIR / "interfaces" | 
					
						
							|  |  |  |     INTERFACES_DIR.mkdir(exist_ok=True, parents=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |     all_tags = [] | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |     for tag, tag_paths in groupby(paths, lambda x: x.http_verbs[0].tags[0]): | 
					
						
							|  |  |  |         file_name = slugify(tag, separator="-") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         tag = camelize(tag) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         tag_paths: list[PathObject] = list(tag_paths) | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |         template = Template(read_template(JS_API_INTERFACE)) | 
					
						
							|  |  |  |         content = template.render( | 
					
						
							|  |  |  |             paths={ | 
					
						
							|  |  |  |                 "prefix": "/api", | 
					
						
							|  |  |  |                 "static_paths": [x.route_object for x in tag_paths if not x.route_object.is_function], | 
					
						
							|  |  |  |                 "function_paths": [x.route_object for x in tag_paths if x.route_object.is_function], | 
					
						
							|  |  |  |                 "all_paths": tag_paths, | 
					
						
							|  |  |  |                 "export_name": tag, | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  |         tag: dict = {"camel": camelize(tag), "slug": file_name} | 
					
						
							|  |  |  |         all_tags.append(tag) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         with open(INTERFACES_DIR.joinpath(file_name + ".ts"), "w") as f: | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  |             f.write(content) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     template = Template(read_template(JS_INDEX)) | 
					
						
							|  |  |  |     content = template.render(files={"files": all_tags}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     with open(JS_DIR.joinpath("index.js"), "w") as f: | 
					
						
							|  |  |  |         f.write(content) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-01 19:24:05 -08:00
										 |  |  | def generate_template(app): | 
					
						
							|  |  |  |     dump_open_api(app) | 
					
						
							|  |  |  |     paths = get_path_objects(app) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static_paths = [x.route_object for x in paths if not x.route_object.is_function] | 
					
						
							|  |  |  |     function_paths = [x.route_object for x in paths if x.route_object.is_function] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static_paths.sort(key=lambda x: x.router_slug) | 
					
						
							|  |  |  |     function_paths.sort(key=lambda x: x.router_slug) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     generate_python_templates(static_paths, function_paths) | 
					
						
							|  |  |  |     generate_js_templates(paths) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-22 21:04:19 -08:00
										 |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     generate_template(app) |