python-library-observer 0.1.0__tar.gz → 0.1.3__tar.gz
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.
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/.gitignore +3 -1
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/PKG-INFO +1 -1
- python_library_observer-0.1.3/README.md +30 -0
- python_library_observer-0.1.0/example.py → python_library_observer-0.1.3/example/demo.py +31 -9
- python_library_observer-0.1.3/observer/bus.py +293 -0
- python_library_observer-0.1.3/observer/context.py +51 -0
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/observer/deractor.py +15 -4
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/pyproject.toml +1 -1
- python_library_observer-0.1.3/tests/test_observer.py +540 -0
- python_library_observer-0.1.0/observer/bus.py +0 -88
- python_library_observer-0.1.0/observer/context.py +0 -29
- python_library_observer-0.1.0/tests/test_observer.py +0 -562
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/example.bat +0 -0
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/observer/__init__.py +0 -0
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/test.bat +0 -0
- {python_library_observer-0.1.0 → python_library_observer-0.1.3}/tests/__init__.py +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# python-library-observer
|
|
2
|
+
|
|
3
|
+
## 设计特性
|
|
4
|
+
|
|
5
|
+
### 同步异步分流
|
|
6
|
+
|
|
7
|
+
普通函数监听走线程池;`async` 监听走库自带的一条后台事件循环。不必为了观测再自己起一套 asyncio。
|
|
8
|
+
|
|
9
|
+
### 单次调用记录
|
|
10
|
+
|
|
11
|
+
被包一层的方法每跑一次,就有一份记录:哪个类、哪个方法、参数、当前是 before / after / 还是出错、大概多久。同一次调用里多段通知共用一个编号,方便对上是不是同一件事。
|
|
12
|
+
|
|
13
|
+
### 条件订阅
|
|
14
|
+
|
|
15
|
+
登记监听时可以加条件,例如只在某个类名、方法名或阶段才执行。写了不存在的字段名会直接报错,不会悄悄永远不触发。
|
|
16
|
+
|
|
17
|
+
### 类级包装
|
|
18
|
+
|
|
19
|
+
类上套装饰器后,实例方法、`@classmethod`、`@staticmethod` 会多一层包装;`@property` 不动。子类新方法也会自动跟上。
|
|
20
|
+
|
|
21
|
+
### 关闭与清理
|
|
22
|
+
|
|
23
|
+
可手动关总线,也可用 `with`。会尽量等正在跑的监听结束,再停线程池和后台事件循环线程。
|
|
24
|
+
|
|
25
|
+
## 配置
|
|
26
|
+
|
|
27
|
+
### `max_workers`
|
|
28
|
+
|
|
29
|
+
| 含义 | 默认 | 说明 |
|
|
30
|
+
| 同步监听最多用几个线程 | 不传就用系统默认 | 回调里如果会卡住(等 IO 等),可以适当调大;别指望无限变快 |
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
1
4
|
from observer import ObserverBus, ObserverContext
|
|
2
5
|
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(level=logging.INFO)
|
|
8
|
+
|
|
3
9
|
bus = ObserverBus()
|
|
4
10
|
|
|
5
11
|
|
|
@@ -12,7 +18,8 @@ def log_all(ctx: ObserverContext) -> None:
|
|
|
12
18
|
|
|
13
19
|
|
|
14
20
|
@bus.callback(cls_name="Demo", phase="after")
|
|
15
|
-
def log_demo_after(ctx: ObserverContext) -> None:
|
|
21
|
+
async def log_demo_after(ctx: ObserverContext) -> None:
|
|
22
|
+
await asyncio.sleep(0.01)
|
|
16
23
|
print(
|
|
17
24
|
f"[DEMO AFTER] {ctx.cls_name}.{ctx.method_name} "
|
|
18
25
|
f"result={ctx.result} elapsed={ctx.elapsed:.6f}s"
|
|
@@ -24,6 +31,11 @@ def log_ping(ctx: ObserverContext) -> None:
|
|
|
24
31
|
print(f"[PING] result={ctx.result}")
|
|
25
32
|
|
|
26
33
|
|
|
34
|
+
@bus.callback(method_name="add", phase="after")
|
|
35
|
+
def broken_callback(ctx: ObserverContext) -> None:
|
|
36
|
+
raise RuntimeError("callback boom")
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
@bus.observe()
|
|
28
40
|
class Demo:
|
|
29
41
|
def add(self, a: int, b: int) -> int:
|
|
@@ -37,6 +49,10 @@ class Demo:
|
|
|
37
49
|
def ping(msg: str) -> str:
|
|
38
50
|
return f"pong:{msg}"
|
|
39
51
|
|
|
52
|
+
async def async_add(self, a: int, b: int) -> int:
|
|
53
|
+
await asyncio.sleep(0.01)
|
|
54
|
+
return a + b
|
|
55
|
+
|
|
40
56
|
|
|
41
57
|
class ChildDemo(Demo):
|
|
42
58
|
def sub(self, a: int, b: int) -> int:
|
|
@@ -46,12 +62,18 @@ class ChildDemo(Demo):
|
|
|
46
62
|
return a + b + 100
|
|
47
63
|
|
|
48
64
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
print(
|
|
52
|
-
print(Demo.
|
|
65
|
+
async def main() -> None:
|
|
66
|
+
d = Demo()
|
|
67
|
+
print(d.add(1, 2))
|
|
68
|
+
print(Demo.build("x"))
|
|
69
|
+
print(Demo.ping("hello"))
|
|
70
|
+
print(await d.async_add(3, 4))
|
|
71
|
+
|
|
72
|
+
child = ChildDemo()
|
|
73
|
+
print(child.add(1, 2))
|
|
74
|
+
print(child.sub(5, 3))
|
|
75
|
+
print(ChildDemo.ping("world"))
|
|
76
|
+
|
|
53
77
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
print(child.sub(5, 3))
|
|
57
|
-
print(ChildDemo.ping("world"))
|
|
78
|
+
asyncio.run(main())
|
|
79
|
+
bus.close(wait=True)
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import concurrent.futures
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from threading import Event, RLock, Thread
|
|
9
|
+
from typing import Any, TypeVar
|
|
10
|
+
|
|
11
|
+
from .context import ObserverContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T")
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
ObserverSyncCallback = Callable[[ObserverContext], None]
|
|
19
|
+
ObserverAsyncCallback = Callable[[ObserverContext], Awaitable[None]]
|
|
20
|
+
ObserverCallback = Callable[[ObserverContext], object]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ObserverBus:
|
|
24
|
+
"""承载订阅、派发与收尾观测总线。
|
|
25
|
+
|
|
26
|
+
在独立线程池中执行同步回调,在自建事件循环线程上调度协程回调;与业务线程解耦。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, *, max_workers: int | None = None) -> None:
|
|
30
|
+
"""创建总线并启动后台执行设施。
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
max_workers: 同步回调所用线程池的最大工作线程数;未指定时由标准库采用默认策略。
|
|
34
|
+
"""
|
|
35
|
+
self._callbacks: list[tuple[ObserverCallback, dict[str, Any]]] = []
|
|
36
|
+
self._pending: set[concurrent.futures.Future[Any]] = set()
|
|
37
|
+
self._lock = RLock()
|
|
38
|
+
self._closed = False
|
|
39
|
+
|
|
40
|
+
self._executor = concurrent.futures.ThreadPoolExecutor(
|
|
41
|
+
max_workers=max_workers,
|
|
42
|
+
thread_name_prefix="observer-callback",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
self._loop = asyncio.new_event_loop()
|
|
46
|
+
self._loop_ready = Event()
|
|
47
|
+
self._loop_thread = Thread(
|
|
48
|
+
target=self._run_event_loop,
|
|
49
|
+
name="observer-async-loop",
|
|
50
|
+
daemon=True,
|
|
51
|
+
)
|
|
52
|
+
self._loop_thread.start()
|
|
53
|
+
self._loop_ready.wait()
|
|
54
|
+
|
|
55
|
+
def subscribe(
|
|
56
|
+
self,
|
|
57
|
+
callback: ObserverCallback,
|
|
58
|
+
**filters: Any,
|
|
59
|
+
) -> ObserverCallback:
|
|
60
|
+
"""注册监听,在派发上下文与附带条件一致时触发。
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
callback: 同步函数、协程函数,或协程可调用实例;入参为当前派发快照。
|
|
64
|
+
**filters: 与快照各槽位逐项相等的匹配条件;留空表示每次派发都考虑该回调。
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
传入的监听对象,便于链式注册或稍后取消订阅。
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
RuntimeError: 总线已关闭后仍尝试注册。
|
|
71
|
+
ValueError: 匹配条件里出现了快照中不存在的字段名。
|
|
72
|
+
"""
|
|
73
|
+
self._validate_filters(filters)
|
|
74
|
+
|
|
75
|
+
with self._lock:
|
|
76
|
+
if self._closed:
|
|
77
|
+
raise RuntimeError("observer bus is closed")
|
|
78
|
+
|
|
79
|
+
exists = any(
|
|
80
|
+
registered_callback is callback and registered_filters == filters
|
|
81
|
+
for registered_callback, registered_filters in self._callbacks
|
|
82
|
+
)
|
|
83
|
+
if not exists:
|
|
84
|
+
self._callbacks.append((callback, dict(filters)))
|
|
85
|
+
return callback
|
|
86
|
+
|
|
87
|
+
def unsubscribe(self, callback: ObserverCallback) -> None:
|
|
88
|
+
"""按对象身份移除此前注册过的监听,与注册时使用的过滤条件无关。
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
callback: 与订阅时传入的同一可调用对象。
|
|
92
|
+
"""
|
|
93
|
+
with self._lock:
|
|
94
|
+
self._callbacks = [
|
|
95
|
+
(registered_callback, registered_filters)
|
|
96
|
+
for registered_callback, registered_filters in self._callbacks
|
|
97
|
+
if registered_callback is not callback
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
def callback(self, **filters: Any):
|
|
101
|
+
"""返回用于声明式注册监听的装饰器工厂。
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
**filters: 与订阅接口相同的匹配条件;非法字段名在装饰器应用时即校验。
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
接收可调用对象并完成注册的装饰器。
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ValueError: 匹配条件里出现了快照中不存在的字段名。
|
|
111
|
+
"""
|
|
112
|
+
self._validate_filters(filters)
|
|
113
|
+
|
|
114
|
+
def decorator(fn: ObserverCallback) -> ObserverCallback:
|
|
115
|
+
return self.subscribe(fn, **filters)
|
|
116
|
+
|
|
117
|
+
return decorator
|
|
118
|
+
|
|
119
|
+
def emit(self, ctx: ObserverContext) -> None:
|
|
120
|
+
"""向当前所有监听异步投递一份上下文快照;总线已关闭时静默忽略。
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
ctx: 本次派发携带的快照,监听侧按匹配条件决定是否执行。
|
|
124
|
+
"""
|
|
125
|
+
with self._lock:
|
|
126
|
+
if self._closed:
|
|
127
|
+
return
|
|
128
|
+
callbacks = tuple(self._callbacks)
|
|
129
|
+
|
|
130
|
+
for callback, filters in callbacks:
|
|
131
|
+
if not self._match_filters(ctx, filters):
|
|
132
|
+
continue
|
|
133
|
+
self._submit_callback(callback, ctx)
|
|
134
|
+
|
|
135
|
+
def observe(
|
|
136
|
+
self,
|
|
137
|
+
*,
|
|
138
|
+
include_private: bool = False,
|
|
139
|
+
emit_before: bool = True,
|
|
140
|
+
) -> Callable[[type[T]], type[T]]:
|
|
141
|
+
"""返回类装饰器,为被装饰类的方法挂上与当前总线关联的观测包装。
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
include_private: 为真时下划线开头的方法也会被包装。
|
|
145
|
+
emit_before: 为真时在目标方法体执行前额外派发切入阶段快照。
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
接收类型对象并原地改写其可调用成员的类装饰器。
|
|
149
|
+
"""
|
|
150
|
+
from .deractor import observe_methods
|
|
151
|
+
|
|
152
|
+
return observe_methods(
|
|
153
|
+
self,
|
|
154
|
+
include_private=include_private,
|
|
155
|
+
emit_before=emit_before,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def close(self, *, wait: bool = True, timeout: float | None = None) -> None:
|
|
159
|
+
"""停止接受新订阅并尽量结束在途回调。
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
wait: 为真时先等待已提交任务结束再关停执行设施。
|
|
163
|
+
timeout: 等待在途同步任务或后台线程结束的上限秒数;与标准库等待语义一致。
|
|
164
|
+
"""
|
|
165
|
+
with self._lock:
|
|
166
|
+
if self._closed:
|
|
167
|
+
return
|
|
168
|
+
self._closed = True
|
|
169
|
+
pending = tuple(self._pending)
|
|
170
|
+
|
|
171
|
+
if wait and pending:
|
|
172
|
+
concurrent.futures.wait(pending, timeout=timeout)
|
|
173
|
+
|
|
174
|
+
self._executor.shutdown(wait=wait, cancel_futures=not wait)
|
|
175
|
+
|
|
176
|
+
if not self._loop.is_closed():
|
|
177
|
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
178
|
+
|
|
179
|
+
join_timeout = timeout if timeout is not None else (None if wait else 0.0)
|
|
180
|
+
self._loop_thread.join(join_timeout)
|
|
181
|
+
|
|
182
|
+
def __enter__(self) -> ObserverBus:
|
|
183
|
+
"""进入上下文管理作用域时返回自身。"""
|
|
184
|
+
return self
|
|
185
|
+
|
|
186
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
187
|
+
"""离开上下文管理作用域时按同步等待策略关闭总线。
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
exc_type: 离开作用域时未处理异常的型别;无异常时为 None。
|
|
191
|
+
exc: 异常实例或 None。
|
|
192
|
+
tb: 对应回溯或 None。
|
|
193
|
+
"""
|
|
194
|
+
self.close(wait=True)
|
|
195
|
+
|
|
196
|
+
def _submit_callback(self, callback: ObserverCallback, ctx: ObserverContext) -> None:
|
|
197
|
+
if self._is_async_callback(callback):
|
|
198
|
+
future = asyncio.run_coroutine_threadsafe(
|
|
199
|
+
self._run_async_callback(callback, ctx),
|
|
200
|
+
self._loop,
|
|
201
|
+
)
|
|
202
|
+
else:
|
|
203
|
+
future = self._executor.submit(callback, ctx)
|
|
204
|
+
|
|
205
|
+
self._track_future(callback, future)
|
|
206
|
+
|
|
207
|
+
def _track_future(
|
|
208
|
+
self,
|
|
209
|
+
callback: ObserverCallback,
|
|
210
|
+
future: concurrent.futures.Future[Any],
|
|
211
|
+
) -> None:
|
|
212
|
+
with self._lock:
|
|
213
|
+
if self._closed:
|
|
214
|
+
future.cancel()
|
|
215
|
+
return
|
|
216
|
+
self._pending.add(future)
|
|
217
|
+
|
|
218
|
+
future.add_done_callback(
|
|
219
|
+
lambda done_future: self._on_future_done(callback, done_future)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _on_future_done(
|
|
223
|
+
self,
|
|
224
|
+
callback: ObserverCallback,
|
|
225
|
+
future: concurrent.futures.Future[Any],
|
|
226
|
+
) -> None:
|
|
227
|
+
with self._lock:
|
|
228
|
+
self._pending.discard(future)
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
future.result()
|
|
232
|
+
except concurrent.futures.CancelledError:
|
|
233
|
+
return
|
|
234
|
+
except Exception:
|
|
235
|
+
logger.exception(
|
|
236
|
+
"observer callback failed: %s",
|
|
237
|
+
self._callback_name(callback),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def _run_event_loop(self) -> None:
|
|
241
|
+
asyncio.set_event_loop(self._loop)
|
|
242
|
+
self._loop_ready.set()
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
self._loop.run_forever()
|
|
246
|
+
finally:
|
|
247
|
+
pending = asyncio.all_tasks(self._loop)
|
|
248
|
+
for task in pending:
|
|
249
|
+
task.cancel()
|
|
250
|
+
|
|
251
|
+
if pending:
|
|
252
|
+
self._loop.run_until_complete(
|
|
253
|
+
asyncio.gather(*pending, return_exceptions=True)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
self._loop.run_until_complete(self._loop.shutdown_asyncgens())
|
|
257
|
+
self._loop.close()
|
|
258
|
+
|
|
259
|
+
async def _run_async_callback(
|
|
260
|
+
self,
|
|
261
|
+
callback: ObserverCallback,
|
|
262
|
+
ctx: ObserverContext,
|
|
263
|
+
) -> None:
|
|
264
|
+
result = callback(ctx)
|
|
265
|
+
if inspect.isawaitable(result):
|
|
266
|
+
await result
|
|
267
|
+
|
|
268
|
+
def _is_async_callback(self, callback: ObserverCallback) -> bool:
|
|
269
|
+
if inspect.iscoroutinefunction(callback):
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
call = getattr(callback, "__call__", None)
|
|
273
|
+
if call is not None and inspect.iscoroutinefunction(call):
|
|
274
|
+
return True
|
|
275
|
+
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
def _callback_name(self, callback: ObserverCallback) -> str:
|
|
279
|
+
return getattr(callback, "__qualname__", getattr(callback, "__name__", repr(callback)))
|
|
280
|
+
|
|
281
|
+
def _match_filters(self, ctx: ObserverContext, filters: dict[str, Any]) -> bool:
|
|
282
|
+
for key, expected in filters.items():
|
|
283
|
+
if getattr(ctx, key) != expected:
|
|
284
|
+
return False
|
|
285
|
+
return True
|
|
286
|
+
|
|
287
|
+
def _validate_filters(self, filters: dict[str, Any]) -> None:
|
|
288
|
+
valid_keys = set(ObserverContext.__dataclass_fields__.keys())
|
|
289
|
+
invalid_keys = [key for key in filters if key not in valid_keys]
|
|
290
|
+
if invalid_keys:
|
|
291
|
+
raise ValueError(
|
|
292
|
+
f"invalid callback filters: {', '.join(sorted(invalid_keys))}"
|
|
293
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
ObserverPhase = Literal["before", "after", "error"]
|
|
8
|
+
ObserverKind = Literal["instance", "class", "static"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(slots=True)
|
|
12
|
+
class ObserverContext:
|
|
13
|
+
"""一次方法被观测时,某一时刻要交给监听者的信息包。"""
|
|
14
|
+
|
|
15
|
+
# 标识与归属
|
|
16
|
+
call_id: str
|
|
17
|
+
"""同一次调用共用的编号,before/after/error 对得上"""
|
|
18
|
+
instance: Any | None
|
|
19
|
+
"""实例方法里的 self;不是实例方法则为空"""
|
|
20
|
+
owner: Any | None
|
|
21
|
+
"""类方法里的类;其它情况多为空"""
|
|
22
|
+
cls: type
|
|
23
|
+
"""当作哪个类来记;实例上一般是真实类型"""
|
|
24
|
+
cls_name: str
|
|
25
|
+
"""类名,用来过滤、打印"""
|
|
26
|
+
method_name: str
|
|
27
|
+
"""方法短名,用来过滤"""
|
|
28
|
+
qualname: str
|
|
29
|
+
"""带类前缀的方法名"""
|
|
30
|
+
method_kind: ObserverKind
|
|
31
|
+
"""实例 / 类方法 / 静态"""
|
|
32
|
+
# 载荷
|
|
33
|
+
args: tuple[Any, ...]
|
|
34
|
+
"""位置参数(已去掉 self/cls 这类接收者)"""
|
|
35
|
+
kwargs: dict[str, Any]
|
|
36
|
+
"""关键字参数"""
|
|
37
|
+
result: Any = None
|
|
38
|
+
"""正常返回时的结果;还没执行完可能没有"""
|
|
39
|
+
error: BaseException | None = None
|
|
40
|
+
"""出错时的异常;没出错为空"""
|
|
41
|
+
# 阶段与时间
|
|
42
|
+
phase: ObserverPhase = "after"
|
|
43
|
+
"""before、after、error 三选一"""
|
|
44
|
+
is_async: bool = False
|
|
45
|
+
"""是不是 async 方法"""
|
|
46
|
+
started_at: float = 0.0
|
|
47
|
+
"""开始时刻(性能计数器)"""
|
|
48
|
+
ended_at: float = 0.0
|
|
49
|
+
"""结束时刻;刚开始还没结束可能是 0"""
|
|
50
|
+
elapsed: float = 0.0
|
|
51
|
+
"""这段调用花了多少秒"""
|
|
@@ -5,11 +5,12 @@ import inspect
|
|
|
5
5
|
import time
|
|
6
6
|
import uuid
|
|
7
7
|
from collections.abc import Callable
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, TypeVar
|
|
9
9
|
|
|
10
10
|
from .bus import ObserverBus
|
|
11
|
-
from .context import ObserverContext, ObserverKind, ObserverPhase
|
|
12
11
|
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
from .context import ObserverContext, ObserverKind, ObserverPhase
|
|
13
14
|
|
|
14
15
|
_WRAPPED_FLAG = "__observer_bus_wrapped__"
|
|
15
16
|
_SUBCLASS_HOOK_FLAG = "__observer_bus_subclass_hook_installed__"
|
|
@@ -20,8 +21,18 @@ def observe_methods(
|
|
|
20
21
|
*,
|
|
21
22
|
include_private: bool = False,
|
|
22
23
|
emit_before: bool = True,
|
|
23
|
-
):
|
|
24
|
-
|
|
24
|
+
) -> Callable[[type[T]], type[T]]:
|
|
25
|
+
"""返回类装饰器,为指定类型及其子类挂上与总线关联的方法级观测。
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
bus: 用于派发各阶段快照的总线实例。
|
|
29
|
+
include_private: 为真时下划线开头的方法也会被包装。
|
|
30
|
+
emit_before: 为真时在目标方法体执行前派发切入阶段快照。
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
接收类型对象并原地改写其可调用成员的类装饰器。
|
|
34
|
+
"""
|
|
35
|
+
def decorator(cls: type[T]) -> type[T]:
|
|
25
36
|
_observe_class(
|
|
26
37
|
cls,
|
|
27
38
|
bus=bus,
|