""" 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 registerModelLabels(modelName: str, modelLabel: Dict[str, str], labels: Dict[str, Dict[str, str]]): """ Register labels for a model's attributes and the model itself. Args: modelName: Name of the model class modelLabel: 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[modelName] = {"model": modelLabel, "attributes": labels} def getModelLabels(modelName: str, language: str = "en") -> Dict[str, str]: """ Get labels for a model's attributes in the specified language. Args: modelName: Name of the model class language: Language code (default: "en") Returns: Dictionary mapping attribute names to their labels in the specified language """ modelData = MODEL_LABELS.get(modelName, {}) attributeLabels = modelData.get("attributes", {}) return { attr: translations.get(language, translations.get("en", attr)) for attr, translations in attributeLabels.items() } def getModelLabel(modelName: str, language: str = "en") -> str: """ Get the label for a model in the specified language. Args: modelName: Name of the model class language: Language code (default: "en") Returns: Model label in the specified language, or model name if no label exists """ modelData = MODEL_LABELS.get(modelName, {}) modelLabel = modelData.get("model", {}) return modelLabel.get(language, modelLabel.get("en", modelName)) 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 = getModelLabels(model_name, userLanguage) model_label = getModelLabel(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, } ] } } )