255 lines
8.5 KiB
Python
255 lines
8.5 KiB
Python
"""
|
|
Shared utilities for model attributes and labels.
|
|
"""
|
|
|
|
from pydantic import BaseModel, Field, ConfigDict
|
|
from typing import Dict, Any, List, Type, Optional
|
|
import inspect
|
|
import importlib
|
|
import os
|
|
|
|
|
|
# Define the AttributeDefinition class here instead of importing it
|
|
class AttributeDefinition(BaseModel):
|
|
"""Definition of a model attribute with its metadata."""
|
|
|
|
name: str
|
|
type: str
|
|
label: str
|
|
description: Optional[str] = None
|
|
required: bool = False
|
|
default: Any = None
|
|
options: Optional[List[Any]] = None
|
|
validation: Optional[Dict[str, Any]] = None
|
|
ui: Optional[Dict[str, Any]] = None
|
|
# New frontend metadata fields
|
|
readonly: bool = False
|
|
editable: bool = True
|
|
visible: bool = True
|
|
order: int = 0
|
|
placeholder: Optional[str] = None
|
|
|
|
|
|
# Global registry for model labels
|
|
MODEL_LABELS: Dict[str, Dict[str, Dict[str, str]]] = {}
|
|
|
|
|
|
def register_model_labels(model_name: str, model_label: Dict[str, str], labels: Dict[str, Dict[str, str]]):
|
|
"""
|
|
Register labels for a model's attributes and the model itself.
|
|
|
|
Args:
|
|
model_name: Name of the model class
|
|
model_label: Dictionary mapping language codes to model labels
|
|
e.g. {"en": "Prompt", "fr": "Invite"}
|
|
labels: Dictionary mapping attribute names to their translations
|
|
e.g. {"name": {"en": "Name", "fr": "Nom"}}
|
|
"""
|
|
MODEL_LABELS[model_name] = {"model": model_label, "attributes": labels}
|
|
|
|
|
|
def get_model_labels(model_name: str, language: str = "en") -> Dict[str, str]:
|
|
"""
|
|
Get labels for a model's attributes in the specified language.
|
|
|
|
Args:
|
|
model_name: Name of the model class
|
|
language: Language code (default: "en")
|
|
|
|
Returns:
|
|
Dictionary mapping attribute names to their labels in the specified language
|
|
"""
|
|
model_data = MODEL_LABELS.get(model_name, {})
|
|
attribute_labels = model_data.get("attributes", {})
|
|
|
|
return {
|
|
attr: translations.get(language, translations.get("en", attr))
|
|
for attr, translations in attribute_labels.items()
|
|
}
|
|
|
|
|
|
def get_model_label(model_name: str, language: str = "en") -> str:
|
|
"""
|
|
Get the label for a model in the specified language.
|
|
|
|
Args:
|
|
model_name: Name of the model class
|
|
language: Language code (default: "en")
|
|
|
|
Returns:
|
|
Model label in the specified language, or model name if no label exists
|
|
"""
|
|
model_data = MODEL_LABELS.get(model_name, {})
|
|
model_label = model_data.get("model", {})
|
|
return model_label.get(language, model_label.get("en", model_name))
|
|
|
|
|
|
def getModelAttributeDefinitions(modelClass: Type[BaseModel] = None, userLanguage: str = "en") -> Dict[str, Any]:
|
|
"""
|
|
Get attribute definitions for a model class.
|
|
|
|
Args:
|
|
modelClass: The model class to get attributes for
|
|
userLanguage: Language code for translations (default: "en")
|
|
|
|
Returns:
|
|
Dictionary containing model label and attribute definitions
|
|
"""
|
|
if not modelClass:
|
|
return {}
|
|
|
|
attributes = []
|
|
model_name = modelClass.__name__
|
|
labels = get_model_labels(model_name, userLanguage)
|
|
model_label = get_model_label(model_name, userLanguage)
|
|
|
|
# Pydantic v2 only
|
|
fields = modelClass.model_fields
|
|
for name, field in fields.items():
|
|
# Extract frontend metadata from field info
|
|
field_info = field.field_info if hasattr(field, "field_info") else None
|
|
# Check both direct attributes and extra field for frontend metadata
|
|
frontend_type = None
|
|
frontend_readonly = False
|
|
frontend_required = field.is_required()
|
|
frontend_options = None
|
|
|
|
if field_info:
|
|
# Try direct attributes first
|
|
frontend_type = getattr(field_info, "frontend_type", None)
|
|
frontend_readonly = getattr(field_info, "frontend_readonly", False)
|
|
frontend_required = getattr(
|
|
field_info, "frontend_required", frontend_required
|
|
)
|
|
frontend_options = getattr(field_info, "frontend_options", None)
|
|
|
|
# If not found, check extra field
|
|
if hasattr(field_info, "extra") and field_info.extra:
|
|
if frontend_type is None:
|
|
frontend_type = field_info.extra.get("frontend_type")
|
|
if not frontend_readonly:
|
|
frontend_readonly = field_info.extra.get(
|
|
"frontend_readonly", False
|
|
)
|
|
if (
|
|
frontend_required == field.is_required()
|
|
): # Only override if we didn't get it from direct attribute
|
|
frontend_required = field_info.extra.get(
|
|
"frontend_required", frontend_required
|
|
)
|
|
if frontend_options is None:
|
|
frontend_options = field_info.extra.get("frontend_options")
|
|
|
|
# Use frontend type if available, otherwise fall back to Python type
|
|
field_type = (
|
|
frontend_type
|
|
if frontend_type
|
|
else (
|
|
field.annotation.__name__
|
|
if hasattr(field.annotation, "__name__")
|
|
else str(field.annotation)
|
|
)
|
|
)
|
|
|
|
attributes.append(
|
|
{
|
|
"name": name,
|
|
"type": field_type,
|
|
"required": frontend_required,
|
|
"description": field.description
|
|
if hasattr(field, "description")
|
|
else "",
|
|
"label": labels.get(name, name),
|
|
"placeholder": f"Please enter {labels.get(name, name)}",
|
|
"editable": not frontend_readonly,
|
|
"visible": True,
|
|
"order": len(attributes),
|
|
"readonly": frontend_readonly,
|
|
"options": frontend_options,
|
|
}
|
|
)
|
|
|
|
return {"model": model_label, "attributes": attributes}
|
|
|
|
|
|
def getModelClasses() -> Dict[str, Type[BaseModel]]:
|
|
"""
|
|
Dynamically get all model classes from all model modules.
|
|
|
|
Returns:
|
|
Dict[str, Type[BaseModel]]: Dictionary of model class names to their classes
|
|
"""
|
|
modelClasses = {}
|
|
|
|
# Get the interfaces directory path
|
|
interfaces_dir = os.path.join(
|
|
os.path.dirname(os.path.dirname(__file__)), "interfaces"
|
|
)
|
|
|
|
# Find all model files in interfaces directory
|
|
for fileName in os.listdir(interfaces_dir):
|
|
if fileName.endswith("Model.py"):
|
|
# Convert fileName to module name (e.g., gatewayModel.py -> gatewayModel)
|
|
module_name = fileName[:-3]
|
|
|
|
# Import the module dynamically
|
|
module = importlib.import_module(f"modules.interfaces.{module_name}")
|
|
|
|
# Get all classes from the module
|
|
for name, obj in inspect.getmembers(module):
|
|
if (
|
|
inspect.isclass(obj)
|
|
and issubclass(obj, BaseModel)
|
|
and obj != BaseModel
|
|
):
|
|
modelClasses[name] = obj
|
|
|
|
# Also get models from datamodels directory
|
|
datamodels_dir = os.path.join(
|
|
os.path.dirname(os.path.dirname(__file__)), "datamodels"
|
|
)
|
|
|
|
# Find all model files in datamodels directory
|
|
for fileName in os.listdir(datamodels_dir):
|
|
if fileName.startswith("datamodel") and fileName.endswith(".py"):
|
|
# Convert fileName to module name (e.g., datamodelUtils.py -> datamodelUtils)
|
|
module_name = fileName[:-3]
|
|
|
|
# Import the module dynamically
|
|
module = importlib.import_module(f"modules.datamodels.{module_name}")
|
|
|
|
# Get all classes from the module
|
|
for name, obj in inspect.getmembers(module):
|
|
if (
|
|
inspect.isclass(obj)
|
|
and issubclass(obj, BaseModel)
|
|
and obj != BaseModel
|
|
):
|
|
modelClasses[name] = obj
|
|
|
|
return modelClasses
|
|
|
|
|
|
class AttributeResponse(BaseModel):
|
|
"""Response model for entity attributes"""
|
|
|
|
attributes: List[AttributeDefinition]
|
|
|
|
model_config = ConfigDict(
|
|
json_schema_extra={
|
|
"example": {
|
|
"attributes": [
|
|
{
|
|
"name": "username",
|
|
"label": "Username",
|
|
"type": "string",
|
|
"required": True,
|
|
"placeholder": "Please enter username",
|
|
"editable": True,
|
|
"visible": True,
|
|
"order": 0,
|
|
}
|
|
]
|
|
}
|
|
}
|
|
)
|