You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iTi-Flask/iti/applications/__init__.py

144 lines
4.5 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import os
import warnings
from collections.abc import Mapping
from apiflask import APIFlask
from iti.applications.common.utils.schema import custom_schema_name_resolver
from iti.applications.service import init_services
from iti.modules import init_modules
from iti.service_client import init_service_clients
from iti.tasks import init_task_runner
from .extensions import init_exts
from .routes import init_routes
from ..config import get_config
from .events import init_event_handlers
def create_app(config_name=None, modules=None, config_mapping=None, model_imports=None):
"""
应用工厂函数
Args:
config_name: 配置名称 ('dev', 'test', 'prod')
如果为 None则从环境变量 FLASK_ENV 读取
modules: 进程内业务模块列表
config_mapping: 业务项目配置映射,格式与 iti.config.config 一致
model_imports: 业务模型导入函数列表。用于让 Flask-Migrate 看到业务模型
Returns:
Flask 应用实例
docs_ui: The UI of API documentation, one of `swagger-ui` (default), `redoc`,
`elements`, `rapidoc`, and `rapipdf`.
"""
# 忽略 apispec 的 schema 名称冲突警告
warnings.filterwarnings(
"ignore",
message="Multiple schemas resolved to the name",
category=UserWarning,
module="apispec.ext.marshmallow.openapi",
)
app = APIFlask(
__name__.split(".")[0],
title="iTi-Flask",
version="1.0.0",
json_errors=True,
docs_ui="elements",
)
# 加载配置
config_obj = _resolve_config(config_name, config_mapping)
app.config.from_object(config_obj)
# 配置自定义 schema 名称解析器
# 参考https://zh.apiflask.com/schema/#%E6%A8%A1%E5%BC%8F%E5%90%8D%E7%A7%B0%E8%A7%A3%E6%9E%90%E5%99%A8
# 用于解决循环引用和嵌套 schema 导致的命名冲突警告
app.schema_name_resolver = custom_schema_name_resolver
# 确保必要的目录存在
_ensure_directories(app)
# 注册框架 CLI
from iti.cli import iti_cli
app.cli.add_command(iti_cli, "iti")
# 使用第三方JWT自定义Security避免doc无法传递header
# 等同于 SECURITY_SCHEMES 配置
app.security_schemes = {
"JWT": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
}
}
# 保护doc文档鉴权后才可访问
# app.config['SPEC_DECORATORS'] = [jwt_required()]
# app.config['DOCS_DECORATORS'] = [jwt_required()]
# 初始化扩展
init_exts(app)
# 导入业务模型,确保 Alembic autogenerate 能看到业务表。
for import_models in model_imports or []:
import_models()
# 初始化可配置服务客户端与任务系统
init_service_clients(app)
init_task_runner(app)
# 初始化业务模块。模块路由会在系统路由之后注册。
module_registry = init_modules(app, modules)
# 初始化事件处理器
init_event_handlers(app)
# 初始化路由
init_routes(app)
module_registry.run_phase("register_routes", app)
module_registry.run_phase("register_permissions", app)
module_registry.run_phase("register_menu_seed", app)
# 初始化Services
init_services(app)
# 打印当前环境信息
env = config_name or os.getenv("FLASK_ENV", "dev")
print(f"🚀 应用启动 - 环境: {env}")
print(f"📊 数据库: {app.config.get('SQLALCHEMY_DATABASE_URI')}")
return app
def _resolve_config(config_name=None, config_mapping=None):
if config_mapping is None:
return get_config(config_name)
env_name = config_name or os.getenv("FLASK_ENV", "dev")
if isinstance(config_mapping, Mapping):
return config_mapping.get(
env_name, config_mapping.get("default", get_config(config_name))
)
return config_mapping
def _ensure_directories(app):
"""确保必要的目录存在"""
# 数据库目录SQLite
db_uri = app.config.get("SQLALCHEMY_DATABASE_URI", "")
if "sqlite:///" in db_uri and not db_uri.endswith(":memory:"):
db_path = db_uri.replace("sqlite:///", "")
db_dir = os.path.dirname(db_path)
if db_dir and not os.path.exists(db_dir):
os.makedirs(db_dir, exist_ok=True)
file_storage_config = app.config.get("FILE_STORAGE", {})
local_config = file_storage_config.get("LOCAL", {})
local_path = local_config.get("base_path")
if local_path:
os.makedirs(local_path, exist_ok=True)