import importlib import inspect import os from functools import lru_cache from pathlib import Path from typing import Any from sqlalchemy import inspect as sa_inspect from app.config.path_conf import BASE_DIR from app.core.exceptions import CustomException class ImportUtil: @classmethod def find_project_root(cls) -> Path: """ 查找项目根目录 :return: 项目根目录路径 """ return BASE_DIR @classmethod def is_valid_model(cls, obj: Any, base_class: type) -> bool: """ 验证是否为有效的SQLAlchemy模型类 :param obj: 待验证的对象 :param base_class: SQLAlchemy的基类 :return: 验证结果 """ # 必须继承自base_class且不是base_class本身 if not (inspect.isclass(obj) and issubclass(obj, base_class) and obj is not base_class): return False # 必须有表名定义(排除抽象基类) if not hasattr(obj, "__tablename__") or obj.__tablename__ is None: return False # 必须有至少一个列定义 try: return len(sa_inspect(obj).columns) > 0 except Exception: return False @classmethod @lru_cache(maxsize=256) def find_models(cls, base_class: type) -> list[Any]: """ 查找并过滤有效的模型类,避免重复和无效定义 :param base_class: SQLAlchemy的Base类,用于验证模型类 :return: 有效模型类列表 """ models = [] # 按类对象去重 seen_models = set() # 按表名去重(防止同表名冲突) seen_tables = set() # 记录已经处理过的model.py文件路径 processed_model_files = set() project_root = cls.find_project_root() # 排除目录扩展 exclude_dirs = { "venv", ".env", ".git", "__pycache__", "migrations", "alembic", "tests", "test", "docs", "examples", "scripts", ".venv", "static", "templates", "sql", "env", } # 定义要搜索的模型目录模式 model_dir_patterns = ["model.py", "models.py"] # 使用一个更高效的方法来查找所有model.py文件 model_files = [] for root, dirs, files in os.walk(project_root): # 过滤排除目录 dirs[:] = [d for d in dirs if d not in exclude_dirs] for file in files: if file in model_dir_patterns: file_path = Path(root) / file # 构建相对于项目根的模块路径 relative_path = file_path.relative_to(project_root) model_files.append((file_path, relative_path)) # 按模块路径排序,确保先导入基础模块 model_files.sort(key=lambda x: str(x[1])) for file_path, relative_path in model_files: # 确保文件路径没有被处理过 if str(file_path) in processed_model_files: continue processed_model_files.add(str(file_path)) # 构建模块名(将路径分隔符转换为点) module_parts = (*relative_path.parts[:-1], relative_path.stem) module_name = ".".join(module_parts) try: # 导入模块 module = importlib.import_module(module_name) # 获取模块中的所有类 for _name, obj in inspect.getmembers(module, inspect.isclass): # 验证模型有效性 if not cls.is_valid_model(obj, base_class): continue # 检查类对象重复 if obj in seen_models: continue # 检查表名重复 table_name = obj.__tablename__ if table_name in seen_tables: continue # 添加到已处理集合 seen_models.add(obj) seen_tables.add(table_name) models.append(obj) except ImportError as e: if "cannot import name" not in str(e): raise ImportError(f"❗️ 警告: 无法导入模块 {module_name}: {e}") except Exception as e: raise CustomException(f"❌️ 处理模块 {module_name} 时出错: {e}") # 查找apscheduler_jobs表的模型(如果存在) cls._find_apscheduler_model(base_class, models, seen_models, seen_tables) return models @classmethod def _find_apscheduler_model( cls, base_class: type, models: list[Any], seen_models: set[Any], seen_tables: set[str], ) -> None: """ 专门查找APScheduler相关的模型 :param base_class: SQLAlchemy的Base类 :param models: 模型列表 :param seen_models: 已处理的模型集合 :param seen_tables: 已处理的表名集合 """ # 尝试从apscheduler相关模块导入 try: # 检查是否有自定义的apscheduler模型 for module_name in [ "app.core.ap_scheduler", "app.module_task.scheduler_test", ]: try: module = importlib.import_module(module_name) for _name, obj in inspect.getmembers(module, inspect.isclass): if ( cls.is_valid_model(obj, base_class) and hasattr(obj, "__tablename__") and obj.__tablename__ == "apscheduler_jobs" ) and (obj not in seen_models and "apscheduler_jobs" not in seen_tables): seen_models.add(obj) seen_tables.add("apscheduler_jobs") models.append(obj) print( f"✅️ 找到有效模型: {obj.__module__}.{obj.__name__} (表: apscheduler_jobs)" ) except ImportError: pass except Exception as e: raise CustomException(f"❗️ 查找APScheduler模型时出错: {e}")