ShiErFeng/shierfeng-fastapi-backend/utils/import_util.py
2026-07-24 09:43:10 +08:00

124 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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