Metadata-Version: 2.4
Name: python-library-callback
Version: 0.2.4
Requires-Python: >=3.10
Requires-Dist: pydantic<3,>=2.0
Description-Content-Type: text/markdown

# callback

## 特性

1. 以 **`Callback`** 或 **`AsyncCallback` 为根** 定义子类，**类型注解** 描述「这一次调用要携带哪些数据」；触发后返回**同一条**已校验、可被处理函数改写的实例，不是新拷贝。

2. 处理函数分 **前、中间、后** 三层；触发时**先整层前、再整层中间、再整层后**；**同一层**里多段逻辑可以**一起收尾**，这一层**全部跑完**才进下一层。

3. **`from callback`** 只面对外给出 **`Callback`** 与 **`AsyncCallback`** 两个根名，其它是细节。

### 同步根和异步根怎么选

| 根类 | 写处理函数时 | 触发时 |
|------|--------------|--------|
| **`Callback`** | 只登记普通 **`def`**；不用事件循环。 | 在同一线程上 **`子类(...)`** 或 **`子类.trigger(...)`**，**同步**返回建好的那一条实例。同层有多个 `def` 时，在**线程池**里并肩跑，层与层之间仍然严格先后。 |
| **`AsyncCallback`** | 只登记 **`async def`**。 | 在协程里 **`await 子类(...)`** 或 **`await 子类.trigger(...)`**；非登记形态下，类调用和 **`trigger` 一样**给出**要 await 的协程**，层内多协程在**当前事件循环**里并发。 |

在 **`async def` 里**要 **await 管线**、不额外嵌一套阻塞，用 **`AsyncCallback`** 子类。同步脚本、同步 Web 视图中只想「一调就结束」时，用 **`Callback`** 子类即可。

### 处理函数必须和根类型一致

- 继承 **`Callback`** 的类：登记 **`async def`** 会 **TypeError**；只接受普通函数。
- 继承 **`AsyncCallback`** 的类：登记非协程的 **`def`** 会 **TypeError**；只接受协程函数。

这样触发时的调度方式与根类型一一对应，不会在同一套子类上混用两套语法。

### 子类名当装饰器、当「触发」

| 形态 | 含义（中间层时） | 与 `Callback` 搭配 | 与 `AsyncCallback` 搭配 |
|------|------------------|----------------------|-------------------------|
| **`@子类` 或 `子类(单参、可调用、无其它参数)`** | 向中间层**登记** | 登记一个 **`def`** | 登记一个 **`async def`** |
| **`子类(字段=…)` 或等价的构造实参** | 按字段**触发** | 直接得到实例 | 得到协程，需 **`await`** |

**单参数、可调用、且没有关键字参数** 的类调用，一律走**登记**；若某次调用的**第一个**数据字段就是可调用对象，用**关键字**传，例如 **`MyType(fn=某函数)`**，避免和 **`@MyType`** 撞形态。

仍可直接用 **`register` / `register_before` / `register_after`**，与上表各层一一对应。

### 子类里哪些名字算「这次调用的数据」

触发时多出来的参数名、没声明的，不能传。只有「**普通类型注解、名字不以 `_` 开头**」的才当作这一次载荷的字段；下划线名用来给**类型检查**看可以；**`ClassVar`** 是类级常量，不算在每条实例的字段里。

| 示例写法 | 算不算载荷字段 |
|----------|-----------------|
| `count: int` | 算 |
| `_tmp: int` | 不算 |
| `foo: ClassVar[...]` | 不算 |

字段可以填**任意 Python 对象**；处理函数和外面读到的是**同一引用**。

### 前、中、后三层和装饰器

| 阶段 | 装饰器 | 与 `register*` |
|------|--------|-----------------|
| 前 | **`@子类.before`** | **`子类.register_before(...)`** |
| 中 | **`@子类` 等** | **`子类.register(...)`** |
| 后 | **`@子类.after`** | **`子类.register_after(...)`** |

中间层和以前「`@子类`」是同一层。可以写带载荷参数的，也可以无参。

每棵子类在类型对象上**各自**挂一套分层登记，**不**和父类共享一份列表。根类型 **`Callback` / `AsyncCallback` 自己** 不再挂可登记容器，只供**往下**继续定义业务子类。

### 同一层里同函数只保留一条

**同一个可调用对象**在同一层里登记多次，内部仍是一条，触发**只跑一遍**。同一段逻辑可以分别落在不同层，那是三层里各记一条、各跑一遍。

### 列出子类、清空登记

- **`子类名.get_all()`**（在根或中间类型上点）：列出**从该类型直接**派生的下一层子类。换一根就要在对应根上再点一次。
- **`Callback.clear_layer_registries()`** 与 **`AsyncCallback.clear_layer_registries()`**：从各自根**向下**清空已登记的处理函数；**两棵根**都用过时，**各清一次**。

```python
@OrderPaid.before
def prepare(cb: OrderPaid) -> None: ...

@OrderPaid
def bump_total(cb: OrderPaid) -> None:
    cb.total += 1

@OrderPaid.after
def notify(cb: OrderPaid) -> None: ...

paid = OrderPaid(order_id="x", total=1)
```

`AsyncCallback` 子类把上面三处都改成 **`async def`**，最后一行在协程里写成 **`paid = await OrderPaid(...)`** 即可。
