anotiflow 0.1.0__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.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.3
2
+ Name: anotiflow
3
+ Version: 0.1.0
4
+ Summary: 可扩展的任务调度通知框架:触发器 + 行为插件 + 事件总线,TOML 配置驱动
5
+ Author: 徐侨
6
+ Author-email: 徐侨 <xu.qiao@kotei.com.cn>
7
+ Requires-Dist: schedule>=1.2
8
+ Requires-Dist: ipush>=0.4
9
+ Requires-Dist: loguru>=0.7
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+
13
+ # anotiflow
14
+
15
+ 可扩展的任务调度通知框架:**触发器 + 行为插件 + 事件总线**,TOML 配置驱动,基于 UV 管理。
16
+
17
+ ```
18
+ 触发器(定时 / 事件)──▶ 任务 ──▶ 多个行为按序执行(飞书 / 钉钉 / 自定义 / 广播事件)
19
+
20
+ └─ bus.publish(...) ──▶ 事件触发其他任务
21
+ ```
22
+
23
+ 一个任务可以绑定 **N 个触发器 + N 个行为**,任意触发器命中即按顺序执行全部行为。通过 `publish_event` 行为或用户自定义函数向 `EventBus` 广播事件,实现任务之间的链式联动。
24
+
25
+ ## 特性
26
+
27
+ - **插件式设计** — 顶层 `Action` / `Trigger` 抽象基类,派生出通知基类 / 具体渠道;装饰器 `@register_action("xxx")` 即可注册新类型,TOML 自动识别
28
+ - **两种触发器** — 定时(基于 `schedule`,**完整保留其原生灵活性**:秒/分/时/天/周、每周一..周日、`at` 精确时刻、`to` 随机区间、`until` 截止时刻)+ 事件(进程内 EventBus 订阅);预留手动触发扩展点
29
+ - **内置通知渠道** — 飞书 / 钉钉(基于 `ipush`),统一继承 `NotifyAction`,一个任务可多渠道同步发送
30
+ - **配置即代码** — TOML 管理任务、触发器、行为参数;新增任务零代码
31
+ - **自定义业务逻辑** — 用户写普通 Python 函数(`fn(context) -> None`),TOML 以 dotted path 引用;通常用于"定时检查 + 满足条件广播事件"的判断层
32
+ - **链式联动** — `EventBus.publish(event, payload)` 与 `EventTrigger` 配对,形成任务间事件链路
33
+ - **工程细节** — loguru 日志、任务启用/禁用、行为级异常捕获、SIGINT/SIGTERM 优雅关闭
34
+
35
+ ## 安装
36
+
37
+ 需要 [UV](https://docs.astral.sh/uv/)。
38
+
39
+ ```bash
40
+ git clone <this-repo>
41
+ cd anotiflow
42
+ uv sync
43
+ ```
44
+
45
+ ## 快速开始
46
+
47
+ ```bash
48
+ uv run anotiflow --config examples/config.toml
49
+ # 可选:--log-level DEBUG
50
+ ```
51
+
52
+ 默认示例会每 5 秒随机模拟一次"股价检查",高于阈值即广播 `stock.high` 事件,触发飞书 / 钉钉通知。将 [examples/config.toml](examples/config.toml) 里的 `token` / `secret` 替换为真实值就能收到真通知;占位值会发送失败但不会让进程崩溃。
53
+
54
+ ## 核心概念
55
+
56
+ ### Task
57
+
58
+ ```
59
+ Task = name + enabled + [Trigger, ...] + [Action, ...]
60
+ ```
61
+
62
+ 任意一个触发器命中 → 按顺序执行所有行为。任一行为抛异常会被记录日志但不影响后续行为 / 其他任务。
63
+
64
+ ### Trigger(触发器)
65
+
66
+ | 类型 | 作用 | 关键字段 |
67
+ |---|---|---|
68
+ | `interval` | 定时,基于 `schedule` | `unit` 必填;`every` / `to` / `at` / `until` 可选 |
69
+ | `event` | 订阅 EventBus 事件 | `event` 事件名 |
70
+
71
+ `interval` 的 `unit` 取值覆盖 `schedule` 的全部灵活性:
72
+
73
+ - `seconds` / `second`、`minutes` / `minute`、`hours` / `hour`、`days` / `day`、`weeks` / `week`
74
+ - 星期名:`monday` / `tuesday` / `wednesday` / `thursday` / `friday` / `saturday` / `sunday`
75
+
76
+ 常见组合:
77
+
78
+ | 需求 | TOML |
79
+ |---|---|
80
+ | 每 5 秒 | `unit="seconds", every=5` |
81
+ | 每 5~10 秒随机 | `unit="seconds", every=5, to=10` |
82
+ | 每分钟的第 23 秒 | `unit="minute", at=":23"` |
83
+ | 每天 09:30 | `unit="day", at="09:30"` |
84
+ | 每周一 13:15 | `unit="monday", at="13:15"` |
85
+ | 每小时执行直到 18:30 | `unit="hour", until="18:30"` |
86
+
87
+ ### Action(行为)
88
+
89
+ 抽象层次:`Action`(顶层)→ `NotifyAction`(通知基类)→ `FeishuNotify` / `DingtalkNotify` / ...
90
+
91
+ 内置类型:
92
+
93
+ | type | 说明 | 关键字段 |
94
+ |---|---|---|
95
+ | `feishu` | 飞书群机器人 | `token`, `secret`, `message_template` |
96
+ | `dingtalk` | 钉钉群机器人 | `token`, `secret`, `title`, `message_template` |
97
+ | `publish_event` | 向 EventBus 广播事件(用于串联任务) | `event`, `[tasks.actions.payload]` |
98
+ | `custom` | 调用用户自定义函数 | `path = "module.func"` |
99
+
100
+ ### Context(行为执行时的上下文)
101
+
102
+ 每次任务触发,框架会组装 context 字典传给每个 action。在 `message_template` / `publish_event.payload` 里用 `{xxx}` 引用:
103
+
104
+ | 字段 | 含义 |
105
+ |---|---|
106
+ | `{task_name}` | 任务名 |
107
+ | `{trigger_name}` | 触发来源描述,如 `interval(every 5 seconds)` / `event(stock.high)` |
108
+ | `{trigger_type}` | `interval` / `event` |
109
+ | `{fired_at}` | 触发时刻 `YYYY-MM-DD HH:MM:SS` |
110
+ | `{trigger_payload}` | 完整业务载荷 dict |
111
+ | `{trigger_payload[symbol]}` | 载荷中某字段 |
112
+
113
+ ### EventBus
114
+
115
+ 进程内线程安全的发布/订阅单例:
116
+
117
+ ```python
118
+ from anotiflow.core.event_bus import bus
119
+ bus.publish("stock.high", {"symbol": "AAPL", "price": 107.6})
120
+ ```
121
+
122
+ ## 配置示例
123
+
124
+ ```toml
125
+ # 任务 1:每 5 秒跑一次自定义业务检查
126
+ [[tasks]]
127
+ name = "check_stock_price"
128
+ enabled = true
129
+
130
+ [[tasks.triggers]]
131
+ type = "interval"
132
+ every = 5
133
+ unit = "seconds"
134
+
135
+ [[tasks.actions]]
136
+ type = "custom"
137
+ path = "examples.user_actions.check_stock_price"
138
+
139
+ # 任务 2:多触发器(定时 + 两个事件),按序发飞书 + 钉钉
140
+ [[tasks]]
141
+ name = "notify_with_multi_triggers"
142
+ enabled = true
143
+
144
+ [[tasks.triggers]]
145
+ type = "interval"
146
+ every = 12
147
+ unit = "seconds"
148
+
149
+ [[tasks.triggers]]
150
+ type = "event"
151
+ event = "stock.high"
152
+
153
+ [[tasks.triggers]]
154
+ type = "event"
155
+ event = "manual.fire"
156
+
157
+ [[tasks.actions]]
158
+ type = "feishu"
159
+ token = "xxxx"
160
+ secret = "yyyy"
161
+ message_template = """[{task_name}] 触发={trigger_name} @ {fired_at}
162
+ 载荷={trigger_payload}"""
163
+
164
+ [[tasks.actions]]
165
+ type = "dingtalk"
166
+ token = "xxxx"
167
+ secret = "yyyy"
168
+ title = "anotiflow 通知"
169
+ message_template = "{trigger_payload[symbol]} = {trigger_payload[price]}"
170
+
171
+ # 任务 3:每天 09:30 广播事件(用 publish_event 串联)
172
+ [[tasks]]
173
+ name = "daily_morning_fire"
174
+ enabled = true
175
+
176
+ [[tasks.triggers]]
177
+ type = "interval"
178
+ unit = "day"
179
+ at = "09:30"
180
+
181
+ [[tasks.actions]]
182
+ type = "publish_event"
183
+ event = "manual.fire"
184
+
185
+ [tasks.actions.payload]
186
+ reason = "morning_cron@{fired_at}"
187
+ ```
188
+
189
+ 完整示例见 [examples/config.toml](examples/config.toml)。
190
+
191
+ ## 自定义业务行为
192
+
193
+ 写一个普通 Python 函数,签名 `fn(context: dict) -> None`:
194
+
195
+ ```python
196
+ # examples/user_actions.py
197
+ from anotiflow.core.event_bus import bus
198
+ from loguru import logger
199
+
200
+ def check_stock_price(context: dict) -> None:
201
+ price = fetch_price("AAPL")
202
+ logger.info(f"[{context['task_name']}] price={price} at {context['fired_at']}")
203
+ if price > 100:
204
+ bus.publish("stock.high", {"symbol": "AAPL", "price": price})
205
+ ```
206
+
207
+ TOML 引用:
208
+
209
+ ```toml
210
+ [[tasks.actions]]
211
+ type = "custom"
212
+ path = "examples.user_actions.check_stock_price"
213
+ ```
214
+
215
+ 模块解析路径:框架会把 CWD 与 config 文件所在目录都加入 `sys.path`,所以 `examples.user_actions.check_stock_price` 在项目根目录执行 `uv run anotiflow` 时能被正确加载。
216
+
217
+ ## 扩展新渠道 / 新触发器
218
+
219
+ 以新增企业微信通知为例:
220
+
221
+ ```python
222
+ # src/anotiflow/actions/wecom.py
223
+ from ipush import WeCom
224
+ from anotiflow.actions.notify_base import NotifyAction
225
+ from anotiflow.core.registry import register_action
226
+
227
+ @register_action("wecom")
228
+ class WeComNotify(NotifyAction):
229
+ def __init__(self, token: str, message_template: str = "") -> None:
230
+ super().__init__(message_template=message_template)
231
+ self.name = "wecom"
232
+ self._client = WeCom(token=token)
233
+
234
+ def _send(self, message: str) -> None:
235
+ self._client.send(message)
236
+ ```
237
+
238
+ 在 [src/anotiflow/actions/__init__.py](src/anotiflow/actions/__init__.py) 里 `import` 该模块触发 `@register_action` 装饰器副作用,之后 TOML 里 `type = "wecom"` 即可使用。
239
+
240
+ 新增触发器同理:继承 `Trigger` + `@register_trigger("your_type")`,在 [src/anotiflow/triggers/__init__.py](src/anotiflow/triggers/__init__.py) 里 `import`。
241
+
242
+ ## 项目结构
243
+
244
+ ```
245
+ anotiflow/
246
+ ├── pyproject.toml
247
+ ├── src/anotiflow/
248
+ │ ├── cli.py # uv run anotiflow 入口
249
+ │ ├── __main__.py # python -m anotiflow
250
+ │ ├── task.py # Task 数据类
251
+ │ ├── logging_setup.py # loguru 初始化
252
+ │ ├── core/
253
+ │ │ ├── event_bus.py # EventBus 单例
254
+ │ │ ├── registry.py # 类型注册表
255
+ │ │ ├── loader.py # TOML → Task[]
256
+ │ │ └── scheduler.py # 主循环 + 优雅关闭
257
+ │ ├── triggers/
258
+ │ │ ├── base.py # Trigger ABC
259
+ │ │ ├── interval.py # 定时触发(schedule)
260
+ │ │ └── event.py # 事件触发(EventBus 订阅)
261
+ │ └── actions/
262
+ │ ├── base.py # Action ABC + CallableAction
263
+ │ ├── notify_base.py # NotifyAction
264
+ │ ├── feishu.py # 飞书通知
265
+ │ ├── dingtalk.py # 钉钉通知
266
+ │ └── publish_event.py # 广播事件行为
267
+ └── examples/
268
+ ├── config.toml # 配置示例(含 4 个任务)
269
+ └── user_actions.py # 自定义业务行为示例
270
+ ```
271
+
272
+ ## 运行
273
+
274
+ ```bash
275
+ uv run anotiflow --config examples/config.toml
276
+ uv run anotiflow --config /path/to/your.toml --log-level DEBUG
277
+ # 等价写法
278
+ uv run python -m anotiflow --config examples/config.toml
279
+ ```
280
+
281
+ `Ctrl-C` 或 `SIGTERM` 会触发 Scheduler 解绑所有触发器并优雅退出。
282
+
283
+ ## 依赖
284
+
285
+ - [schedule](https://pypi.org/project/schedule/) — 定时调度
286
+ - [ipush](https://pypi.org/project/ipush/) — 飞书 / 钉钉等推送渠道统一封装
287
+ - [loguru](https://pypi.org/project/loguru/) — 日志
@@ -0,0 +1,275 @@
1
+ # anotiflow
2
+
3
+ 可扩展的任务调度通知框架:**触发器 + 行为插件 + 事件总线**,TOML 配置驱动,基于 UV 管理。
4
+
5
+ ```
6
+ 触发器(定时 / 事件)──▶ 任务 ──▶ 多个行为按序执行(飞书 / 钉钉 / 自定义 / 广播事件)
7
+
8
+ └─ bus.publish(...) ──▶ 事件触发其他任务
9
+ ```
10
+
11
+ 一个任务可以绑定 **N 个触发器 + N 个行为**,任意触发器命中即按顺序执行全部行为。通过 `publish_event` 行为或用户自定义函数向 `EventBus` 广播事件,实现任务之间的链式联动。
12
+
13
+ ## 特性
14
+
15
+ - **插件式设计** — 顶层 `Action` / `Trigger` 抽象基类,派生出通知基类 / 具体渠道;装饰器 `@register_action("xxx")` 即可注册新类型,TOML 自动识别
16
+ - **两种触发器** — 定时(基于 `schedule`,**完整保留其原生灵活性**:秒/分/时/天/周、每周一..周日、`at` 精确时刻、`to` 随机区间、`until` 截止时刻)+ 事件(进程内 EventBus 订阅);预留手动触发扩展点
17
+ - **内置通知渠道** — 飞书 / 钉钉(基于 `ipush`),统一继承 `NotifyAction`,一个任务可多渠道同步发送
18
+ - **配置即代码** — TOML 管理任务、触发器、行为参数;新增任务零代码
19
+ - **自定义业务逻辑** — 用户写普通 Python 函数(`fn(context) -> None`),TOML 以 dotted path 引用;通常用于"定时检查 + 满足条件广播事件"的判断层
20
+ - **链式联动** — `EventBus.publish(event, payload)` 与 `EventTrigger` 配对,形成任务间事件链路
21
+ - **工程细节** — loguru 日志、任务启用/禁用、行为级异常捕获、SIGINT/SIGTERM 优雅关闭
22
+
23
+ ## 安装
24
+
25
+ 需要 [UV](https://docs.astral.sh/uv/)。
26
+
27
+ ```bash
28
+ git clone <this-repo>
29
+ cd anotiflow
30
+ uv sync
31
+ ```
32
+
33
+ ## 快速开始
34
+
35
+ ```bash
36
+ uv run anotiflow --config examples/config.toml
37
+ # 可选:--log-level DEBUG
38
+ ```
39
+
40
+ 默认示例会每 5 秒随机模拟一次"股价检查",高于阈值即广播 `stock.high` 事件,触发飞书 / 钉钉通知。将 [examples/config.toml](examples/config.toml) 里的 `token` / `secret` 替换为真实值就能收到真通知;占位值会发送失败但不会让进程崩溃。
41
+
42
+ ## 核心概念
43
+
44
+ ### Task
45
+
46
+ ```
47
+ Task = name + enabled + [Trigger, ...] + [Action, ...]
48
+ ```
49
+
50
+ 任意一个触发器命中 → 按顺序执行所有行为。任一行为抛异常会被记录日志但不影响后续行为 / 其他任务。
51
+
52
+ ### Trigger(触发器)
53
+
54
+ | 类型 | 作用 | 关键字段 |
55
+ |---|---|---|
56
+ | `interval` | 定时,基于 `schedule` | `unit` 必填;`every` / `to` / `at` / `until` 可选 |
57
+ | `event` | 订阅 EventBus 事件 | `event` 事件名 |
58
+
59
+ `interval` 的 `unit` 取值覆盖 `schedule` 的全部灵活性:
60
+
61
+ - `seconds` / `second`、`minutes` / `minute`、`hours` / `hour`、`days` / `day`、`weeks` / `week`
62
+ - 星期名:`monday` / `tuesday` / `wednesday` / `thursday` / `friday` / `saturday` / `sunday`
63
+
64
+ 常见组合:
65
+
66
+ | 需求 | TOML |
67
+ |---|---|
68
+ | 每 5 秒 | `unit="seconds", every=5` |
69
+ | 每 5~10 秒随机 | `unit="seconds", every=5, to=10` |
70
+ | 每分钟的第 23 秒 | `unit="minute", at=":23"` |
71
+ | 每天 09:30 | `unit="day", at="09:30"` |
72
+ | 每周一 13:15 | `unit="monday", at="13:15"` |
73
+ | 每小时执行直到 18:30 | `unit="hour", until="18:30"` |
74
+
75
+ ### Action(行为)
76
+
77
+ 抽象层次:`Action`(顶层)→ `NotifyAction`(通知基类)→ `FeishuNotify` / `DingtalkNotify` / ...
78
+
79
+ 内置类型:
80
+
81
+ | type | 说明 | 关键字段 |
82
+ |---|---|---|
83
+ | `feishu` | 飞书群机器人 | `token`, `secret`, `message_template` |
84
+ | `dingtalk` | 钉钉群机器人 | `token`, `secret`, `title`, `message_template` |
85
+ | `publish_event` | 向 EventBus 广播事件(用于串联任务) | `event`, `[tasks.actions.payload]` |
86
+ | `custom` | 调用用户自定义函数 | `path = "module.func"` |
87
+
88
+ ### Context(行为执行时的上下文)
89
+
90
+ 每次任务触发,框架会组装 context 字典传给每个 action。在 `message_template` / `publish_event.payload` 里用 `{xxx}` 引用:
91
+
92
+ | 字段 | 含义 |
93
+ |---|---|
94
+ | `{task_name}` | 任务名 |
95
+ | `{trigger_name}` | 触发来源描述,如 `interval(every 5 seconds)` / `event(stock.high)` |
96
+ | `{trigger_type}` | `interval` / `event` |
97
+ | `{fired_at}` | 触发时刻 `YYYY-MM-DD HH:MM:SS` |
98
+ | `{trigger_payload}` | 完整业务载荷 dict |
99
+ | `{trigger_payload[symbol]}` | 载荷中某字段 |
100
+
101
+ ### EventBus
102
+
103
+ 进程内线程安全的发布/订阅单例:
104
+
105
+ ```python
106
+ from anotiflow.core.event_bus import bus
107
+ bus.publish("stock.high", {"symbol": "AAPL", "price": 107.6})
108
+ ```
109
+
110
+ ## 配置示例
111
+
112
+ ```toml
113
+ # 任务 1:每 5 秒跑一次自定义业务检查
114
+ [[tasks]]
115
+ name = "check_stock_price"
116
+ enabled = true
117
+
118
+ [[tasks.triggers]]
119
+ type = "interval"
120
+ every = 5
121
+ unit = "seconds"
122
+
123
+ [[tasks.actions]]
124
+ type = "custom"
125
+ path = "examples.user_actions.check_stock_price"
126
+
127
+ # 任务 2:多触发器(定时 + 两个事件),按序发飞书 + 钉钉
128
+ [[tasks]]
129
+ name = "notify_with_multi_triggers"
130
+ enabled = true
131
+
132
+ [[tasks.triggers]]
133
+ type = "interval"
134
+ every = 12
135
+ unit = "seconds"
136
+
137
+ [[tasks.triggers]]
138
+ type = "event"
139
+ event = "stock.high"
140
+
141
+ [[tasks.triggers]]
142
+ type = "event"
143
+ event = "manual.fire"
144
+
145
+ [[tasks.actions]]
146
+ type = "feishu"
147
+ token = "xxxx"
148
+ secret = "yyyy"
149
+ message_template = """[{task_name}] 触发={trigger_name} @ {fired_at}
150
+ 载荷={trigger_payload}"""
151
+
152
+ [[tasks.actions]]
153
+ type = "dingtalk"
154
+ token = "xxxx"
155
+ secret = "yyyy"
156
+ title = "anotiflow 通知"
157
+ message_template = "{trigger_payload[symbol]} = {trigger_payload[price]}"
158
+
159
+ # 任务 3:每天 09:30 广播事件(用 publish_event 串联)
160
+ [[tasks]]
161
+ name = "daily_morning_fire"
162
+ enabled = true
163
+
164
+ [[tasks.triggers]]
165
+ type = "interval"
166
+ unit = "day"
167
+ at = "09:30"
168
+
169
+ [[tasks.actions]]
170
+ type = "publish_event"
171
+ event = "manual.fire"
172
+
173
+ [tasks.actions.payload]
174
+ reason = "morning_cron@{fired_at}"
175
+ ```
176
+
177
+ 完整示例见 [examples/config.toml](examples/config.toml)。
178
+
179
+ ## 自定义业务行为
180
+
181
+ 写一个普通 Python 函数,签名 `fn(context: dict) -> None`:
182
+
183
+ ```python
184
+ # examples/user_actions.py
185
+ from anotiflow.core.event_bus import bus
186
+ from loguru import logger
187
+
188
+ def check_stock_price(context: dict) -> None:
189
+ price = fetch_price("AAPL")
190
+ logger.info(f"[{context['task_name']}] price={price} at {context['fired_at']}")
191
+ if price > 100:
192
+ bus.publish("stock.high", {"symbol": "AAPL", "price": price})
193
+ ```
194
+
195
+ TOML 引用:
196
+
197
+ ```toml
198
+ [[tasks.actions]]
199
+ type = "custom"
200
+ path = "examples.user_actions.check_stock_price"
201
+ ```
202
+
203
+ 模块解析路径:框架会把 CWD 与 config 文件所在目录都加入 `sys.path`,所以 `examples.user_actions.check_stock_price` 在项目根目录执行 `uv run anotiflow` 时能被正确加载。
204
+
205
+ ## 扩展新渠道 / 新触发器
206
+
207
+ 以新增企业微信通知为例:
208
+
209
+ ```python
210
+ # src/anotiflow/actions/wecom.py
211
+ from ipush import WeCom
212
+ from anotiflow.actions.notify_base import NotifyAction
213
+ from anotiflow.core.registry import register_action
214
+
215
+ @register_action("wecom")
216
+ class WeComNotify(NotifyAction):
217
+ def __init__(self, token: str, message_template: str = "") -> None:
218
+ super().__init__(message_template=message_template)
219
+ self.name = "wecom"
220
+ self._client = WeCom(token=token)
221
+
222
+ def _send(self, message: str) -> None:
223
+ self._client.send(message)
224
+ ```
225
+
226
+ 在 [src/anotiflow/actions/__init__.py](src/anotiflow/actions/__init__.py) 里 `import` 该模块触发 `@register_action` 装饰器副作用,之后 TOML 里 `type = "wecom"` 即可使用。
227
+
228
+ 新增触发器同理:继承 `Trigger` + `@register_trigger("your_type")`,在 [src/anotiflow/triggers/__init__.py](src/anotiflow/triggers/__init__.py) 里 `import`。
229
+
230
+ ## 项目结构
231
+
232
+ ```
233
+ anotiflow/
234
+ ├── pyproject.toml
235
+ ├── src/anotiflow/
236
+ │ ├── cli.py # uv run anotiflow 入口
237
+ │ ├── __main__.py # python -m anotiflow
238
+ │ ├── task.py # Task 数据类
239
+ │ ├── logging_setup.py # loguru 初始化
240
+ │ ├── core/
241
+ │ │ ├── event_bus.py # EventBus 单例
242
+ │ │ ├── registry.py # 类型注册表
243
+ │ │ ├── loader.py # TOML → Task[]
244
+ │ │ └── scheduler.py # 主循环 + 优雅关闭
245
+ │ ├── triggers/
246
+ │ │ ├── base.py # Trigger ABC
247
+ │ │ ├── interval.py # 定时触发(schedule)
248
+ │ │ └── event.py # 事件触发(EventBus 订阅)
249
+ │ └── actions/
250
+ │ ├── base.py # Action ABC + CallableAction
251
+ │ ├── notify_base.py # NotifyAction
252
+ │ ├── feishu.py # 飞书通知
253
+ │ ├── dingtalk.py # 钉钉通知
254
+ │ └── publish_event.py # 广播事件行为
255
+ └── examples/
256
+ ├── config.toml # 配置示例(含 4 个任务)
257
+ └── user_actions.py # 自定义业务行为示例
258
+ ```
259
+
260
+ ## 运行
261
+
262
+ ```bash
263
+ uv run anotiflow --config examples/config.toml
264
+ uv run anotiflow --config /path/to/your.toml --log-level DEBUG
265
+ # 等价写法
266
+ uv run python -m anotiflow --config examples/config.toml
267
+ ```
268
+
269
+ `Ctrl-C` 或 `SIGTERM` 会触发 Scheduler 解绑所有触发器并优雅退出。
270
+
271
+ ## 依赖
272
+
273
+ - [schedule](https://pypi.org/project/schedule/) — 定时调度
274
+ - [ipush](https://pypi.org/project/ipush/) — 飞书 / 钉钉等推送渠道统一封装
275
+ - [loguru](https://pypi.org/project/loguru/) — 日志
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "anotiflow"
3
+ version = "0.1.0"
4
+ description = "可扩展的任务调度通知框架:触发器 + 行为插件 + 事件总线,TOML 配置驱动"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "徐侨", email = "xu.qiao@kotei.com.cn" }
8
+ ]
9
+ requires-python = ">=3.6"
10
+ dependencies = [
11
+ "schedule>=1.2",
12
+ "ipush>=0.4",
13
+ "loguru>=0.7",
14
+ ]
15
+
16
+ [project.scripts]
17
+ anotiflow = "anotiflow.cli:main"
18
+
19
+ [build-system]
20
+ requires = ["uv_build>=0.11.8,<0.12.0"]
21
+ build-backend = "uv_build"
@@ -0,0 +1,3 @@
1
+ """anotiflow - 可扩展任务调度通知框架"""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from anotiflow.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,16 @@
1
+ """行为模块:顶层 Action 基类 + 通知基类 + 具体实现"""
2
+
3
+ from anotiflow.actions.base import Action, CallableAction
4
+ from anotiflow.actions.notify_base import NotifyAction
5
+ from anotiflow.actions.feishu import FeishuNotify
6
+ from anotiflow.actions.dingtalk import DingtalkNotify
7
+ from anotiflow.actions.publish_event import PublishEventAction
8
+
9
+ __all__ = [
10
+ "Action",
11
+ "CallableAction",
12
+ "NotifyAction",
13
+ "FeishuNotify",
14
+ "DingtalkNotify",
15
+ "PublishEventAction",
16
+ ]
@@ -0,0 +1,38 @@
1
+ """Action 顶层抽象基类 + 将用户自定义函数包装为 Action 的适配器。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Callable
7
+
8
+
9
+ class Action(ABC):
10
+ """所有行为的顶层基类。子类必须实现 execute。"""
11
+
12
+ name: str = ""
13
+
14
+ @abstractmethod
15
+ def execute(self, context: dict) -> None:
16
+ """执行行为。
17
+
18
+ context 至少包含:
19
+ - task_name: 所属任务名
20
+ - trigger_payload: 触发器传入的载荷(事件 payload 或定时任务的空 dict)
21
+ """
22
+
23
+ def __repr__(self) -> str:
24
+ return f"<{self.__class__.__name__} name={self.name!r}>"
25
+
26
+
27
+ class CallableAction(Action):
28
+ """把用户自定义函数(dotted-path 加载)包装成 Action。
29
+
30
+ 函数签名约定: fn(context: dict) -> None
31
+ """
32
+
33
+ def __init__(self, fn: Callable[[dict], None], name: str = "") -> None:
34
+ self._fn = fn
35
+ self.name = name or getattr(fn, "__qualname__", repr(fn))
36
+
37
+ def execute(self, context: dict) -> None:
38
+ self._fn(context)
@@ -0,0 +1,28 @@
1
+ """钉钉机器人通知行为,基于 ipush.Dingtalk。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ipush import Dingtalk
6
+ from loguru import logger
7
+
8
+ from anotiflow.actions.notify_base import NotifyAction
9
+ from anotiflow.core.registry import register_action
10
+
11
+
12
+ @register_action("dingtalk")
13
+ class DingtalkNotify(NotifyAction):
14
+ def __init__(
15
+ self,
16
+ token: str,
17
+ secret: str = "",
18
+ message_template: str = "",
19
+ title: str = "",
20
+ ) -> None:
21
+ super().__init__(message_template=message_template)
22
+ self.name = "dingtalk"
23
+ self.title = title
24
+ self._client = Dingtalk(token=token, secret=secret)
25
+
26
+ def _send(self, message: str) -> None:
27
+ logger.info(f"[dingtalk] sending: {message}")
28
+ self._client.send(message, title=self.title)