mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 02:03:35 -04:00 
			
		
		
		
	update development scripts
This commit is contained in:
		| @@ -1,28 +1,22 @@ | ||||
| import json | ||||
| import re | ||||
| from enum import Enum | ||||
| from itertools import groupby | ||||
| from pathlib import Path | ||||
| from typing import Optional | ||||
|  | ||||
| import slugify | ||||
| from fastapi import FastAPI | ||||
| from humps import camelize | ||||
| from jinja2 import Template | ||||
| from mealie.app import app | ||||
| from pydantic import BaseModel | ||||
| from pydantic import BaseModel, Field | ||||
| from slugify import slugify | ||||
|  | ||||
| CWD = Path(__file__).parent | ||||
| OUT_DIR = CWD / "output" | ||||
| OUT_FILE = OUT_DIR / "app_routes.py" | ||||
|  | ||||
| JS_DIR = OUT_DIR / "javascriptAPI" | ||||
| JS_OUT_FILE = JS_DIR / "apiRoutes.js" | ||||
| TEMPLATES_DIR = CWD / "templates" | ||||
|  | ||||
| PYTEST_TEMPLATE = TEMPLATES_DIR / "pytest_routes.j2" | ||||
| JS_REQUESTS = TEMPLATES_DIR / "js_requests.j2" | ||||
| JS_ROUTES = TEMPLATES_DIR / "js_routes.j2" | ||||
| JS_INDEX = TEMPLATES_DIR / "js_index.j2" | ||||
|  | ||||
| JS_DIR = OUT_DIR / "javascriptAPI" | ||||
| JS_DIR.mkdir(exist_ok=True, parents=True) | ||||
|  | ||||
|  | ||||
| @@ -34,17 +28,9 @@ class RouteObject: | ||||
|         self.parts = route_string.split("/")[1:] | ||||
|         self.var = re.findall(r"\{(.*?)\}", route_string) | ||||
|         self.is_function = "{" in self.route | ||||
|         self.router_slug = slugify.slugify("_".join(self.parts[1:]), separator="_") | ||||
|         self.router_slug = slugify("_".join(self.parts[1:]), separator="_") | ||||
|         self.router_camel = camelize(self.router_slug) | ||||
|  | ||||
|     def __repr__(self) -> str: | ||||
|         return f"""Route: {self.route} | ||||
| Parts: {self.parts} | ||||
| Function: {self.is_function} | ||||
| Var: {self.var} | ||||
| Slug: {self.router_slug} | ||||
| """ | ||||
|  | ||||
|  | ||||
| class RequestType(str, Enum): | ||||
|     get = "get" | ||||
| @@ -54,15 +40,59 @@ class RequestType(str, Enum): | ||||
|     delete = "delete" | ||||
|  | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
| class HTTPRequest(BaseModel): | ||||
|     request_type: RequestType | ||||
|     description: str = "" | ||||
|     summary: str | ||||
|     requestBody: Optional[RequestBody] | ||||
|  | ||||
|     parameters: list[RouterParameter] = [] | ||||
|     tags: list[str] | ||||
|  | ||||
|     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) | ||||
|  | ||||
|     @property | ||||
|     def summary_camel(self): | ||||
|         return camelize(self.summary) | ||||
|         return camelize(slugify(self.summary)) | ||||
|  | ||||
|     @property | ||||
|     def js_docs(self): | ||||
| @@ -94,40 +124,68 @@ def get_path_objects(app: FastAPI): | ||||
|     return paths | ||||
|  | ||||
|  | ||||
| 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())) | ||||
|  | ||||
|  | ||||
| def read_template(file: Path): | ||||
|     with open(file, "r") as f: | ||||
|         return f.read() | ||||
|  | ||||
|  | ||||
| def generate_template(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) | ||||
| 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" | ||||
|  | ||||
|     template = Template(read_template(PYTEST_TEMPLATE)) | ||||
|     content = template.render(paths={"prefix": "/api", "static_paths": static_paths, "function_paths": function_paths}) | ||||
|     with open(OUT_FILE, "w") as f: | ||||
|     content = template.render( | ||||
|         paths={ | ||||
|             "prefix": "/api", | ||||
|             "static_paths": static_paths, | ||||
|             "function_paths": function_paths, | ||||
|         } | ||||
|     ) | ||||
|     with open(PYTHON_OUT_FILE, "w") as f: | ||||
|         f.write(content) | ||||
|  | ||||
|     template = Template(read_template(JS_ROUTES)) | ||||
|     content = template.render( | ||||
|         paths={"prefix": "/api", "static_paths": static_paths, "function_paths": function_paths, "all_paths": paths} | ||||
|     ) | ||||
|     with open(JS_OUT_FILE, "w") as f: | ||||
|         f.write(content) | ||||
|     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) | ||||
|  | ||||
|     all_tags = [] | ||||
|     for k, g in groupby(paths, lambda x: x.http_verbs[0].tags[0]): | ||||
|         template = Template(read_template(JS_REQUESTS)) | ||||
|         content = template.render(paths={"all_paths": list(g), "export_name": camelize(k)}) | ||||
|     for tag, tag_paths in groupby(paths, lambda x: x.http_verbs[0].tags[0]): | ||||
|         file_name = slugify(tag, separator="-") | ||||
|  | ||||
|         all_tags.append(camelize(k)) | ||||
|         tag = camelize(tag) | ||||
|  | ||||
|         with open(JS_DIR.joinpath(camelize(k) + ".js"), "w") as f: | ||||
|         tag_paths: list[PathObject] = list(tag_paths) | ||||
|  | ||||
|         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, | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         tag: dict = {"camel": camelize(tag), "slug": file_name} | ||||
|         all_tags.append(tag) | ||||
|  | ||||
|         with open(INTERFACES_DIR.joinpath(file_name + ".ts"), "w") as f: | ||||
|             f.write(content) | ||||
|  | ||||
|     template = Template(read_template(JS_INDEX)) | ||||
| @@ -137,5 +195,19 @@ def generate_template(app): | ||||
|         f.write(content) | ||||
|  | ||||
|  | ||||
| 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) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     generate_template(app) | ||||
|   | ||||
							
								
								
									
										1
									
								
								dev/scripts/openapi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								dev/scripts/openapi.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										17
									
								
								dev/scripts/templates/js_api_interface.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								dev/scripts/templates/js_api_interface.j2
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| import { requests } from "../requests"; | ||||
|  | ||||
| const prefix = '{{paths.prefix}}' | ||||
|  | ||||
| const routes = { {% for path in paths.static_paths %} | ||||
|   {{ path.router_camel }}: `${prefix}{{ path.route }}`,{% endfor %} | ||||
| {% for path in paths.function_paths  %} | ||||
|   {{path.router_camel}}: ({{path.var|join(", ")}}) => `${prefix}{{ path.js_route }}`,{% endfor %} | ||||
| } | ||||
|  | ||||
| export const {{paths.export_name}}API = { {% for path in paths.all_paths  %} {% for verb in path.http_verbs  %} | ||||
|    {% if verb.js_docs %}/** {{ verb.js_docs }}  | ||||
|    */ {% endif %} | ||||
|   async {{ verb.summary_camel }}({{ verb.function_args() }}) { | ||||
|     return await requests.{{ verb.request_type.value }}(routes.{{ path.route_object.router_camel }}{% if path.route_object.is_function %}({{verb.path_params()}}){% endif %}, {{ verb.query_params() }} {{ verb.payload() }}) | ||||
|   }, {% endfor %} {% endfor %} | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| {% for api in files.files  %} | ||||
| import { {{ api }}API } from "./{{api}}.js" {% endfor %} | ||||
| import { {{ api.camel }}API } from "./interfaces/{{ api.slug }}" {% endfor %} | ||||
|  | ||||
| export const api = { | ||||
| {% for api in files.files  %} | ||||
|     {{api}}: {{api}}API, {% endfor %} | ||||
|     {{api.camel}}: {{api.camel}}API, {% endfor %} | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| // This Content is Auto Generated | ||||
| import { API_ROUTES } from "./apiRoutes" | ||||
|  | ||||
| export const {{paths.export_name}}API = { {% for path in paths.all_paths  %} {% for verb in path.http_verbs  %} {% if path.route_object.is_function %} | ||||
|   /** {{ verb.js_docs }} {% for v in path.route_object.var %} | ||||
|    * @param {{ v }} {% endfor %} | ||||
|    */ | ||||
|   {{ verb.summary_camel }}({{path.route_object.var|join(", ")}}) { | ||||
|     const response = await apiReq.{{ verb.request_type.value }}(API_ROUTES.{{ path.route_object.router_camel }}({{path.route_object.var|join(", ")}})) | ||||
|     return response.data | ||||
|   }, {% else %} | ||||
|   /** {{ verb.js_docs }} {% for v in path.route_object.var %} | ||||
|    * @param {{ v }} {% endfor %} | ||||
|    */ | ||||
|   {{ verb.summary_camel }}() { | ||||
|     const response = await apiReq.{{ verb.request_type.value }}(API_ROUTES.{{ path.route_object.router_camel }}) | ||||
|     return response.data | ||||
|   },{% endif %} {% endfor %} {% endfor %} | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| // This Content is Auto Generated | ||||
| const prefix = '{{paths.prefix}}' | ||||
| export const API_ROUTES = { {% for path in paths.static_paths %} | ||||
|   {{ path.router_camel }}: `${prefix}{{ path.route }}`,{% endfor %} | ||||
| {% for path in paths.function_paths  %} | ||||
|   {{path.router_camel}}: ({{path.var|join(", ")}}) => `${prefix}{{ path.js_route }}`,{% endfor %} | ||||
| } | ||||
							
								
								
									
										37
									
								
								dev/scripts/types_gen.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								dev/scripts/types_gen.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| from pathlib import Path | ||||
|  | ||||
| from pydantic2ts import generate_typescript_defs | ||||
|  | ||||
| CWD = Path(__file__).parent | ||||
|  | ||||
| PROJECT_DIR = Path(__file__).parent.parent.parent | ||||
| SCHEMA_PATH = Path("/Users/hayden/Projects/Vue/mealie/mealie/schema/") | ||||
|  | ||||
| TYPES_DIR = CWD / "output" / "types" / "api-types" | ||||
|  | ||||
|  | ||||
| def path_to_module(path: Path): | ||||
|     path: str = str(path) | ||||
|  | ||||
|     path = path.removeprefix(str(PROJECT_DIR)) | ||||
|     path = path.removeprefix("/") | ||||
|     path = path.replace("/", ".") | ||||
|  | ||||
|     return path | ||||
|  | ||||
|  | ||||
| for module in SCHEMA_PATH.iterdir(): | ||||
|  | ||||
|     if not module.is_dir() or not module.joinpath("__init__.py").is_file(): | ||||
|         continue | ||||
|  | ||||
|     ts_out_name = module.name.replace("_", "-") + ".ts" | ||||
|  | ||||
|     out_path = TYPES_DIR.joinpath(ts_out_name) | ||||
|  | ||||
|     print(module) | ||||
|     try: | ||||
|         path_as_module = path_to_module(module) | ||||
|         generate_typescript_defs(path_as_module, str(out_path), exclude=("CamelModel")) | ||||
|     except Exception: | ||||
|         pass | ||||
		Reference in New Issue
	
	Block a user