update development scripts
This commit is contained in:
parent
791211f787
commit
0e8e2971d0
7 changed files with 171 additions and 70 deletions
|
@ -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
|
Loading…
Reference in a new issue