update development scripts

This commit is contained in:
hay-kot 2021-08-01 19:24:05 -08:00
parent 791211f787
commit 0e8e2971d0
7 changed files with 171 additions and 70 deletions

View File

@ -1,28 +1,22 @@
import json
import re import re
from enum import Enum from enum import Enum
from itertools import groupby from itertools import groupby
from pathlib import Path from pathlib import Path
from typing import Optional
import slugify
from fastapi import FastAPI from fastapi import FastAPI
from humps import camelize from humps import camelize
from jinja2 import Template from jinja2 import Template
from mealie.app import app from mealie.app import app
from pydantic import BaseModel from pydantic import BaseModel, Field
from slugify import slugify
CWD = Path(__file__).parent CWD = Path(__file__).parent
OUT_DIR = CWD / "output" 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" TEMPLATES_DIR = CWD / "templates"
PYTEST_TEMPLATE = TEMPLATES_DIR / "pytest_routes.j2" JS_DIR = OUT_DIR / "javascriptAPI"
JS_REQUESTS = TEMPLATES_DIR / "js_requests.j2"
JS_ROUTES = TEMPLATES_DIR / "js_routes.j2"
JS_INDEX = TEMPLATES_DIR / "js_index.j2"
JS_DIR.mkdir(exist_ok=True, parents=True) JS_DIR.mkdir(exist_ok=True, parents=True)
@ -34,17 +28,9 @@ class RouteObject:
self.parts = route_string.split("/")[1:] self.parts = route_string.split("/")[1:]
self.var = re.findall(r"\{(.*?)\}", route_string) self.var = re.findall(r"\{(.*?)\}", route_string)
self.is_function = "{" in self.route 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) 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): class RequestType(str, Enum):
get = "get" get = "get"
@ -54,15 +40,59 @@ class RequestType(str, Enum):
delete = "delete" 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): class HTTPRequest(BaseModel):
request_type: RequestType request_type: RequestType
description: str = "" description: str = ""
summary: str summary: str
requestBody: Optional[RequestBody]
parameters: list[RouterParameter] = []
tags: list[str] 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 @property
def summary_camel(self): def summary_camel(self):
return camelize(self.summary) return camelize(slugify(self.summary))
@property @property
def js_docs(self): def js_docs(self):
@ -94,40 +124,68 @@ def get_path_objects(app: FastAPI):
return paths 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): def read_template(file: Path):
with open(file, "r") as f: with open(file, "r") as f:
return f.read() return f.read()
def generate_template(app): def generate_python_templates(static_paths: list[PathObject], function_paths: list[PathObject]):
paths = get_path_objects(app) PYTEST_TEMPLATE = TEMPLATES_DIR / "pytest_routes.j2"
PYTHON_OUT_FILE = OUT_DIR / "app_routes.py"
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)
template = Template(read_template(PYTEST_TEMPLATE)) template = Template(read_template(PYTEST_TEMPLATE))
content = template.render(paths={"prefix": "/api", "static_paths": static_paths, "function_paths": function_paths}) content = template.render(
with open(OUT_FILE, "w") as f: paths={
"prefix": "/api",
"static_paths": static_paths,
"function_paths": function_paths,
}
)
with open(PYTHON_OUT_FILE, "w") as f:
f.write(content) f.write(content)
template = Template(read_template(JS_ROUTES)) return
content = template.render(
paths={"prefix": "/api", "static_paths": static_paths, "function_paths": function_paths, "all_paths": paths}
) def generate_js_templates(paths: list[PathObject]):
with open(JS_OUT_FILE, "w") as f: # Template Path
f.write(content) 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 = [] all_tags = []
for k, g in groupby(paths, lambda x: x.http_verbs[0].tags[0]): for tag, tag_paths in groupby(paths, lambda x: x.http_verbs[0].tags[0]):
template = Template(read_template(JS_REQUESTS)) file_name = slugify(tag, separator="-")
content = template.render(paths={"all_paths": list(g), "export_name": camelize(k)})
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) f.write(content)
template = Template(read_template(JS_INDEX)) template = Template(read_template(JS_INDEX))
@ -137,5 +195,19 @@ def generate_template(app):
f.write(content) 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__": if __name__ == "__main__":
generate_template(app) generate_template(app)

1
dev/scripts/openapi.json Normal file

File diff suppressed because one or more lines are too long

View 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 %}
}

View File

@ -1,7 +1,7 @@
{% for api in files.files %} {% for api in files.files %}
import { {{ api }}API } from "./{{api}}.js" {% endfor %} import { {{ api.camel }}API } from "./interfaces/{{ api.slug }}" {% endfor %}
export const api = { export const api = {
{% for api in files.files %} {% for api in files.files %}
{{api}}: {{api}}API, {% endfor %} {{api.camel}}: {{api.camel}}API, {% endfor %}
} }

View File

@ -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 %}
}

View File

@ -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
View 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