canary-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- canary_framework/__init__.py +31 -0
- canary_framework/core/__init__.py +24 -0
- canary_framework/core/decorators/__init__.py +1 -0
- canary_framework/core/decorators/config.py +58 -0
- canary_framework/core/decorators/lifecycle.py +86 -0
- canary_framework/core/decorators/module.py +66 -0
- canary_framework/core/decorators/service.py +54 -0
- canary_framework/core/engine/canary.py +287 -0
- canary_framework/core/engine/context.py +89 -0
- canary_framework/core/engine/injector.py +37 -0
- canary_framework/core/engine/sorter.py +67 -0
- canary_framework/core/registry/__init__.py +0 -0
- canary_framework/core/registry/registry.py +138 -0
- canary_framework/core/utils/__init__.py +0 -0
- canary_framework/core/utils/naming.py +34 -0
- canary_framework/web/fastapi/__init__.py +21 -0
- canary_framework/web/fastapi/decorators/__init__.py +0 -0
- canary_framework/web/fastapi/decorators/router.py +105 -0
- canary_framework/web/fastapi/decorators/web.py +45 -0
- canary_framework/web/fastapi/web_canary.py +150 -0
- canary_framework-0.1.0.dist-info/METADATA +167 -0
- canary_framework-0.1.0.dist-info/RECORD +24 -0
- canary_framework-0.1.0.dist-info/WHEEL +4 -0
- canary_framework-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""CF 框架 —— 轻量级 Python 服务框架。
|
|
2
|
+
|
|
3
|
+
核心导出:
|
|
4
|
+
- 装饰器: config, service, module, on_init, on_start, on_end
|
|
5
|
+
- 引擎: Canary, Context
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
from canary_framework.core import (
|
|
11
|
+
Canary,
|
|
12
|
+
Context,
|
|
13
|
+
config,
|
|
14
|
+
module,
|
|
15
|
+
on_end,
|
|
16
|
+
on_init,
|
|
17
|
+
on_start,
|
|
18
|
+
service,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Canary",
|
|
23
|
+
"Context",
|
|
24
|
+
"config",
|
|
25
|
+
"module",
|
|
26
|
+
"on_end",
|
|
27
|
+
"on_init",
|
|
28
|
+
"on_start",
|
|
29
|
+
"service",
|
|
30
|
+
"__version__",
|
|
31
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""CF 核心模块 —— 装饰器、引擎、注册中心和工具函数。
|
|
2
|
+
|
|
3
|
+
公开 API:
|
|
4
|
+
- 装饰器: config, service, module, on_init, on_start, on_end
|
|
5
|
+
- 引擎类: Canary, Context
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from canary_framework.core.decorators.config import config
|
|
9
|
+
from canary_framework.core.decorators.lifecycle import on_end, on_init, on_start
|
|
10
|
+
from canary_framework.core.decorators.module import module
|
|
11
|
+
from canary_framework.core.decorators.service import service
|
|
12
|
+
from canary_framework.core.engine.canary import Canary
|
|
13
|
+
from canary_framework.core.engine.context import Context
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"config",
|
|
17
|
+
"service",
|
|
18
|
+
"module",
|
|
19
|
+
"on_init",
|
|
20
|
+
"on_start",
|
|
21
|
+
"on_end",
|
|
22
|
+
"Canary",
|
|
23
|
+
"Context",
|
|
24
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""@config 装饰器 —— 将普通类转换为 pydantic-settings BaseSettings 子类。
|
|
2
|
+
|
|
3
|
+
转换后的类自动读取环境变量和 .env 文件(内置 env_file=".env")。
|
|
4
|
+
配置字段优先级: 环境变量 > .env 文件 > 默认值。
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
@config
|
|
8
|
+
class AppConfig:
|
|
9
|
+
host: str = "0.0.0.0"
|
|
10
|
+
port: int = 8000
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def config(cls: type) -> type:
|
|
19
|
+
"""将普通类转为 BaseSettings 子类,使其具备自动配置加载能力。
|
|
20
|
+
|
|
21
|
+
动态创建逻辑:
|
|
22
|
+
1. 提取原始类的 __annotations__ 作为 pydantic 的字段声明
|
|
23
|
+
2. 提取原始类的类变量作为字段默认值
|
|
24
|
+
3. SettingsConfigDict(env_file=".env") 让 pydantic-settings 自动读取 .env
|
|
25
|
+
4. 用 type() 动态构造新类,保持原始类的 __name__ / __qualname__ / __module__
|
|
26
|
+
|
|
27
|
+
参数可以是无括号装饰器 @config 或有括号 @config(),效果相同。
|
|
28
|
+
"""
|
|
29
|
+
# 提取用户定义的类型注解(pydantic 按此推断字段类型和环境变量映射)
|
|
30
|
+
annotations = getattr(cls, "__annotations__", {})
|
|
31
|
+
|
|
32
|
+
settings_cls = type(
|
|
33
|
+
cls.__name__,
|
|
34
|
+
(BaseSettings,),
|
|
35
|
+
{
|
|
36
|
+
"__annotations__": annotations,
|
|
37
|
+
# BaseSettings 的行为配置
|
|
38
|
+
"model_config": SettingsConfigDict(
|
|
39
|
+
env_file=".env", # 自动从当前目录 .env 读取
|
|
40
|
+
env_file_encoding="utf-8",
|
|
41
|
+
extra="ignore", # 忽略未声明的环境变量
|
|
42
|
+
env_prefix="", # 无前缀,字段名直接对应环境变量键
|
|
43
|
+
),
|
|
44
|
+
# 将原始类的类变量(默认值)复制到新类
|
|
45
|
+
**{
|
|
46
|
+
k: v
|
|
47
|
+
for k, v in vars(cls).items()
|
|
48
|
+
if not k.startswith("__") and k != "__annotations__"
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# 保持元信息一致,方便调试和日志
|
|
54
|
+
settings_cls.__name__ = cls.__name__
|
|
55
|
+
settings_cls.__qualname__ = cls.__qualname__
|
|
56
|
+
settings_cls.__module__ = cls.__module__
|
|
57
|
+
|
|
58
|
+
return settings_cls
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""生命周期钩子装饰器 —— @on_init / @on_start / @on_end。
|
|
2
|
+
|
|
3
|
+
用于标记服务/模块类中的方法为生命周期钩子,框架在对应阶段自动查找并调用。
|
|
4
|
+
钩子方法可以是 sync 或 async,框架通过 asyncio.iscoroutine 自动适配。
|
|
5
|
+
|
|
6
|
+
三个阶段:
|
|
7
|
+
on_init(ctx) — 初始化: 依赖已注入、配置已加载,接收 Context。
|
|
8
|
+
on_start() — 启动: 无参数。
|
|
9
|
+
on_end() — 停止: 无参数,按启动逆序调用。
|
|
10
|
+
|
|
11
|
+
find_hooks 查找策略:
|
|
12
|
+
1. 优先查找被装饰器标记的方法(检查 __cf_on_init__ 等属性)
|
|
13
|
+
2. 回退: 按方法名 on_init / on_start / on_end 直接匹配
|
|
14
|
+
3. 以上均未找到 → 该阶段钩子为 None
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from collections.abc import Callable
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
_CF_ON_INIT = "__cf_on_init__" # 标记: 初始化钩子
|
|
23
|
+
_CF_ON_START = "__cf_on_start__" # 标记: 启动钩子
|
|
24
|
+
_CF_ON_END = "__cf_on_end__" # 标记: 停止钩子
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def on_init(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
28
|
+
"""标记方法为 on_init 钩子。框架在其 init 阶段调用,传入 Context。"""
|
|
29
|
+
setattr(fn, _CF_ON_INIT, True)
|
|
30
|
+
return fn
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def on_start(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
34
|
+
"""标记方法为 on_start 钩子。框架在其 start 阶段调用,无参数。"""
|
|
35
|
+
setattr(fn, _CF_ON_START, True)
|
|
36
|
+
return fn
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def on_end(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
40
|
+
"""标记方法为 on_end 钩子。框架在其 stop 阶段按逆序调用,无参数。"""
|
|
41
|
+
setattr(fn, _CF_ON_END, True)
|
|
42
|
+
return fn
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def find_hooks(instance: object) -> dict[str, Callable[..., Any] | None]:
|
|
46
|
+
"""在服务/模块实例上查找所有生命周期钩子方法。
|
|
47
|
+
|
|
48
|
+
两步查找:
|
|
49
|
+
1. 遍历 dir(instance),通过 __cf_on_init__ 等标记属性匹配装饰的方法。
|
|
50
|
+
2. 回退: 未被装饰但方法名为 on_init/on_start/on_end 的也视作钩子。
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
instance: 服务或模块的类实例。
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
{"on_init": method | None, "on_start": method | None, "on_end": method | None}
|
|
57
|
+
未找到的钩子对应值为 None。
|
|
58
|
+
"""
|
|
59
|
+
hooks: dict[str, Callable[..., Any] | None] = {
|
|
60
|
+
"on_init": None,
|
|
61
|
+
"on_start": None,
|
|
62
|
+
"on_end": None,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# 第一步: 检查装饰器标记
|
|
66
|
+
for attr_name in dir(instance):
|
|
67
|
+
try:
|
|
68
|
+
attr = getattr(instance, attr_name)
|
|
69
|
+
except Exception:
|
|
70
|
+
continue
|
|
71
|
+
if not callable(attr):
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
if getattr(attr, _CF_ON_INIT, False):
|
|
75
|
+
hooks["on_init"] = attr
|
|
76
|
+
elif getattr(attr, _CF_ON_START, False):
|
|
77
|
+
hooks["on_start"] = attr
|
|
78
|
+
elif getattr(attr, _CF_ON_END, False):
|
|
79
|
+
hooks["on_end"] = attr
|
|
80
|
+
|
|
81
|
+
# 第二步: 回退 —— 未标记但恰好名称为钩子名的方法
|
|
82
|
+
for key in ("on_init", "on_start", "on_end"):
|
|
83
|
+
if hooks[key] is None:
|
|
84
|
+
hooks[key] = getattr(instance, key, None)
|
|
85
|
+
|
|
86
|
+
return hooks
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""模块装饰器 —— 将类声明为 CF 框架的模块(服务的组合容器)。
|
|
2
|
+
|
|
3
|
+
模块本身也是服务,支持配置、生命周期钩子。额外的能力是声明 services 子节点列表。
|
|
4
|
+
子服务/子模块通过 services 参数以类列表的形式注册到当前模块。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from canary_framework.core.decorators.service import is_cf_service
|
|
12
|
+
|
|
13
|
+
_CF_MODULE_ATTR = "_cf_module__" # 标记: 属于 CF 模块
|
|
14
|
+
_CF_MODULE_META = "_cf_module_meta__" # 存储: 元数据字典
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def module(
|
|
18
|
+
name: str, # 模块名称,全局唯一
|
|
19
|
+
*,
|
|
20
|
+
config: type | None = None, # 模块配置类(可选,子服务可继承)
|
|
21
|
+
services: list[type] | None = None, # 子服务和子模块类列表(可选)
|
|
22
|
+
):
|
|
23
|
+
"""将类声明为 CF 框架的模块。
|
|
24
|
+
|
|
25
|
+
在类上设置 _cf_module__ = True 标记和 _cf_module_meta__ 元数据字典。
|
|
26
|
+
框架在 _collect 阶段识别并递归处理其 services 子节点。
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
name: 模块名称,全局唯一。
|
|
30
|
+
config: @config 装饰的配置类。子服务未声明 config 时继承此配置。
|
|
31
|
+
services: 子服务和子模块的类列表,每个必须被 @service 或 @module 装饰。
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
TypeError: services 列表中存在未被 @service 或 @module 装饰的类。
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
内层装饰器函数。
|
|
38
|
+
"""
|
|
39
|
+
_config = config
|
|
40
|
+
_services = services or []
|
|
41
|
+
|
|
42
|
+
def decorator(cls: type) -> type:
|
|
43
|
+
# 校验子节点合法性
|
|
44
|
+
for svc_cls in _services:
|
|
45
|
+
if not is_cf_service(svc_cls) and not is_cf_module(svc_cls):
|
|
46
|
+
raise TypeError(
|
|
47
|
+
f"@module '{name}': '{svc_cls.__name__}' is not a @service or @module class."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
meta = {"name": name, "config_cls": _config, "services": _services}
|
|
51
|
+
setattr(cls, _CF_MODULE_ATTR, True)
|
|
52
|
+
setattr(cls, _CF_MODULE_META, meta)
|
|
53
|
+
cls.__cf_name__ = name # type: ignore[attr-defined]
|
|
54
|
+
return cls
|
|
55
|
+
|
|
56
|
+
return decorator
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_cf_module(cls: type) -> bool:
|
|
60
|
+
"""判断类是否被 @module 装饰过。"""
|
|
61
|
+
return bool(getattr(cls, _CF_MODULE_ATTR, False))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_module_meta(cls: type) -> dict[str, Any]:
|
|
65
|
+
"""获取 @module 装饰器设置的元数据字典。"""
|
|
66
|
+
return getattr(cls, _CF_MODULE_META, {})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""服务装饰器 —— 将类声明为 CF 框架的服务(最小运行单元)。
|
|
2
|
+
|
|
3
|
+
服务通过 name 标识自身,通过 deps 声明依赖关系。
|
|
4
|
+
依赖的服务实例由框架自动注入为 snake_case 属性名。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
_CF_SERVICE_ATTR = "__cf_service__" # 标记: 属于 CF 服务
|
|
12
|
+
_CF_SERVICE_META = "__cf_service_meta__" # 存储: 元数据字典
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def service(
|
|
16
|
+
name: str, # 服务名称,全局唯一
|
|
17
|
+
*,
|
|
18
|
+
config: type | None = None, # @config 装饰的配置类(可选)
|
|
19
|
+
deps: list[type] | None = None, # 依赖的服务类列表(可选)
|
|
20
|
+
):
|
|
21
|
+
"""将类声明为 CF 框架的服务。
|
|
22
|
+
|
|
23
|
+
在类上设置 __cf_service__ = True 标记和 __cf_service_meta__ 元数据字典。
|
|
24
|
+
框架在 _collect 阶段识别这些标记并注册该服务。
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name: 服务名称,全局唯一,用于依赖声明和名称索引。
|
|
28
|
+
config: @config 装饰的配置类,None 时从父模块继承。
|
|
29
|
+
deps: 依赖的服务类列表,框架自动将其实例注入为 snake_case 属性。
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
内层装饰器函数。
|
|
33
|
+
"""
|
|
34
|
+
_config = config
|
|
35
|
+
_deps = deps or []
|
|
36
|
+
|
|
37
|
+
def decorator(cls: type) -> type:
|
|
38
|
+
meta = {"name": name, "deps": _deps, "config_cls": _config}
|
|
39
|
+
setattr(cls, _CF_SERVICE_ATTR, True)
|
|
40
|
+
setattr(cls, _CF_SERVICE_META, meta)
|
|
41
|
+
cls.__cf_name__ = name # type: ignore[attr-defined]
|
|
42
|
+
return cls
|
|
43
|
+
|
|
44
|
+
return decorator
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def is_cf_service(cls: type) -> bool:
|
|
48
|
+
"""判断类是否被 @service 装饰过。"""
|
|
49
|
+
return bool(getattr(cls, _CF_SERVICE_ATTR, False))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_service_meta(cls: type) -> dict[str, Any]:
|
|
53
|
+
"""获取 @service 装饰器设置的元数据字典。"""
|
|
54
|
+
return getattr(cls, _CF_SERVICE_META, {})
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""CF 框架核心引擎 —— 生命周期编排器。
|
|
2
|
+
|
|
3
|
+
负责服务的递归发现、依赖校验、拓扑排序、Context 树构建和生命周期钩子调度。
|
|
4
|
+
通过 Canary 和 WebCanary 两个公开类暴露给用户。
|
|
5
|
+
|
|
6
|
+
日志系统:
|
|
7
|
+
框架使用 "cf" 命名空间的 logger,通过 CF_LOG_LEVEL 环境变量控制级别(默认 INFO)。
|
|
8
|
+
日志格式: [CF] [LEVEL] [module] message
|
|
9
|
+
与 uvicorn 日志隔离(不同的 logger 名称),不会重复输出。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
from canary_framework.core.decorators.lifecycle import find_hooks
|
|
19
|
+
from canary_framework.core.decorators.module import get_module_meta, is_cf_module
|
|
20
|
+
from canary_framework.core.decorators.service import get_service_meta, is_cf_service
|
|
21
|
+
from canary_framework.core.engine.context import Context
|
|
22
|
+
from canary_framework.core.engine.injector import inject_deps
|
|
23
|
+
from canary_framework.core.engine.sorter import topological_sort
|
|
24
|
+
from canary_framework.core.registry.registry import Registry, ServiceEntry
|
|
25
|
+
|
|
26
|
+
# 框架日志系统
|
|
27
|
+
# 格式 [CF] [LEVEL] [模块] 消息,与 uvicorn 的 root logger 完全隔离(cf 命名空间)
|
|
28
|
+
_cf_logger = logging.getLogger("cf")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _init_logging() -> None:
|
|
32
|
+
"""初始化框架日志:从 CF_LOG_LEVEL 环境变量读取级别,配置格式和 handler。
|
|
33
|
+
|
|
34
|
+
只会配置一次(幂等),避免重复添加 handler。
|
|
35
|
+
logger.propagate = False 确保日志不传播到 root logger,不与 uvicorn 冲突。
|
|
36
|
+
"""
|
|
37
|
+
level_name = os.environ.get("CF_LOG_LEVEL", "INFO").upper()
|
|
38
|
+
level = getattr(logging, level_name, logging.INFO)
|
|
39
|
+
_cf_logger.setLevel(level)
|
|
40
|
+
|
|
41
|
+
# 幂等:已经配置过 handler 则跳过
|
|
42
|
+
if _cf_logger.handlers:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
handler = logging.StreamHandler()
|
|
46
|
+
handler.setFormatter(logging.Formatter("[CF] [%(levelname)-5s] [%(name)s] %(message)s"))
|
|
47
|
+
_cf_logger.addHandler(handler)
|
|
48
|
+
# 禁止向 root logger 传播,避免 uvicorn 或其他库的 root handler 重复打印
|
|
49
|
+
_cf_logger.propagate = False
|
|
50
|
+
|
|
51
|
+
_cf_logger.debug("Logging initialized at level=%s", level_name)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _get_logger(name: str) -> logging.Logger:
|
|
55
|
+
"""获取 cf 命名空间下的子 logger,如 cf.engine、cf.di、cf.config。"""
|
|
56
|
+
return logging.getLogger(f"cf.{name}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Canary:
|
|
60
|
+
"""Canary 核心引擎 —— 服务生命周期编排器。
|
|
61
|
+
|
|
62
|
+
负责完整的应用生命周期:
|
|
63
|
+
init() → 收集 → 校验 → 拓扑排序 → Context 树 → DI → 配置 → on_init
|
|
64
|
+
start() → 按拓扑序触发 on_start
|
|
65
|
+
stop() → 按逆序触发 on_end
|
|
66
|
+
|
|
67
|
+
Usage:
|
|
68
|
+
app = Canary(MyRootModule)
|
|
69
|
+
await app.init()
|
|
70
|
+
await app.start()
|
|
71
|
+
await app.stop()
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, target: type) -> None:
|
|
75
|
+
self._target = target # 根模块/服务类(入口点)
|
|
76
|
+
self._registry = Registry() # 全局注册中心
|
|
77
|
+
self._startup_order: list[str] = [] # 拓扑排序后的名称列表
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def registry(self) -> Registry:
|
|
81
|
+
"""公开 Registry,供子类(WebCanary)和测试访问。"""
|
|
82
|
+
return self._registry
|
|
83
|
+
|
|
84
|
+
# ── 公开生命周期方法 ─────────────────────────────────
|
|
85
|
+
|
|
86
|
+
async def init(self) -> None:
|
|
87
|
+
"""初始化阶段:收集服务、校验依赖、构建 Context、注入、加载配置、调 on_init。
|
|
88
|
+
|
|
89
|
+
四阶段流程:
|
|
90
|
+
0. _collect() — 递归发现并注册所有 @service / @module 类
|
|
91
|
+
1. _validate() — 校验 deps 中声明的依赖完整性
|
|
92
|
+
2. topological_sort() — Kahn 算法计算启动顺序
|
|
93
|
+
3. _build_context_tree() — 按模块树建立 Context parent 链
|
|
94
|
+
4. 按拓扑序: 依赖注入 → 配置加载 → on_init(ctx)
|
|
95
|
+
"""
|
|
96
|
+
_init_logging()
|
|
97
|
+
engine = _get_logger("engine")
|
|
98
|
+
engine.info("── init start ──")
|
|
99
|
+
|
|
100
|
+
# ── 阶段0: 收集 ──
|
|
101
|
+
engine.debug("Phase 0: collecting services/modules")
|
|
102
|
+
self._collect(self._target)
|
|
103
|
+
|
|
104
|
+
# ── 阶段1: 校验 ──
|
|
105
|
+
engine.debug("Phase 1: validating dependencies")
|
|
106
|
+
self._validate()
|
|
107
|
+
|
|
108
|
+
# ── 阶段2: 拓扑排序 ──
|
|
109
|
+
engine.debug("Phase 2: topological sort")
|
|
110
|
+
self._startup_order = topological_sort(self._registry)
|
|
111
|
+
engine.info(
|
|
112
|
+
"Startup order (%d): %s", len(self._startup_order), " → ".join(self._startup_order)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# ── 阶段3: 构建 Context 链 ──
|
|
116
|
+
engine.debug("Phase 3: building context tree")
|
|
117
|
+
self._build_context_tree(self._target, parent_ctx=None)
|
|
118
|
+
|
|
119
|
+
# ── 阶段4: 逐个初始化 ──
|
|
120
|
+
engine.debug("Phase 4: initializing entries")
|
|
121
|
+
for name in self._startup_order:
|
|
122
|
+
entry = self._registry.get_by_name(name)
|
|
123
|
+
await self._init_entry(entry)
|
|
124
|
+
|
|
125
|
+
engine.info("── init complete (%d services) ──", len(self._startup_order))
|
|
126
|
+
|
|
127
|
+
async def start(self) -> None:
|
|
128
|
+
"""启动阶段:按拓扑序触发所有服务的 on_start 钩子。"""
|
|
129
|
+
engine = _get_logger("engine")
|
|
130
|
+
engine.info("── start ──")
|
|
131
|
+
for name in self._startup_order:
|
|
132
|
+
entry = self._registry.get_by_name(name)
|
|
133
|
+
await self._call_hook(entry, "on_start")
|
|
134
|
+
engine.info("── start complete ──")
|
|
135
|
+
|
|
136
|
+
async def stop(self) -> None:
|
|
137
|
+
"""停止阶段:按逆序触发所有服务的 on_end 钩子。"""
|
|
138
|
+
engine = _get_logger("engine")
|
|
139
|
+
engine.info("── stop ──")
|
|
140
|
+
for name in reversed(self._startup_order):
|
|
141
|
+
entry = self._registry.get_by_name(name)
|
|
142
|
+
await self._call_hook(entry, "on_end")
|
|
143
|
+
engine.info("── stop complete ──")
|
|
144
|
+
|
|
145
|
+
# ── _init_entry: 单个 entry 的初始化 ──────────────────
|
|
146
|
+
|
|
147
|
+
async def _init_entry(self, entry: ServiceEntry) -> None:
|
|
148
|
+
"""对单个注册项执行: 依赖注入 → 配置加载 → 触发 on_init 钩子。"""
|
|
149
|
+
engine = _get_logger("engine")
|
|
150
|
+
config_log = _get_logger("config")
|
|
151
|
+
|
|
152
|
+
engine.info(" init %s", entry.name)
|
|
153
|
+
|
|
154
|
+
# 依赖注入
|
|
155
|
+
inject_deps(entry.instance, entry, self._registry)
|
|
156
|
+
|
|
157
|
+
# 配置加载
|
|
158
|
+
if entry.config_cls is not None:
|
|
159
|
+
entry.config_instance = entry.config_cls()
|
|
160
|
+
config_log.info(
|
|
161
|
+
" %s config loaded: %s",
|
|
162
|
+
entry.name,
|
|
163
|
+
{k: v for k, v in vars(entry.config_instance).items() if not k.startswith("_")},
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
config_log.debug(" %s has no config", entry.name)
|
|
167
|
+
|
|
168
|
+
# on_init 钩子
|
|
169
|
+
await self._call_hook(entry, "on_init", entry.context)
|
|
170
|
+
|
|
171
|
+
# ── _call_hook ─────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
async def _call_hook(entry: ServiceEntry, hook_name: str, *args: object) -> None:
|
|
175
|
+
"""统一钩子调用(sync / async 自动适配)。
|
|
176
|
+
|
|
177
|
+
钩子查找结果缓存在 entry._hooks 上,每个 entry 仅扫描一次 dir(instance)。
|
|
178
|
+
asyncio.iscoroutine 判断方法返回值是否为协程,是则 await。
|
|
179
|
+
"""
|
|
180
|
+
lifecycle = _get_logger("lifecycle")
|
|
181
|
+
if entry._hooks is None:
|
|
182
|
+
entry._hooks = find_hooks(entry.instance)
|
|
183
|
+
fn = entry._hooks.get(hook_name)
|
|
184
|
+
if fn is None:
|
|
185
|
+
lifecycle.debug(" %s.%s: not defined", entry.name, hook_name)
|
|
186
|
+
return
|
|
187
|
+
lifecycle.info(" %s.%s()", entry.name, hook_name)
|
|
188
|
+
result = fn(*args)
|
|
189
|
+
if asyncio.iscoroutine(result):
|
|
190
|
+
await result
|
|
191
|
+
|
|
192
|
+
# ── _collect ─────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
def _collect(
|
|
195
|
+
self,
|
|
196
|
+
cls: type,
|
|
197
|
+
parent_entry: ServiceEntry | None = None,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""递归收集 @service / @module 类,注册到 Registry,建立父子关系。
|
|
200
|
+
|
|
201
|
+
对于模块,递归处理其 services 列表中的子节点。
|
|
202
|
+
对于服务,仅注册自身。
|
|
203
|
+
config_cls 继承: 子节点未声明 config → 从父模块拷贝。
|
|
204
|
+
"""
|
|
205
|
+
if self._registry.has(cls):
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
registry_log = _get_logger("registry")
|
|
209
|
+
|
|
210
|
+
# 分支:模块
|
|
211
|
+
if is_cf_module(cls):
|
|
212
|
+
meta = get_module_meta(cls)
|
|
213
|
+
self._registry.register(cls, is_module=True, meta=meta)
|
|
214
|
+
entry = self._registry.get_by_class(cls)
|
|
215
|
+
entry.parent_entry = parent_entry
|
|
216
|
+
self._inherit_config(entry, parent_entry)
|
|
217
|
+
|
|
218
|
+
registry_log.info(
|
|
219
|
+
" module %-30s config=%s services=%d",
|
|
220
|
+
entry.name,
|
|
221
|
+
entry.config_cls.__name__ if entry.config_cls else "inherit",
|
|
222
|
+
len(meta.get("services", [])),
|
|
223
|
+
)
|
|
224
|
+
for sub_cls in meta.get("services", []):
|
|
225
|
+
self._collect(sub_cls, parent_entry=entry)
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# 分支:服务
|
|
229
|
+
if is_cf_service(cls):
|
|
230
|
+
meta = get_service_meta(cls)
|
|
231
|
+
self._registry.register(cls, is_module=False, meta=meta)
|
|
232
|
+
entry = self._registry.get_by_class(cls)
|
|
233
|
+
entry.parent_entry = parent_entry
|
|
234
|
+
self._inherit_config(entry, parent_entry)
|
|
235
|
+
|
|
236
|
+
registry_log.info(
|
|
237
|
+
" service %-30s config=%s deps=%d",
|
|
238
|
+
entry.name,
|
|
239
|
+
entry.config_cls.__name__ if entry.config_cls else "inherit",
|
|
240
|
+
len(entry.deps),
|
|
241
|
+
)
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
raise TypeError(f"'{cls.__name__}' is not decorated with @service or @module")
|
|
245
|
+
|
|
246
|
+
def _inherit_config(self, entry: ServiceEntry, parent_entry: ServiceEntry | None) -> None:
|
|
247
|
+
"""子节点未声明 config_cls 时,从父模块拷贝。"""
|
|
248
|
+
if entry.config_cls is None and parent_entry is not None:
|
|
249
|
+
entry.config_cls = parent_entry.config_cls
|
|
250
|
+
|
|
251
|
+
# ── _build_context_tree ──────────────────────────────
|
|
252
|
+
|
|
253
|
+
def _build_context_tree(
|
|
254
|
+
self,
|
|
255
|
+
cls: type,
|
|
256
|
+
parent_ctx: Context | None,
|
|
257
|
+
) -> None:
|
|
258
|
+
"""按模块树递归构建 Context parent 链。
|
|
259
|
+
|
|
260
|
+
每个 ServiceEntry 绑定一个 Context,根模块的 parent 为 None。
|
|
261
|
+
子服务的 parent 指向所属模块的 Context。
|
|
262
|
+
"""
|
|
263
|
+
entry = self._registry.get_by_class(cls)
|
|
264
|
+
ctx = Context(entry=entry, parent=parent_ctx, registry=self._registry)
|
|
265
|
+
entry.context = ctx
|
|
266
|
+
|
|
267
|
+
if entry.is_module:
|
|
268
|
+
for sub_cls in entry.sub_services:
|
|
269
|
+
self._build_context_tree(sub_cls, parent_ctx=ctx)
|
|
270
|
+
|
|
271
|
+
# ── _validate ────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
def _validate(self) -> None:
|
|
274
|
+
"""校验所有 deps 中声明的依赖是否都已注册。
|
|
275
|
+
|
|
276
|
+
遍历所有 ServiceEntry,检查 dep_names 中的每个名称是否在 Registry 中存在。
|
|
277
|
+
不存在时抛出 ValueError 并列出所有已注册名称。
|
|
278
|
+
"""
|
|
279
|
+
all_names = set(self._registry.names())
|
|
280
|
+
for entry in self._registry.all_entries():
|
|
281
|
+
for dep_name in entry.dep_names:
|
|
282
|
+
if dep_name not in all_names:
|
|
283
|
+
raise ValueError(
|
|
284
|
+
f"Service '{entry.name}' depends on '{dep_name}', "
|
|
285
|
+
f"but '{dep_name}' is not registered. "
|
|
286
|
+
f"Registered: {sorted(all_names)}"
|
|
287
|
+
)
|