wag-managment-api-service-v.../ApiEvents/ValidationServiceApi/schema_converter.py

159 lines
4.8 KiB
Python

from typing import Dict, Any, Type, get_type_hints, get_args, get_origin
from pydantic import BaseModel, Field, EmailStr
from enum import Enum
import inspect
from fastapi import APIRouter
from datetime import datetime
class SchemaConverter:
"""Converts Pydantic models to Zod schema definitions"""
TYPE_MAPPINGS = {
str: "string",
int: "number",
float: "number",
bool: "boolean",
list: "array",
dict: "object",
datetime: "date",
EmailStr: "string.email()",
}
def __init__(self):
self.processed_models = set()
def convert_model(self, model: Type[BaseModel]) -> Dict[str, Any]:
"""Convert a Pydantic model to a Zod schema definition"""
if model.__name__ in self.processed_models:
return {"$ref": model.__name__}
self.processed_models.add(model.__name__)
schema = {
"name": model.__name__,
"type": "object",
"fields": {},
"validations": {}
}
for field_name, field in model.__fields__.items():
field_info = self._convert_field(field)
schema["fields"][field_name] = field_info
# Get validations from field
validations = self._get_field_validations(field)
if validations:
schema["validations"][field_name] = validations
return schema
def _convert_field(self, field) -> Dict[str, Any]:
"""Convert a Pydantic field to Zod field definition"""
field_type = field.outer_type_
origin = get_origin(field_type)
if origin is not None:
# Handle generic types (List, Dict, etc)
args = get_args(field_type)
if origin == list:
return {
"type": "array",
"items": self._get_type_name(args[0])
}
elif origin == dict:
return {
"type": "object",
"additionalProperties": self._get_type_name(args[1])
}
if inspect.isclass(field_type) and issubclass(field_type, BaseModel):
# Nested model
return self.convert_model(field_type)
if inspect.isclass(field_type) and issubclass(field_type, Enum):
# Enum type
return {
"type": "enum",
"values": [e.value for e in field_type]
}
return {
"type": self._get_type_name(field_type)
}
def _get_field_validations(self, field) -> Dict[str, Any]:
"""Extract validations from field"""
validations = {}
if field.field_info.min_length is not None:
validations["min_length"] = field.field_info.min_length
if field.field_info.max_length is not None:
validations["max_length"] = field.field_info.max_length
if field.field_info.regex is not None:
validations["pattern"] = field.field_info.regex.pattern
if field.field_info.gt is not None:
validations["gt"] = field.field_info.gt
if field.field_info.lt is not None:
validations["lt"] = field.field_info.lt
return validations
def _get_type_name(self, type_: Type) -> str:
"""Get Zod type name for Python type"""
return self.TYPE_MAPPINGS.get(type_, "any")
# FastAPI router
router = APIRouter(prefix="/api/validation", tags=["Validation"])
converter = SchemaConverter()
@router.get("/schema/{model_name}")
async def get_schema(model_name: str) -> Dict[str, Any]:
"""Get Zod schema for a specific model"""
# This is just an example - you'd need to implement model lookup
models = {
"User": UserModel,
"Product": ProductModel,
# Add your models here
}
if model_name not in models:
raise ValueError(f"Model {model_name} not found")
return converter.convert_model(models[model_name])
# Example usage:
"""
class UserModel(BaseModel):
email: EmailStr
username: str = Field(min_length=3, max_length=50)
age: int = Field(gt=0, lt=150)
is_active: bool = True
roles: List[str] = []
# GET /api/validation/schema/User would return:
{
"name": "User",
"type": "object",
"fields": {
"email": {"type": "string.email()"},
"username": {"type": "string"},
"age": {"type": "number"},
"is_active": {"type": "boolean"},
"roles": {
"type": "array",
"items": "string"
}
},
"validations": {
"username": {
"min_length": 3,
"max_length": 50
},
"age": {
"gt": 0,
"lt": 150
}
}
}
"""