180 lines
6.4 KiB
Python
180 lines
6.4 KiB
Python
""" Generate service files "service and route", CRUD.
|
|
READ models on a modules, and generate code usin template Jija2
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Dict, List, Any
|
|
# #
|
|
from jinja2 import Environment, FileSystemLoader
|
|
from sqlalchemy import inspect, Table, Column
|
|
from sqlalchemy.engine import create_mock_engine
|
|
#####
|
|
from database import Base
|
|
import importlib
|
|
import enum
|
|
|
|
|
|
class CodeGenerator:
|
|
def __init__(self, templates_dir: str = "templates", output_dir: str = "generated"):
|
|
|
|
self.templates_dir = Path(templates_dir)
|
|
self.output_dir = Path(output_dir)
|
|
self.env = Environment(loader=FileSystemLoader(templates_dir))
|
|
|
|
# create directory of output
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
(self.output_dir / "services").mkdir(exist_ok=True)
|
|
(self.output_dir / "routes").mkdir(exist_ok=True)
|
|
(self.output_dir / "schemas").mkdir(exist_ok=True)
|
|
|
|
def get_model_metadata(self, model_class) -> Dict[str, Any]:
|
|
"""" Extract metadata from SQLAlchemy """
|
|
if not hasattr(model_class, '__table__'):
|
|
print(f"{model_class.__name__} no tiene __table__, jumping")
|
|
return None
|
|
|
|
fields = []
|
|
required_fields = []
|
|
unique_fields = []
|
|
|
|
for column in model_class.__table__.columns:
|
|
|
|
column_type = str(column.type)
|
|
if 'enum' in column_type.lower() or 'ENUM' in column_type:
|
|
# extract possible values of enum
|
|
if hasattr(column.type, 'enum_class'):
|
|
enum_values = list(column.type.enum_class.__members__.keys())
|
|
python_type = "str"
|
|
else:
|
|
python_type = "str"
|
|
else:
|
|
python_type = self._sqlalchemy_to_python_type(column.type)
|
|
|
|
field_info = {
|
|
"name": column.name,
|
|
"type": column_type,
|
|
"python_type": python_type,
|
|
"nullable": column.nullable ,
|
|
"primary_key": column.primary_key,
|
|
"unique": column.unique,
|
|
"default": column.default is None,
|
|
"is_enum": 'enum' in column_type.lower(),
|
|
|
|
}
|
|
fields.append(field_info)
|
|
|
|
if not column.nullable and not column.primary_key:
|
|
required_fields.append(column.name)
|
|
if column.unique:
|
|
unique_fields.append(column.name)
|
|
|
|
return{
|
|
"class_name" : model_class.__name__,
|
|
"entity_name" : model_class.__name__.lower(),
|
|
"model_name" : model_class.__name__,
|
|
"model_folder" : model_class.__module__.split(".")[-2] if "." in model_class.__module__ else "models",
|
|
"schema_folder" : "schemas" ,
|
|
"fields" : fields,
|
|
"required_fields" : required_fields,
|
|
"unique_fields" : unique_fields,
|
|
"has_soft_delete" : any(c.name in ["is_active", "deleted_at"] for c in model_class.__table__.columns),
|
|
}
|
|
|
|
def _sqlalchemy_to_python_type(self, sqlalchemy_type) -> str:
|
|
""" Convert SQAlchemy types on a python"""
|
|
type_str = str(sqlalchemy_type).lower()
|
|
if "int" in type_str:
|
|
return "int"
|
|
elif "str" in type_str or "varchar" in type_str or "text" in type_str:
|
|
return "str"
|
|
elif "bool" in type_str:
|
|
return "bool"
|
|
elif "datetime" in type_str or "date" in type_str:
|
|
return "datetime"
|
|
elif "float" in type_str or "decimal" in type_str:
|
|
return "float"
|
|
else:
|
|
return "Any"
|
|
|
|
def generate_service(self, model_metada: Dict[str, Any]):
|
|
""" Generate file of service using template"""
|
|
template = self.env.get_template("service_template.j2")
|
|
output = template.render(**model_metada)
|
|
|
|
output_file = self.output_dir / "services" /f"{model_metada['entity_name']}_service.py"
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
f.write(output)
|
|
|
|
print(f"Generated: {output_file}")
|
|
|
|
def generate_route(self, model_metadata: Dict[str, Any]):
|
|
"""Generate file of route using template"""
|
|
|
|
template = self.env.get_template("route_template.j2")
|
|
output = template.render(**model_metadata)
|
|
|
|
output_file = self.output_dir / "routers" / f"{model_metadata['entity_name']}_router.py"
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
f.write(output)
|
|
|
|
print(f"Generated: {output_file}")
|
|
|
|
def generate_all(self, models_list: List):
|
|
""" Generate services and router for a list models"""
|
|
print(f"Intializing generation")
|
|
|
|
for model in models_list:
|
|
print(f"Processing model: {model.__name__}")
|
|
metadata = self.get_model_metadata(model)
|
|
|
|
if metadata is None:
|
|
print(f"Error: can't obtain metadara, jumped \n")
|
|
continue
|
|
|
|
self.generate_service(metadata)
|
|
self.generate_route(metadata)
|
|
|
|
print("Generation completed")
|
|
|
|
def get_all_models():
|
|
"""Import and retunr all models SQLAlchemy"""
|
|
|
|
from app.modules.coments.models import Coments
|
|
from app.modules.configuration.models import Configuration
|
|
from app.modules.credits.models import Credits
|
|
from app.modules.edos.models import EDOS
|
|
from app.modules.efos.models import EFOS
|
|
from app.modules.feed.models import Feed
|
|
from app.modules.files.models import Files
|
|
from app.modules.interactions.models import Interaccion
|
|
from app.modules.invoices.models import Invoices
|
|
from app.modules.license.models import License
|
|
from app.modules.moves.models import Moves
|
|
from app.modules.location.models import Locations
|
|
from app.modules.suppliers.models import Suppliers
|
|
from app.modules.branches.models import Branches
|
|
|
|
|
|
|
|
return [Branches, Coments, Configuration, Credits, EDOS, EFOS, Feed, Files, Interaccion, Invoices, License, Moves, Locations, Suppliers]
|
|
|
|
|
|
def main():
|
|
#configure routes
|
|
|
|
generator = CodeGenerator(
|
|
templates_dir = "templates",
|
|
output_dir = "output/generated"
|
|
)
|
|
|
|
models = get_all_models()
|
|
|
|
generator.generate_all(models)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|