feat: add langgraph first tool; pydantic v2
This commit is contained in:
parent
68d6ab9890
commit
98b258ae53
7 changed files with 718 additions and 432 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
"""Security models: Token and AuthEvent."""
|
"""Security models: Token and AuthEvent."""
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, ConfigDict
|
||||||
from modules.shared.attributeUtils import register_model_labels, ModelMixin
|
from modules.shared.attributeUtils import register_model_labels, ModelMixin
|
||||||
from modules.shared.timezoneUtils import get_utc_timestamp
|
from modules.shared.timezoneUtils import get_utc_timestamp
|
||||||
from .datamodelUam import AuthAuthority
|
from .datamodelUam import AuthAuthority
|
||||||
|
|
@ -18,21 +18,36 @@ class Token(BaseModel, ModelMixin):
|
||||||
id: Optional[str] = None
|
id: Optional[str] = None
|
||||||
userId: str
|
userId: str
|
||||||
authority: AuthAuthority
|
authority: AuthAuthority
|
||||||
connectionId: Optional[str] = Field(None, description="ID of the connection this token belongs to")
|
connectionId: Optional[str] = Field(
|
||||||
|
None, description="ID of the connection this token belongs to"
|
||||||
|
)
|
||||||
tokenAccess: str
|
tokenAccess: str
|
||||||
tokenType: str = "bearer"
|
tokenType: str = "bearer"
|
||||||
expiresAt: float = Field(description="When the token expires (UTC timestamp in seconds)")
|
expiresAt: float = Field(
|
||||||
|
description="When the token expires (UTC timestamp in seconds)"
|
||||||
|
)
|
||||||
tokenRefresh: Optional[str] = None
|
tokenRefresh: Optional[str] = None
|
||||||
createdAt: Optional[float] = Field(None, description="When the token was created (UTC timestamp in seconds)")
|
createdAt: Optional[float] = Field(
|
||||||
status: TokenStatus = Field(default=TokenStatus.ACTIVE, description="Token status: active/revoked")
|
None, description="When the token was created (UTC timestamp in seconds)"
|
||||||
revokedAt: Optional[float] = Field(None, description="When the token was revoked (UTC timestamp in seconds)")
|
)
|
||||||
revokedBy: Optional[str] = Field(None, description="User ID who revoked the token (admin/self)")
|
status: TokenStatus = Field(
|
||||||
|
default=TokenStatus.ACTIVE, description="Token status: active/revoked"
|
||||||
|
)
|
||||||
|
revokedAt: Optional[float] = Field(
|
||||||
|
None, description="When the token was revoked (UTC timestamp in seconds)"
|
||||||
|
)
|
||||||
|
revokedBy: Optional[str] = Field(
|
||||||
|
None, description="User ID who revoked the token (admin/self)"
|
||||||
|
)
|
||||||
reason: Optional[str] = Field(None, description="Optional revocation reason")
|
reason: Optional[str] = Field(None, description="Optional revocation reason")
|
||||||
sessionId: Optional[str] = Field(None, description="Logical session grouping for logout revocation")
|
sessionId: Optional[str] = Field(
|
||||||
mandateId: Optional[str] = Field(None, description="Mandate ID for tenant scoping of the token")
|
None, description="Logical session grouping for logout revocation"
|
||||||
|
)
|
||||||
|
mandateId: Optional[str] = Field(
|
||||||
|
None, description="Mandate ID for tenant scoping of the token"
|
||||||
|
)
|
||||||
|
|
||||||
class Config:
|
model_config = ConfigDict(use_enum_values=True)
|
||||||
use_enum_values = True
|
|
||||||
|
|
||||||
|
|
||||||
register_model_labels(
|
register_model_labels(
|
||||||
|
|
@ -59,14 +74,60 @@ register_model_labels(
|
||||||
|
|
||||||
|
|
||||||
class AuthEvent(BaseModel, ModelMixin):
|
class AuthEvent(BaseModel, ModelMixin):
|
||||||
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the auth event", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
id: str = Field(
|
||||||
userId: str = Field(description="ID of the user this event belongs to", frontend_type="text", frontend_readonly=True, frontend_required=True)
|
default_factory=lambda: str(uuid.uuid4()),
|
||||||
eventType: str = Field(description="Type of authentication event (e.g., 'login', 'logout', 'token_refresh')", frontend_type="text", frontend_readonly=True, frontend_required=True)
|
description="Unique ID of the auth event",
|
||||||
timestamp: float = Field(default_factory=get_utc_timestamp, description="Unix timestamp when the event occurred", frontend_type="datetime", frontend_readonly=True, frontend_required=True)
|
frontend_type="text",
|
||||||
ipAddress: Optional[str] = Field(default=None, description="IP address from which the event originated", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
frontend_readonly=True,
|
||||||
userAgent: Optional[str] = Field(default=None, description="User agent string from the request", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
frontend_required=False,
|
||||||
success: bool = Field(default=True, description="Whether the authentication event was successful", frontend_type="boolean", frontend_readonly=True, frontend_required=True)
|
)
|
||||||
details: Optional[str] = Field(default=None, description="Additional details about the event", frontend_type="text", frontend_readonly=True, frontend_required=False)
|
userId: str = Field(
|
||||||
|
description="ID of the user this event belongs to",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=True,
|
||||||
|
)
|
||||||
|
eventType: str = Field(
|
||||||
|
description="Type of authentication event (e.g., 'login', 'logout', 'token_refresh')",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=True,
|
||||||
|
)
|
||||||
|
timestamp: float = Field(
|
||||||
|
default_factory=get_utc_timestamp,
|
||||||
|
description="Unix timestamp when the event occurred",
|
||||||
|
frontend_type="datetime",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=True,
|
||||||
|
)
|
||||||
|
ipAddress: Optional[str] = Field(
|
||||||
|
default=None,
|
||||||
|
description="IP address from which the event originated",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=False,
|
||||||
|
)
|
||||||
|
userAgent: Optional[str] = Field(
|
||||||
|
default=None,
|
||||||
|
description="User agent string from the request",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=False,
|
||||||
|
)
|
||||||
|
success: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="Whether the authentication event was successful",
|
||||||
|
frontend_type="boolean",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=True,
|
||||||
|
)
|
||||||
|
details: Optional[str] = Field(
|
||||||
|
default=None,
|
||||||
|
description="Additional details about the event",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
register_model_labels(
|
register_model_labels(
|
||||||
|
|
@ -83,5 +144,3 @@ register_model_labels(
|
||||||
"details": {"en": "Details", "fr": "Détails"},
|
"details": {"en": "Details", "fr": "Détails"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
1
modules/features/chatBot/chatbotTools/__init__.py
Normal file
1
modules/features/chatBot/chatbotTools/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"""Contains all tools available for the chatbot to use."""
|
||||||
|
|
@ -1 +1,7 @@
|
||||||
"""Tools that are custom to a specific customer go here."""
|
"""Shared tools available across all chatbot implementations."""
|
||||||
|
|
||||||
|
from modules.features.chatBot.chatbotTools.sharedTools.toolTavilySearch import (
|
||||||
|
tavily_search,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ["tavily_search"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""Tavily Search Tool for LangGraph.
|
||||||
|
|
||||||
|
This tool provides web search capabilities using the Tavily API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Annotated
|
||||||
|
from langchain_core.tools import tool
|
||||||
|
from modules.connectors.connectorAiTavily import ConnectorWeb
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@tool
|
||||||
|
async def tavily_search(
|
||||||
|
query: Annotated[str, "The search query to look up on the web"],
|
||||||
|
) -> str:
|
||||||
|
"""Search the web using Tavily API.
|
||||||
|
|
||||||
|
Use this tool to search for current information, news, or any web content.
|
||||||
|
The tool returns relevant search results including titles and URLs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query: The search query string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A formatted string containing search results with titles and URLs
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Create connector instance
|
||||||
|
connector = await ConnectorWeb.create()
|
||||||
|
|
||||||
|
# Perform search with default parameters
|
||||||
|
results = await connector._search(
|
||||||
|
query=query,
|
||||||
|
max_results=5,
|
||||||
|
search_depth="basic",
|
||||||
|
include_answer=True,
|
||||||
|
include_raw_content=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Format results
|
||||||
|
if not results:
|
||||||
|
return f"No results found for query: {query}"
|
||||||
|
|
||||||
|
formatted_results = [f"Search results for '{query}':\n"]
|
||||||
|
for i, result in enumerate(results, 1):
|
||||||
|
formatted_results.append(f"{i}. {result.title}")
|
||||||
|
formatted_results.append(f" URL: {result.url}\n")
|
||||||
|
|
||||||
|
return "\n".join(formatted_results)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in tavily_search tool: {str(e)}")
|
||||||
|
return f"Error performing search: {str(e)}"
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,52 +2,55 @@
|
||||||
Shared utilities for model attributes and labels.
|
Shared utilities for model attributes and labels.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, ConfigDict
|
||||||
from typing import Dict, Any, List, Type, Optional, Union
|
from typing import Dict, Any, List, Type, Optional, Union
|
||||||
import inspect
|
import inspect
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class ModelMixin:
|
class ModelMixin:
|
||||||
"""Mixin class that provides serialization methods for Pydantic models."""
|
"""Mixin class that provides serialization methods for Pydantic models."""
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Convert a Pydantic model to a dictionary.
|
Convert a Pydantic model to a dictionary.
|
||||||
Handles both Pydantic v1 and v2.
|
Handles both Pydantic v1 and v2.
|
||||||
All timestamp fields remain as float values.
|
All timestamp fields remain as float values.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, Any]: Dictionary representation of the model
|
Dict[str, Any]: Dictionary representation of the model
|
||||||
"""
|
"""
|
||||||
# Get the raw dictionary
|
# Get the raw dictionary
|
||||||
if hasattr(self, 'model_dump'):
|
if hasattr(self, "model_dump"):
|
||||||
data: Dict[str, Any] = self.model_dump() # Pydantic v2
|
data: Dict[str, Any] = self.model_dump() # Pydantic v2
|
||||||
else:
|
else:
|
||||||
data: Dict[str, Any] = self.dict() # Pydantic v1
|
data: Dict[str, Any] = self.dict() # Pydantic v1
|
||||||
|
|
||||||
# All fields (including timestamps) remain in their original format
|
# All fields (including timestamps) remain in their original format
|
||||||
# No conversions needed - timestamps are already float
|
# No conversions needed - timestamps are already float
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: Dict[str, Any]) -> 'ModelMixin':
|
def from_dict(cls, data: Dict[str, Any]) -> "ModelMixin":
|
||||||
"""
|
"""
|
||||||
Create a Pydantic model instance from a dictionary.
|
Create a Pydantic model instance from a dictionary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data: Dictionary containing the model data
|
data: Dictionary containing the model data
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ModelMixin: New instance of the model class
|
ModelMixin: New instance of the model class
|
||||||
"""
|
"""
|
||||||
return cls(**data)
|
return cls(**data)
|
||||||
|
|
||||||
|
|
||||||
# Define the AttributeDefinition class here instead of importing it
|
# Define the AttributeDefinition class here instead of importing it
|
||||||
class AttributeDefinition(BaseModel, ModelMixin):
|
class AttributeDefinition(BaseModel, ModelMixin):
|
||||||
"""Definition of a model attribute with its metadata."""
|
"""Definition of a model attribute with its metadata."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
type: str
|
type: str
|
||||||
label: str
|
label: str
|
||||||
|
|
@ -64,41 +67,47 @@ class AttributeDefinition(BaseModel, ModelMixin):
|
||||||
order: int = 0
|
order: int = 0
|
||||||
placeholder: Optional[str] = None
|
placeholder: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
# Global registry for model labels
|
# Global registry for model labels
|
||||||
MODEL_LABELS: Dict[str, Dict[str, Dict[str, str]]] = {}
|
MODEL_LABELS: Dict[str, Dict[str, Dict[str, str]]] = {}
|
||||||
|
|
||||||
|
|
||||||
def to_dict(model: BaseModel) -> Dict[str, Any]:
|
def to_dict(model: BaseModel) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Convert a Pydantic model to a dictionary.
|
Convert a Pydantic model to a dictionary.
|
||||||
Handles both Pydantic v1 and v2.
|
Handles both Pydantic v1 and v2.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model: The Pydantic model instance to convert
|
model: The Pydantic model instance to convert
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, Any]: Dictionary representation of the model
|
Dict[str, Any]: Dictionary representation of the model
|
||||||
"""
|
"""
|
||||||
if hasattr(model, 'model_dump'):
|
if hasattr(model, "model_dump"):
|
||||||
return model.model_dump() # Pydantic v2
|
return model.model_dump() # Pydantic v2
|
||||||
return model.dict() # Pydantic v1
|
return model.dict() # Pydantic v1
|
||||||
|
|
||||||
|
|
||||||
def from_dict(model_class: Type[BaseModel], data: Dict[str, Any]) -> BaseModel:
|
def from_dict(model_class: Type[BaseModel], data: Dict[str, Any]) -> BaseModel:
|
||||||
"""
|
"""
|
||||||
Create a Pydantic model instance from a dictionary.
|
Create a Pydantic model instance from a dictionary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model_class: The Pydantic model class to instantiate
|
model_class: The Pydantic model class to instantiate
|
||||||
data: Dictionary containing the model data
|
data: Dictionary containing the model data
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
BaseModel: New instance of the model class
|
BaseModel: New instance of the model class
|
||||||
"""
|
"""
|
||||||
return model_class(**data)
|
return model_class(**data)
|
||||||
|
|
||||||
def register_model_labels(model_name: str, model_label: Dict[str, str], labels: 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.
|
Register labels for a model's attributes and the model itself.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model_name: Name of the model class
|
model_name: Name of the model class
|
||||||
model_label: Dictionary mapping language codes to model labels
|
model_label: Dictionary mapping language codes to model labels
|
||||||
|
|
@ -106,38 +115,37 @@ def register_model_labels(model_name: str, model_label: Dict[str, str], labels:
|
||||||
labels: Dictionary mapping attribute names to their translations
|
labels: Dictionary mapping attribute names to their translations
|
||||||
e.g. {"name": {"en": "Name", "fr": "Nom"}}
|
e.g. {"name": {"en": "Name", "fr": "Nom"}}
|
||||||
"""
|
"""
|
||||||
MODEL_LABELS[model_name] = {
|
MODEL_LABELS[model_name] = {"model": model_label, "attributes": labels}
|
||||||
"model": model_label,
|
|
||||||
"attributes": labels
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_model_labels(model_name: str, language: str = "en") -> Dict[str, str]:
|
def get_model_labels(model_name: str, language: str = "en") -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Get labels for a model's attributes in the specified language.
|
Get labels for a model's attributes in the specified language.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model_name: Name of the model class
|
model_name: Name of the model class
|
||||||
language: Language code (default: "en")
|
language: Language code (default: "en")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary mapping attribute names to their labels in the specified language
|
Dictionary mapping attribute names to their labels in the specified language
|
||||||
"""
|
"""
|
||||||
model_data = MODEL_LABELS.get(model_name, {})
|
model_data = MODEL_LABELS.get(model_name, {})
|
||||||
attribute_labels = model_data.get("attributes", {})
|
attribute_labels = model_data.get("attributes", {})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attr: translations.get(language, translations.get("en", attr))
|
attr: translations.get(language, translations.get("en", attr))
|
||||||
for attr, translations in attribute_labels.items()
|
for attr, translations in attribute_labels.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_model_label(model_name: str, language: str = "en") -> str:
|
def get_model_label(model_name: str, language: str = "en") -> str:
|
||||||
"""
|
"""
|
||||||
Get the label for a model in the specified language.
|
Get the label for a model in the specified language.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model_name: Name of the model class
|
model_name: Name of the model class
|
||||||
language: Language code (default: "en")
|
language: Language code (default: "en")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Model label in the specified language, or model name if no label exists
|
Model label in the specified language, or model name if no label exists
|
||||||
"""
|
"""
|
||||||
|
|
@ -145,156 +153,205 @@ def get_model_label(model_name: str, language: str = "en") -> str:
|
||||||
model_label = model_data.get("model", {})
|
model_label = model_data.get("model", {})
|
||||||
return model_label.get(language, model_label.get("en", model_name))
|
return model_label.get(language, model_label.get("en", model_name))
|
||||||
|
|
||||||
def getModelAttributeDefinitions(modelClass: Type[BaseModel] = None, userLanguage: str = "en") -> Dict[str, Any]:
|
|
||||||
|
def getModelAttributeDefinitions(
|
||||||
|
modelClass: Type[BaseModel] = None, userLanguage: str = "en"
|
||||||
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get attribute definitions for a model class.
|
Get attribute definitions for a model class.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
modelClass: The model class to get attributes for
|
modelClass: The model class to get attributes for
|
||||||
userLanguage: Language code for translations (default: "en")
|
userLanguage: Language code for translations (default: "en")
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary containing model label and attribute definitions
|
Dictionary containing model label and attribute definitions
|
||||||
"""
|
"""
|
||||||
if not modelClass:
|
if not modelClass:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
attributes = []
|
attributes = []
|
||||||
model_name = modelClass.__name__
|
model_name = modelClass.__name__
|
||||||
labels = get_model_labels(model_name, userLanguage)
|
labels = get_model_labels(model_name, userLanguage)
|
||||||
model_label = get_model_label(model_name, userLanguage)
|
model_label = get_model_label(model_name, userLanguage)
|
||||||
|
|
||||||
# Handle both Pydantic v1 and v2
|
# Handle both Pydantic v1 and v2
|
||||||
if hasattr(modelClass, 'model_fields'): # Pydantic v2
|
if hasattr(modelClass, "model_fields"): # Pydantic v2
|
||||||
fields = modelClass.model_fields
|
fields = modelClass.model_fields
|
||||||
for name, field in fields.items():
|
for name, field in fields.items():
|
||||||
# Extract frontend metadata from field info
|
# Extract frontend metadata from field info
|
||||||
field_info = field.field_info if hasattr(field, 'field_info') else None
|
field_info = field.field_info if hasattr(field, "field_info") else None
|
||||||
# Check both direct attributes and extra field for frontend metadata
|
# Check both direct attributes and extra field for frontend metadata
|
||||||
frontend_type = None
|
frontend_type = None
|
||||||
frontend_readonly = False
|
frontend_readonly = False
|
||||||
frontend_required = field.is_required()
|
frontend_required = field.is_required()
|
||||||
frontend_options = None
|
frontend_options = None
|
||||||
|
|
||||||
if field_info:
|
if field_info:
|
||||||
# Try direct attributes first
|
# Try direct attributes first
|
||||||
frontend_type = getattr(field_info, 'frontend_type', None)
|
frontend_type = getattr(field_info, "frontend_type", None)
|
||||||
frontend_readonly = getattr(field_info, 'frontend_readonly', False)
|
frontend_readonly = getattr(field_info, "frontend_readonly", False)
|
||||||
frontend_required = getattr(field_info, 'frontend_required', frontend_required)
|
frontend_required = getattr(
|
||||||
frontend_options = getattr(field_info, 'frontend_options', None)
|
field_info, "frontend_required", frontend_required
|
||||||
|
)
|
||||||
|
frontend_options = getattr(field_info, "frontend_options", None)
|
||||||
|
|
||||||
# If not found, check extra field
|
# If not found, check extra field
|
||||||
if hasattr(field_info, 'extra') and field_info.extra:
|
if hasattr(field_info, "extra") and field_info.extra:
|
||||||
if frontend_type is None:
|
if frontend_type is None:
|
||||||
frontend_type = field_info.extra.get('frontend_type')
|
frontend_type = field_info.extra.get("frontend_type")
|
||||||
if not frontend_readonly:
|
if not frontend_readonly:
|
||||||
frontend_readonly = field_info.extra.get('frontend_readonly', False)
|
frontend_readonly = field_info.extra.get(
|
||||||
if frontend_required == field.is_required(): # Only override if we didn't get it from direct attribute
|
"frontend_readonly", False
|
||||||
frontend_required = field_info.extra.get('frontend_required', frontend_required)
|
)
|
||||||
|
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:
|
if frontend_options is None:
|
||||||
frontend_options = field_info.extra.get('frontend_options')
|
frontend_options = field_info.extra.get("frontend_options")
|
||||||
|
|
||||||
# Use frontend type if available, otherwise fall back to Python type
|
# 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))
|
field_type = (
|
||||||
|
frontend_type
|
||||||
attributes.append({
|
if frontend_type
|
||||||
"name": name,
|
else (
|
||||||
"type": field_type,
|
field.annotation.__name__
|
||||||
"required": frontend_required,
|
if hasattr(field.annotation, "__name__")
|
||||||
"description": field.description if hasattr(field, "description") else "",
|
else str(field.annotation)
|
||||||
"label": labels.get(name, name),
|
)
|
||||||
"placeholder": f"Please enter {labels.get(name, name)}",
|
)
|
||||||
"editable": not frontend_readonly,
|
|
||||||
"visible": True,
|
attributes.append(
|
||||||
"order": len(attributes),
|
{
|
||||||
"readonly": frontend_readonly,
|
"name": name,
|
||||||
"options": frontend_options
|
"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,
|
||||||
|
}
|
||||||
|
)
|
||||||
else: # Pydantic v1
|
else: # Pydantic v1
|
||||||
fields = modelClass.__fields__
|
fields = modelClass.__fields__
|
||||||
for name, field in fields.items():
|
for name, field in fields.items():
|
||||||
# Extract frontend metadata from field info
|
# Extract frontend metadata from field info
|
||||||
field_info = field.field_info if hasattr(field, 'field_info') else None
|
field_info = field.field_info if hasattr(field, "field_info") else None
|
||||||
# Check both direct attributes and extra field for frontend metadata
|
# Check both direct attributes and extra field for frontend metadata
|
||||||
frontend_type = None
|
frontend_type = None
|
||||||
frontend_readonly = False
|
frontend_readonly = False
|
||||||
frontend_required = field.required
|
frontend_required = field.required
|
||||||
frontend_options = None
|
frontend_options = None
|
||||||
|
|
||||||
if field_info:
|
if field_info:
|
||||||
# Try direct attributes first
|
# Try direct attributes first
|
||||||
frontend_type = getattr(field_info, 'frontend_type', None)
|
frontend_type = getattr(field_info, "frontend_type", None)
|
||||||
frontend_readonly = getattr(field_info, 'frontend_readonly', False)
|
frontend_readonly = getattr(field_info, "frontend_readonly", False)
|
||||||
frontend_required = getattr(field_info, 'frontend_required', frontend_required)
|
frontend_required = getattr(
|
||||||
frontend_options = getattr(field_info, 'frontend_options', None)
|
field_info, "frontend_required", frontend_required
|
||||||
|
)
|
||||||
|
frontend_options = getattr(field_info, "frontend_options", None)
|
||||||
|
|
||||||
# If not found, check extra field
|
# If not found, check extra field
|
||||||
if hasattr(field_info, 'extra') and field_info.extra:
|
if hasattr(field_info, "extra") and field_info.extra:
|
||||||
if frontend_type is None:
|
if frontend_type is None:
|
||||||
frontend_type = field_info.extra.get('frontend_type')
|
frontend_type = field_info.extra.get("frontend_type")
|
||||||
if not frontend_readonly:
|
if not frontend_readonly:
|
||||||
frontend_readonly = field_info.extra.get('frontend_readonly', False)
|
frontend_readonly = field_info.extra.get(
|
||||||
if frontend_required == field.required: # Only override if we didn't get it from direct attribute
|
"frontend_readonly", False
|
||||||
frontend_required = field_info.extra.get('frontend_required', frontend_required)
|
)
|
||||||
|
if (
|
||||||
|
frontend_required == field.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:
|
if frontend_options is None:
|
||||||
frontend_options = field_info.extra.get('frontend_options')
|
frontend_options = field_info.extra.get("frontend_options")
|
||||||
|
|
||||||
# Use frontend type if available, otherwise fall back to Python type
|
# Use frontend type if available, otherwise fall back to Python type
|
||||||
field_type = frontend_type if frontend_type else (field.type_.__name__ if hasattr(field.type_, "__name__") else str(field.type_))
|
field_type = (
|
||||||
|
frontend_type
|
||||||
attributes.append({
|
if frontend_type
|
||||||
"name": name,
|
else (
|
||||||
"type": field_type,
|
field.type_.__name__
|
||||||
"required": frontend_required,
|
if hasattr(field.type_, "__name__")
|
||||||
"description": field.field_info.description if hasattr(field.field_info, "description") else "",
|
else str(field.type_)
|
||||||
"label": labels.get(name, name),
|
)
|
||||||
"placeholder": f"Please enter {labels.get(name, name)}",
|
)
|
||||||
"editable": not frontend_readonly,
|
|
||||||
"visible": True,
|
attributes.append(
|
||||||
"order": len(attributes),
|
{
|
||||||
"readonly": frontend_readonly,
|
"name": name,
|
||||||
"options": frontend_options
|
"type": field_type,
|
||||||
})
|
"required": frontend_required,
|
||||||
|
"description": field.field_info.description
|
||||||
return {
|
if hasattr(field.field_info, "description")
|
||||||
"model": model_label,
|
else "",
|
||||||
"attributes": attributes
|
"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]]:
|
def getModelClasses() -> Dict[str, Type[BaseModel]]:
|
||||||
"""
|
"""
|
||||||
Dynamically get all model classes from all model modules.
|
Dynamically get all model classes from all model modules.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, Type[BaseModel]]: Dictionary of model class names to their classes
|
Dict[str, Type[BaseModel]]: Dictionary of model class names to their classes
|
||||||
"""
|
"""
|
||||||
modelClasses = {}
|
modelClasses = {}
|
||||||
|
|
||||||
# Get the interfaces directory path
|
# Get the interfaces directory path
|
||||||
interfaces_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'interfaces')
|
interfaces_dir = os.path.join(
|
||||||
|
os.path.dirname(os.path.dirname(__file__)), "interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
# Find all model files
|
# Find all model files
|
||||||
for fileName in os.listdir(interfaces_dir):
|
for fileName in os.listdir(interfaces_dir):
|
||||||
if fileName.endswith('Model.py'):
|
if fileName.endswith("Model.py"):
|
||||||
# Convert fileName to module name (e.g., gatewayModel.py -> gatewayModel)
|
# Convert fileName to module name (e.g., gatewayModel.py -> gatewayModel)
|
||||||
module_name = fileName[:-3]
|
module_name = fileName[:-3]
|
||||||
|
|
||||||
# Import the module dynamically
|
# Import the module dynamically
|
||||||
module = importlib.import_module(f'modules.interfaces.{module_name}')
|
module = importlib.import_module(f"modules.interfaces.{module_name}")
|
||||||
|
|
||||||
# Get all classes from the module
|
# Get all classes from the module
|
||||||
for name, obj in inspect.getmembers(module):
|
for name, obj in inspect.getmembers(module):
|
||||||
if inspect.isclass(obj) and issubclass(obj, BaseModel) and obj != BaseModel:
|
if (
|
||||||
|
inspect.isclass(obj)
|
||||||
|
and issubclass(obj, BaseModel)
|
||||||
|
and obj != BaseModel
|
||||||
|
):
|
||||||
modelClasses[name] = obj
|
modelClasses[name] = obj
|
||||||
|
|
||||||
return modelClasses
|
return modelClasses
|
||||||
|
|
||||||
|
|
||||||
class AttributeResponse(BaseModel):
|
class AttributeResponse(BaseModel):
|
||||||
"""Response model for entity attributes"""
|
"""Response model for entity attributes"""
|
||||||
|
|
||||||
attributes: List[AttributeDefinition]
|
attributes: List[AttributeDefinition]
|
||||||
|
|
||||||
class Config:
|
model_config = ConfigDict(
|
||||||
schema_extra = {
|
json_schema_extra={
|
||||||
"example": {
|
"example": {
|
||||||
"attributes": [
|
"attributes": [
|
||||||
{
|
{
|
||||||
|
|
@ -305,8 +362,9 @@ class AttributeResponse(BaseModel):
|
||||||
"placeholder": "Please enter username",
|
"placeholder": "Please enter username",
|
||||||
"editable": True,
|
"editable": True,
|
||||||
"visible": True,
|
"visible": True,
|
||||||
"order": 0
|
"order": 0,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ websockets==12.0
|
||||||
uvicorn==0.23.2
|
uvicorn==0.23.2
|
||||||
python-multipart==0.0.6
|
python-multipart==0.0.6
|
||||||
httpx==0.25.0
|
httpx==0.25.0
|
||||||
pydantic==1.10.13 # Ältere Version ohne Rust-Abhängigkeit
|
pydantic>=2.0.0 # Upgraded to v2 for LangChain compatibility
|
||||||
email-validator==2.0.0 # Required by Pydantic for email validation
|
email-validator==2.0.0 # Required by Pydantic for email validation
|
||||||
slowapi==0.1.8 # For rate limiting
|
slowapi==0.1.8 # For rate limiting
|
||||||
|
|
||||||
|
|
@ -108,3 +108,8 @@ xyzservices>=2021.09.1
|
||||||
|
|
||||||
# PostgreSQL connector dependencies
|
# PostgreSQL connector dependencies
|
||||||
psycopg2-binary==2.9.9
|
psycopg2-binary==2.9.9
|
||||||
|
|
||||||
|
## LangChain & LangGraph
|
||||||
|
langchain==0.3.27
|
||||||
|
langgraph==0.6.8
|
||||||
|
langchain-core==0.3.77
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue