átlagos kiegészítséek jó sok
This commit is contained in:
124
backend/app/models/identity/registry.py
Normal file
124
backend/app/models/identity/registry.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Central Model Registry for Service Finder
|
||||
|
||||
Automatically discovers and imports all SQLAlchemy models from the models directory,
|
||||
ensuring Base.metadata is fully populated with tables, constraints, and indexes.
|
||||
|
||||
Usage:
|
||||
from app.models.registry import Base, get_all_models, ensure_models_loaded
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Type
|
||||
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
# Import the Base from database (circular dependency will be resolved later)
|
||||
# We'll define our own Base if needed, but better to reuse existing one.
|
||||
# We'll import after path setup.
|
||||
|
||||
# Add backend to path if not already
|
||||
backend_dir = Path(__file__).parent.parent.parent
|
||||
if str(backend_dir) not in sys.path:
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
# Import Base from database (this will be the same Base used everywhere)
|
||||
from app.database import Base
|
||||
|
||||
def discover_model_files() -> List[Path]:
|
||||
"""
|
||||
Walk through models directory and collect all .py files except __init__.py and registry.py.
|
||||
"""
|
||||
models_dir = Path(__file__).parent
|
||||
model_files = []
|
||||
for root, _, files in os.walk(models_dir):
|
||||
for file in files:
|
||||
if file.endswith('.py') and file not in ('__init__.py', 'registry.py'):
|
||||
full_path = Path(root) / file
|
||||
model_files.append(full_path)
|
||||
return model_files
|
||||
|
||||
def import_module_from_file(file_path: Path) -> str:
|
||||
"""
|
||||
Import a Python module from its file path.
|
||||
Returns the module name.
|
||||
"""
|
||||
# Compute module name relative to backend/app
|
||||
rel_path = file_path.relative_to(backend_dir)
|
||||
module_name = str(rel_path).replace(os.sep, '.').replace('.py', '')
|
||||
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
||||
if spec is None:
|
||||
raise ImportError(f"Could not load spec for {module_name}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module_name
|
||||
except Exception as e:
|
||||
# Silently skip import errors (maybe due to missing dependencies)
|
||||
# but log for debugging
|
||||
print(f"⚠️ Could not import {module_name}: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
def load_all_models() -> List[str]:
|
||||
"""
|
||||
Dynamically import all model files to populate Base.metadata.
|
||||
Returns list of successfully imported module names.
|
||||
"""
|
||||
model_files = discover_model_files()
|
||||
imported = []
|
||||
for file in model_files:
|
||||
module_name = import_module_from_file(file)
|
||||
if module_name:
|
||||
imported.append(module_name)
|
||||
# Also ensure the __init__.py is loaded (it imports many models manually)
|
||||
try:
|
||||
import app.models
|
||||
imported.append('app.models')
|
||||
except ImportError:
|
||||
pass
|
||||
print(f"✅ Registry loaded {len(imported)} model modules. Total tables in metadata: {len(Base.metadata.tables)}")
|
||||
return imported
|
||||
|
||||
def get_all_models() -> Dict[str, Type[DeclarativeMeta]]:
|
||||
"""
|
||||
Return a mapping of class name to model class for all registered SQLAlchemy models.
|
||||
This works only after models have been imported.
|
||||
"""
|
||||
# This is a heuristic: find all subclasses of Base in loaded modules
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
models = {}
|
||||
for cls in Base.__subclasses__():
|
||||
models[cls.__name__] = cls
|
||||
# Also check deeper inheritance (if models inherit from other models that inherit from Base)
|
||||
for module_name, module in sys.modules.items():
|
||||
if module_name.startswith('app.models.'):
|
||||
for attr_name in dir(module):
|
||||
attr = getattr(module, attr_name)
|
||||
if isinstance(attr, type) and issubclass(attr, Base) and attr is not Base:
|
||||
models[attr.__name__] = attr
|
||||
return models
|
||||
|
||||
def ensure_models_loaded():
|
||||
"""
|
||||
Ensure that all models are loaded into Base.metadata.
|
||||
This is idempotent and can be called multiple times.
|
||||
"""
|
||||
if len(Base.metadata.tables) == 0:
|
||||
load_all_models()
|
||||
else:
|
||||
# Already loaded
|
||||
pass
|
||||
|
||||
# Auto-load models when this module is imported (optional, but useful)
|
||||
# We'll make it explicit via a function call to avoid side effects.
|
||||
# Instead, we'll provide a function to trigger loading.
|
||||
|
||||
# Export
|
||||
__all__ = ['Base', 'discover_model_files', 'load_all_models', 'get_all_models', 'ensure_models_loaded']
|
||||
Reference in New Issue
Block a user