init chongming fastapi 脚手架

This commit is contained in:
2026-01-31 16:00:12 +08:00
parent aed8792664
commit 4b4772ac6e
42 changed files with 1785 additions and 0 deletions
@@ -0,0 +1,74 @@
# main.py - 修复 reload 问题
#!/usr/bin/env python3
"""
FastAPI + Tortoise-ORM + Chongming 示例
主入口文件,适配 chongming 打包系统
"""
import sys
import os
from utils.launch import launch
from multiprocessing import freeze_support
def app_run(app_config: dict, default_config: dict):
"""
应用运行函数 - 适配模块银行环境
Args:
app_config: 应用配置
default_config: 默认配置
"""
import uvicorn
from app.logger import setup_logging, get_logger # type: ignore
setup_logging(app_config["logging"])
logger = get_logger("app")
# 获取服务器配置
server_config = app_config.get("server", {})
host = server_config.get("host", "0.0.0.0")
port = server_config.get("port", 8000)
# 打包环境强制禁用重载
is_frozen = getattr(sys, 'frozen', False)
reload = False if is_frozen else server_config.get("reload", False)
workers = server_config.get("workers", 4)
# 显示启动信息
env = app_config.get("env", "development")
logger.info("=" * 50)
logger.info(f"🚀 启动 {default_config.get('app.name', 'FastAPI 应用')}")
logger.info(f"📦 版本: {default_config.get('app.version', '1.0.0')}")
logger.info(f"🌍 环境: {env}")
logger.info(f"📍 地址: http://{host}:{port}")
logger.info(f"📚 文档: http://{host}:{port}/docs")
logger.info(f"🔧 调试: {'启用' if app_config.get('debug', False) else '禁用'}")
logger.info(f"🔄 重载: {'启用' if reload else '禁用'}")
logger.info(f"👥 Workers: {workers}")
logger.info("=" * 50)
# 关键修改:根据 reload 模式选择不同的启动方式
uvicorn.run(
"app:app",
host=host,
port=port,
reload=reload,
workers=workers,
log_level="debug" if app_config.get("debug", False) else "info",
access_log=True,
)
def main():
freeze_support()
# 如果是打包后的可执行文件,调整路径
if getattr(sys, 'frozen', False):
exe_dir = os.path.dirname(sys.executable)
if exe_dir not in sys.path:
sys.path.insert(0, exe_dir)
# 启动应用
launch(app_run, "development")
if __name__ == "__main__":
main()
@@ -0,0 +1,82 @@
# config_loader.py - 读取config.toml的示例代码
import toml
import os
from pathlib import Path
from typing import Any, Dict, Optional
class ConfigLoader:
"""TOML配置文件加载器"""
def __init__(self, config_path: Optional[str] = None):
"""
初始化配置加载器
Args:
config_path: 配置文件路径,如果为None则自动查找
"""
if config_path is None:
# 自动查找配置文件
config_path = self._find_config_file()
self.config_path = Path(config_path)
self._config: Dict[str, Any] = {}
self.load()
def _find_config_file(self) -> str:
"""自动查找配置文件"""
possible_paths = [
"config.toml",
"config/config.toml",
"~/.config/myapp/config.toml",
"/etc/myapp/config.toml"
]
for path in possible_paths:
expanded_path = os.path.expanduser(path)
if os.path.exists(expanded_path):
return expanded_path
raise FileNotFoundError("未找到配置文件")
def load(self) -> None:
"""加载配置文件"""
if not self.config_path.exists():
raise FileNotFoundError(f"配置文件不存在: {self.config_path}")
with open(self.config_path, 'r', encoding='utf-8') as f:
self._config = toml.load(f)
def get(self, key: str, default: Any = None) -> Any:
"""获取配置值,支持点号分隔的路径"""
keys = key.split('.')
value = self._config
try:
for k in keys:
value = value[k]
return value
except (KeyError, TypeError):
return default
def get_section(self, section: str) -> Dict[str, Any]:
"""获取整个配置节"""
return self._config.get(section, {})
def reload(self) -> None:
"""重新加载配置文件"""
self.load()
def __getitem__(self, key: str) -> Any:
"""支持字典式访问"""
return self.get(key)
def __contains__(self, key: str) -> bool:
"""检查配置是否存在"""
return self.get(key) is not None
@property
def all(self) -> Dict[str, Any]:
"""获取所有配置"""
return self._config
@@ -0,0 +1,49 @@
import sys
from .config import ConfigLoader
import argparse
import os
from module_bank import PythonToSQLite
from typing import Callable
# 检查是否在 PyInstaller 子进程中
def is_pyinstaller_child():
return len(sys.argv) > 1 and ('_child.py' in sys.argv[0] or sys.argv[1].isdigit())
def parse_args():
"""安全的参数解析函数"""
if is_pyinstaller_child():
# PyInstaller 子进程,使用默认参数
return argparse.Namespace(config="config.toml")
parser = argparse.ArgumentParser(description="chongming 应用启动脚本")
parser.add_argument("--config", default="config.toml", help="配置文件路径")
return parser.parse_args()
args = parse_args()
def init_module_bank(config):
MODULE_BANK_PATH_LIST = config["production"]["module_system"]["path"]
for module_path in MODULE_BANK_PATH_LIST:
if os.path.exists(module_path) == False:
raise ValueError(f"模块路径不存在: {module_path}")
packer_list = [PythonToSQLite(module_path) for module_path in MODULE_BANK_PATH_LIST]
for packer in packer_list:
modules = packer.list_modules()
for module in modules:
print(f"{module['module_name']} {'[包]' if module['is_package'] else ''}")
packer.install_importer()
def launch(run_fun: Callable[[dict, dict], None], run_mode: str):
config = ConfigLoader(args.config)
# 获取对应运行模式的配置
default_config = config.get("default", {})
run_mode = default_config.get("env", "development")
app_config = config.get(run_mode, {})
if run_mode == "production":
init_module_bank(config)
run_fun(app_config, default_config)