124 lines
4.1 KiB
Python
124 lines
4.1 KiB
Python
import importlib
|
||
import inspect
|
||
import os
|
||
import sys
|
||
from functools import lru_cache
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from sqlalchemy import inspect as sa_inspect
|
||
|
||
from config.database import Base
|
||
|
||
|
||
class ImportUtil:
|
||
@classmethod
|
||
def find_project_root(cls) -> Path:
|
||
"""
|
||
查找项目根目录
|
||
|
||
:return: 项目根目录路径
|
||
"""
|
||
current_dir = Path(__file__).resolve().parent
|
||
while current_dir != current_dir.parent:
|
||
if any(current_dir.joinpath(file).exists() for file in ['setup.py', 'pyproject.toml', 'requirements.txt']):
|
||
return current_dir
|
||
current_dir = current_dir.parent
|
||
return Path(__file__).resolve().parent
|
||
|
||
@classmethod
|
||
def is_valid_model(cls, obj: Any, base_class: Base) -> bool:
|
||
"""
|
||
验证是否为有效的SQLAlchemy模型类
|
||
|
||
:param obj: 待验证的对象
|
||
:param base_class: SQLAlchemy的Base类
|
||
:return: 验证结果
|
||
"""
|
||
# 必须继承自Base且不是Base本身
|
||
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: Base) -> list[Base]:
|
||
"""
|
||
查找并过滤有效的模型类,避免重复和无效定义
|
||
|
||
:param base_class: SQLAlchemy的Base类,用于验证模型类
|
||
:return: 有效模型类列表
|
||
"""
|
||
models = []
|
||
# 按类对象去重
|
||
seen_models = set()
|
||
# 按表名去重(防止同表名冲突)
|
||
seen_tables = set()
|
||
project_root = cls.find_project_root()
|
||
|
||
sys.path.append(str(project_root))
|
||
print(f'⏰️ 开始在项目根目录 {project_root} 中查找模型...')
|
||
|
||
# 排除目录扩展
|
||
exclude_dirs = {
|
||
'venv',
|
||
'.env',
|
||
'.git',
|
||
'__pycache__',
|
||
'migrations',
|
||
'alembic',
|
||
'tests',
|
||
'test',
|
||
'docs',
|
||
'examples',
|
||
'scripts',
|
||
}
|
||
|
||
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.endswith('.py') and not file.startswith('__'):
|
||
relative_path = Path(root).relative_to(project_root)
|
||
module_parts = [*list(relative_path.parts), file[:-3]]
|
||
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)
|
||
print(f'✅️ 找到有效模型: {obj.__module__}.{obj.__name__} (表: {table_name})')
|
||
|
||
except ImportError as e:
|
||
if 'cannot import name' not in str(e):
|
||
print(f'❗️ 警告: 无法导入模块 {module_name}: {e}')
|
||
except Exception as e:
|
||
print(f'❌️ 处理模块 {module_name} 时出错: {e}')
|
||
|
||
return models
|