leap-feishu-webhook-notify 0.3.1__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.
Files changed (42) hide show
  1. leap_feishu_webhook_notify-0.3.1/.gitattributes +5 -0
  2. leap_feishu_webhook_notify-0.3.1/.github/workflows/ci.yml +21 -0
  3. leap_feishu_webhook_notify-0.3.1/.github/workflows/publish.yml +22 -0
  4. leap_feishu_webhook_notify-0.3.1/.gitignore +11 -0
  5. leap_feishu_webhook_notify-0.3.1/.python-version +1 -0
  6. leap_feishu_webhook_notify-0.3.1/CHANGELOG.md +60 -0
  7. leap_feishu_webhook_notify-0.3.1/LICENSE +21 -0
  8. leap_feishu_webhook_notify-0.3.1/PKG-INFO +293 -0
  9. leap_feishu_webhook_notify-0.3.1/README.md +238 -0
  10. leap_feishu_webhook_notify-0.3.1/pyproject.toml +70 -0
  11. leap_feishu_webhook_notify-0.3.1/scripts/check.sh +8 -0
  12. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/__init__.py +40 -0
  13. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/_version.py +1 -0
  14. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/bot.py +394 -0
  15. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/context.py +64 -0
  16. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/manager.py +84 -0
  17. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/primitives.py +156 -0
  18. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/py.typed +0 -0
  19. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/schemas.py +66 -0
  20. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/splitter.py +81 -0
  21. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/templates/__init__.py +0 -0
  22. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/templates/alert.py +152 -0
  23. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/templates/lifecycle.py +127 -0
  24. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/templates/report.py +146 -0
  25. leap_feishu_webhook_notify-0.3.1/src/feishu_notify/transport.py +56 -0
  26. leap_feishu_webhook_notify-0.3.1/tests/__init__.py +0 -0
  27. leap_feishu_webhook_notify-0.3.1/tests/integration/__init__.py +0 -0
  28. leap_feishu_webhook_notify-0.3.1/tests/integration/test_real_webhook.py +185 -0
  29. leap_feishu_webhook_notify-0.3.1/tests/unit/__init__.py +0 -0
  30. leap_feishu_webhook_notify-0.3.1/tests/unit/test_bot.py +667 -0
  31. leap_feishu_webhook_notify-0.3.1/tests/unit/test_bot_templates.py +124 -0
  32. leap_feishu_webhook_notify-0.3.1/tests/unit/test_context.py +71 -0
  33. leap_feishu_webhook_notify-0.3.1/tests/unit/test_manager.py +103 -0
  34. leap_feishu_webhook_notify-0.3.1/tests/unit/test_primitives.py +123 -0
  35. leap_feishu_webhook_notify-0.3.1/tests/unit/test_public_api.py +53 -0
  36. leap_feishu_webhook_notify-0.3.1/tests/unit/test_schemas.py +56 -0
  37. leap_feishu_webhook_notify-0.3.1/tests/unit/test_signing.py +58 -0
  38. leap_feishu_webhook_notify-0.3.1/tests/unit/test_splitter.py +97 -0
  39. leap_feishu_webhook_notify-0.3.1/tests/unit/test_templates_alert.py +188 -0
  40. leap_feishu_webhook_notify-0.3.1/tests/unit/test_templates_lifecycle.py +89 -0
  41. leap_feishu_webhook_notify-0.3.1/tests/unit/test_templates_report.py +126 -0
  42. leap_feishu_webhook_notify-0.3.1/uv.lock +439 -0
@@ -0,0 +1,5 @@
1
+ docs/archive/** export-ignore
2
+ .coverage export-ignore
3
+ .pytest_cache/** export-ignore
4
+ .ruff_cache/** export-ignore
5
+ .venv/** export-ignore
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.11"
16
+ - uses: astral-sh/setup-uv@v5
17
+ - run: uv sync --extra dev
18
+ - run: uv run ruff check src tests
19
+ - run: uv run pyright src
20
+ - run: uv run pytest tests/unit/ --cov=feishu_notify --cov-report=term --cov-fail-under=85
21
+ - run: uv run python -m compileall -q src/
@@ -0,0 +1,22 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: read
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v5
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+ - name: Build
20
+ run: uv build
21
+ - name: Publish
22
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .coverage
5
+ htmlcov/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .venv/
10
+ .ruff_cache/
11
+ .pyright/
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ This project follows Keep a Changelog and Semantic Versioning.
4
+
5
+ ## [0.3.1] - 2026-05-28
6
+
7
+ ### Fixed
8
+
9
+ - Replaced CardKit v2 `note` elements with markdown-based footer text after the
10
+ Lark/Feishu card API stopped accepting the `note` tag.
11
+ - Replaced the deprecated `action` wrapper with top-level `button` elements.
12
+ Existing calls to `primitives.action_button(...)` keep the same Python API.
13
+
14
+ ### Migration
15
+
16
+ - Upgrade from `0.3.0` to `0.3.1` with no application code changes.
17
+
18
+ ## [0.3.0] - 2026-05-27
19
+
20
+ ### Added
21
+
22
+ - Multi-target webhook support with `webhook_urls=[...]` and `secrets=[...]`.
23
+ - `FeishuBot.reset()` to restore the initial webhook(s) and secret(s).
24
+ - Stable public properties: `bot.enabled`, `bot.name`, and `bot.context`.
25
+
26
+ ### Changed
27
+
28
+ - `bot.enabled` now returns `True` when any configured webhook URL is non-empty.
29
+ - `send()` and template send helpers return `list[bool]` in multi-target mode.
30
+ Single-webhook mode still returns `bool`.
31
+
32
+ ### Backward Compatibility
33
+
34
+ - Existing single-webhook users can upgrade from `0.2.x` without code changes.
35
+
36
+ ## [0.2.0] - 2026-05-21
37
+
38
+ ### Added
39
+
40
+ - `FeishuBot.reconfigure(...)` for runtime webhook and secret updates.
41
+
42
+ ### Documentation
43
+
44
+ - Added dynamic configuration examples for in-memory, persisted, and multi-node
45
+ configuration patterns.
46
+
47
+ ## [0.1.0] - 2026-05-21
48
+
49
+ ### Added
50
+
51
+ - `FeishuBot` with low-level `send(payload)` support.
52
+ - HMAC-SHA256 signing for Lark/Feishu custom bot webhooks.
53
+ - CardKit v2 primitives.
54
+ - Lifecycle templates: deploy, startup, shutdown, ping, and heartbeat.
55
+ - Alert templates: warning, error, and critical.
56
+ - Period report template support via the `PeriodSummary` protocol.
57
+ - `BotManager` for registering and broadcasting to multiple bots.
58
+ - Payload splitting for oversized interactive cards.
59
+ - Safe failure behavior: send methods log failures and return booleans instead
60
+ of raising.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Llugaes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.4
2
+ Name: leap-feishu-webhook-notify
3
+ Version: 0.3.1
4
+ Summary: Async Lark/Feishu custom bot webhook notifications with CardKit v2 templates
5
+ Project-URL: Homepage, https://github.com/Llugaes/leap-feishu-webhook-notify
6
+ Project-URL: Repository, https://github.com/Llugaes/leap-feishu-webhook-notify
7
+ Project-URL: Issues, https://github.com/Llugaes/leap-feishu-webhook-notify/issues
8
+ Author-email: Llugaes <249094896@qq.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Llugaes
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: cardkit,feishu,lark,notification,webhook
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3 :: Only
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Communications
42
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
43
+ Classifier: Typing :: Typed
44
+ Requires-Python: >=3.11
45
+ Requires-Dist: httpx>=0.27
46
+ Requires-Dist: loguru>=0.7
47
+ Provides-Extra: dev
48
+ Requires-Dist: pyright; extra == 'dev'
49
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
50
+ Requires-Dist: pytest-cov; extra == 'dev'
51
+ Requires-Dist: pytest>=8; extra == 'dev'
52
+ Requires-Dist: respx>=0.21; extra == 'dev'
53
+ Requires-Dist: ruff; extra == 'dev'
54
+ Description-Content-Type: text/markdown
55
+
56
+ # leap-feishu-webhook-notify
57
+
58
+ Async Python notifications for Lark/Feishu custom bot webhooks.
59
+
60
+ The PyPI distribution is `leap-feishu-webhook-notify`. The import package remains
61
+ `feishu_notify`.
62
+
63
+ ## Features
64
+
65
+ - Async `httpx` transport with reusable connections.
66
+ - Lark/Feishu custom bot HMAC-SHA256 signing.
67
+ - CardKit v2 interactive cards.
68
+ - Built-in templates for lifecycle events, alerts, heartbeats, pings, and period reports.
69
+ - Payload splitting for large cards.
70
+ - Single-target and multi-target webhook fan-out.
71
+ - Runtime reconfiguration and reset support.
72
+ - Failure-safe sends: network and API failures are logged and returned as `False`, not raised.
73
+
74
+ ## Installation
75
+
76
+ ```bash
77
+ pip install leap-feishu-webhook-notify
78
+ ```
79
+
80
+ With uv:
81
+
82
+ ```bash
83
+ uv add leap-feishu-webhook-notify
84
+ ```
85
+
86
+ ## Quick Start
87
+
88
+ ```python
89
+ import asyncio
90
+ import os
91
+
92
+ from feishu_notify import FeishuBot, ServiceContext
93
+
94
+
95
+ async def main() -> None:
96
+ bot = FeishuBot(
97
+ webhook_url=os.environ["FEISHU_WEBHOOK_URL"],
98
+ secret=os.environ.get("FEISHU_WEBHOOK_SECRET", ""),
99
+ context=ServiceContext(service="orders-api", env="prod"),
100
+ )
101
+ try:
102
+ ok = await bot.send_heartbeat(message="notification channel is ready")
103
+ print(ok)
104
+ finally:
105
+ await bot.aclose()
106
+
107
+
108
+ asyncio.run(main())
109
+ ```
110
+
111
+ ## Alert Cards
112
+
113
+ ```python
114
+ await bot.send_warning(
115
+ title="Payment provider timeout",
116
+ message="The provider did not respond after 3 retries.",
117
+ error_message="TimeoutError: request timed out",
118
+ trace_id="trace-123",
119
+ details={"provider": "example-pay", "retry": 3},
120
+ )
121
+ ```
122
+
123
+ Set optional URLs on `ServiceContext` to render action buttons:
124
+
125
+ ```python
126
+ context = ServiceContext(
127
+ service="orders-api",
128
+ env="prod",
129
+ host="worker-1",
130
+ log_url_template="https://logs.example.com/trace/{trace_id}",
131
+ commit_url_template="https://git.example.com/repo/commit/{commit}",
132
+ dashboard_url="https://metrics.example.com/d/orders",
133
+ )
134
+ ```
135
+
136
+ ## Multi-target Webhooks
137
+
138
+ Use `webhook_urls` when the same message should be sent to several groups.
139
+
140
+ ```python
141
+ bot = FeishuBot(
142
+ webhook_urls=[
143
+ os.environ["FEISHU_PRIMARY_WEBHOOK"],
144
+ os.environ["FEISHU_ONCALL_WEBHOOK"],
145
+ ],
146
+ secrets=[
147
+ os.environ.get("FEISHU_PRIMARY_SECRET", ""),
148
+ os.environ.get("FEISHU_ONCALL_SECRET", ""),
149
+ ],
150
+ context=ServiceContext(service="orders-api", env="prod"),
151
+ )
152
+
153
+ result = await bot.send_error(
154
+ title="Database unavailable",
155
+ message="All replicas failed the health check.",
156
+ error_message="ConnectionError: no healthy upstream",
157
+ )
158
+
159
+ oks = result if isinstance(result, list) else [result]
160
+ failed_count = oks.count(False)
161
+ ```
162
+
163
+ Return values:
164
+
165
+ | Mode | Return type |
166
+ |---|---|
167
+ | Single webhook | `bool` |
168
+ | Multiple webhooks | `list[bool]`, in the same order as `webhook_urls` |
169
+
170
+ ## Runtime Reconfiguration
171
+
172
+ `reconfigure()` updates the active webhook(s) in memory. It does not persist
173
+ anything and does not read environment variables.
174
+
175
+ ```python
176
+ bot.reconfigure(webhook_url=new_url, secret=new_secret)
177
+
178
+ bot.reconfigure(
179
+ webhook_urls=[primary_url, oncall_url],
180
+ secrets=[primary_secret, oncall_secret],
181
+ )
182
+ ```
183
+
184
+ `reset()` restores the webhook(s) and secret(s) passed to `__init__`.
185
+
186
+ ```python
187
+ if override_url:
188
+ bot.reconfigure(webhook_url=override_url, secret=override_secret)
189
+ else:
190
+ bot.reset()
191
+ ```
192
+
193
+ ## BotManager
194
+
195
+ Use `BotManager` when different messages should go to different bots.
196
+
197
+ ```python
198
+ from feishu_notify import BotManager
199
+
200
+ manager = BotManager()
201
+ manager.register(ops_bot)
202
+ manager.register(alerts_bot)
203
+
204
+ await manager.broadcast_via("send_startup")
205
+ ```
206
+
207
+ `BotManager.broadcast*()` folds a multi-target bot result with `all(...)`, so
208
+ each bot name maps to one success boolean.
209
+
210
+ ## Period Reports
211
+
212
+ Any object implementing the `PeriodSummary` protocol can be sent as a report.
213
+
214
+ ```python
215
+ from dataclasses import dataclass, field
216
+
217
+ from feishu_notify import FailureItem, StatGroup
218
+
219
+
220
+ @dataclass
221
+ class DailySummary:
222
+ period_label: str = "2026-06-09"
223
+ period_type: str = "daily"
224
+ total_counts: dict[str, int] = field(default_factory=lambda: {"ok": 120, "failed": 2})
225
+ groups: list[StatGroup] = field(default_factory=list)
226
+ failures: list[FailureItem] = field(default_factory=list)
227
+ prev_total_counts: dict[str, int] | None = None
228
+
229
+
230
+ await bot.send_period_report(summary=DailySummary())
231
+ ```
232
+
233
+ ## Low-level Card API
234
+
235
+ Use `feishu_notify.primitives` to build custom CardKit v2 payloads.
236
+
237
+ ```python
238
+ from feishu_notify import primitives
239
+
240
+ payload = primitives.card(
241
+ "Custom report",
242
+ "blue",
243
+ [
244
+ primitives.md("**Status**: OK"),
245
+ primitives.hr(),
246
+ primitives.action_button("Open dashboard", "https://metrics.example.com"),
247
+ ],
248
+ )
249
+
250
+ await bot.send(payload)
251
+ ```
252
+
253
+ ## Failure Behavior
254
+
255
+ All send methods are designed to be safe in application code:
256
+
257
+ | Failure | Behavior |
258
+ |---|---|
259
+ | Empty webhook URL | Return `False` |
260
+ | Network error or timeout | Log warning, invoke `on_failure`, return `False` |
261
+ | HTTP 4xx/5xx | Log status and body preview, return `False` |
262
+ | Lark/Feishu `code != 0` | Log code and message, return `False` |
263
+ | Invalid JSON response | Log warning, return `False` |
264
+ | Unexpected exception | Log exception, invoke `on_failure`, return `False` |
265
+
266
+ Avoid putting webhook URLs, secrets, tokens, passwords, or other sensitive data
267
+ inside card `message`, `details`, or logs.
268
+
269
+ ## Development
270
+
271
+ ```bash
272
+ uv sync --extra dev
273
+ uv run ruff check src tests
274
+ uv run pyright src
275
+ uv run pytest tests/unit/ --cov=feishu_notify --cov-report=term --cov-fail-under=85
276
+ uv run python -m compileall -q src/
277
+ ```
278
+
279
+ Real webhook smoke tests are skipped unless these environment variables are set:
280
+
281
+ - `FEISHU_TEST_WEBHOOK`
282
+ - `FEISHU_TEST_SECRET`
283
+ - `FEISHU_TEST_WEBHOOK_LIST`
284
+
285
+ Run them explicitly:
286
+
287
+ ```bash
288
+ uv run pytest tests/integration/ -v
289
+ ```
290
+
291
+ ## License
292
+
293
+ MIT
@@ -0,0 +1,238 @@
1
+ # leap-feishu-webhook-notify
2
+
3
+ Async Python notifications for Lark/Feishu custom bot webhooks.
4
+
5
+ The PyPI distribution is `leap-feishu-webhook-notify`. The import package remains
6
+ `feishu_notify`.
7
+
8
+ ## Features
9
+
10
+ - Async `httpx` transport with reusable connections.
11
+ - Lark/Feishu custom bot HMAC-SHA256 signing.
12
+ - CardKit v2 interactive cards.
13
+ - Built-in templates for lifecycle events, alerts, heartbeats, pings, and period reports.
14
+ - Payload splitting for large cards.
15
+ - Single-target and multi-target webhook fan-out.
16
+ - Runtime reconfiguration and reset support.
17
+ - Failure-safe sends: network and API failures are logged and returned as `False`, not raised.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install leap-feishu-webhook-notify
23
+ ```
24
+
25
+ With uv:
26
+
27
+ ```bash
28
+ uv add leap-feishu-webhook-notify
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ import asyncio
35
+ import os
36
+
37
+ from feishu_notify import FeishuBot, ServiceContext
38
+
39
+
40
+ async def main() -> None:
41
+ bot = FeishuBot(
42
+ webhook_url=os.environ["FEISHU_WEBHOOK_URL"],
43
+ secret=os.environ.get("FEISHU_WEBHOOK_SECRET", ""),
44
+ context=ServiceContext(service="orders-api", env="prod"),
45
+ )
46
+ try:
47
+ ok = await bot.send_heartbeat(message="notification channel is ready")
48
+ print(ok)
49
+ finally:
50
+ await bot.aclose()
51
+
52
+
53
+ asyncio.run(main())
54
+ ```
55
+
56
+ ## Alert Cards
57
+
58
+ ```python
59
+ await bot.send_warning(
60
+ title="Payment provider timeout",
61
+ message="The provider did not respond after 3 retries.",
62
+ error_message="TimeoutError: request timed out",
63
+ trace_id="trace-123",
64
+ details={"provider": "example-pay", "retry": 3},
65
+ )
66
+ ```
67
+
68
+ Set optional URLs on `ServiceContext` to render action buttons:
69
+
70
+ ```python
71
+ context = ServiceContext(
72
+ service="orders-api",
73
+ env="prod",
74
+ host="worker-1",
75
+ log_url_template="https://logs.example.com/trace/{trace_id}",
76
+ commit_url_template="https://git.example.com/repo/commit/{commit}",
77
+ dashboard_url="https://metrics.example.com/d/orders",
78
+ )
79
+ ```
80
+
81
+ ## Multi-target Webhooks
82
+
83
+ Use `webhook_urls` when the same message should be sent to several groups.
84
+
85
+ ```python
86
+ bot = FeishuBot(
87
+ webhook_urls=[
88
+ os.environ["FEISHU_PRIMARY_WEBHOOK"],
89
+ os.environ["FEISHU_ONCALL_WEBHOOK"],
90
+ ],
91
+ secrets=[
92
+ os.environ.get("FEISHU_PRIMARY_SECRET", ""),
93
+ os.environ.get("FEISHU_ONCALL_SECRET", ""),
94
+ ],
95
+ context=ServiceContext(service="orders-api", env="prod"),
96
+ )
97
+
98
+ result = await bot.send_error(
99
+ title="Database unavailable",
100
+ message="All replicas failed the health check.",
101
+ error_message="ConnectionError: no healthy upstream",
102
+ )
103
+
104
+ oks = result if isinstance(result, list) else [result]
105
+ failed_count = oks.count(False)
106
+ ```
107
+
108
+ Return values:
109
+
110
+ | Mode | Return type |
111
+ |---|---|
112
+ | Single webhook | `bool` |
113
+ | Multiple webhooks | `list[bool]`, in the same order as `webhook_urls` |
114
+
115
+ ## Runtime Reconfiguration
116
+
117
+ `reconfigure()` updates the active webhook(s) in memory. It does not persist
118
+ anything and does not read environment variables.
119
+
120
+ ```python
121
+ bot.reconfigure(webhook_url=new_url, secret=new_secret)
122
+
123
+ bot.reconfigure(
124
+ webhook_urls=[primary_url, oncall_url],
125
+ secrets=[primary_secret, oncall_secret],
126
+ )
127
+ ```
128
+
129
+ `reset()` restores the webhook(s) and secret(s) passed to `__init__`.
130
+
131
+ ```python
132
+ if override_url:
133
+ bot.reconfigure(webhook_url=override_url, secret=override_secret)
134
+ else:
135
+ bot.reset()
136
+ ```
137
+
138
+ ## BotManager
139
+
140
+ Use `BotManager` when different messages should go to different bots.
141
+
142
+ ```python
143
+ from feishu_notify import BotManager
144
+
145
+ manager = BotManager()
146
+ manager.register(ops_bot)
147
+ manager.register(alerts_bot)
148
+
149
+ await manager.broadcast_via("send_startup")
150
+ ```
151
+
152
+ `BotManager.broadcast*()` folds a multi-target bot result with `all(...)`, so
153
+ each bot name maps to one success boolean.
154
+
155
+ ## Period Reports
156
+
157
+ Any object implementing the `PeriodSummary` protocol can be sent as a report.
158
+
159
+ ```python
160
+ from dataclasses import dataclass, field
161
+
162
+ from feishu_notify import FailureItem, StatGroup
163
+
164
+
165
+ @dataclass
166
+ class DailySummary:
167
+ period_label: str = "2026-06-09"
168
+ period_type: str = "daily"
169
+ total_counts: dict[str, int] = field(default_factory=lambda: {"ok": 120, "failed": 2})
170
+ groups: list[StatGroup] = field(default_factory=list)
171
+ failures: list[FailureItem] = field(default_factory=list)
172
+ prev_total_counts: dict[str, int] | None = None
173
+
174
+
175
+ await bot.send_period_report(summary=DailySummary())
176
+ ```
177
+
178
+ ## Low-level Card API
179
+
180
+ Use `feishu_notify.primitives` to build custom CardKit v2 payloads.
181
+
182
+ ```python
183
+ from feishu_notify import primitives
184
+
185
+ payload = primitives.card(
186
+ "Custom report",
187
+ "blue",
188
+ [
189
+ primitives.md("**Status**: OK"),
190
+ primitives.hr(),
191
+ primitives.action_button("Open dashboard", "https://metrics.example.com"),
192
+ ],
193
+ )
194
+
195
+ await bot.send(payload)
196
+ ```
197
+
198
+ ## Failure Behavior
199
+
200
+ All send methods are designed to be safe in application code:
201
+
202
+ | Failure | Behavior |
203
+ |---|---|
204
+ | Empty webhook URL | Return `False` |
205
+ | Network error or timeout | Log warning, invoke `on_failure`, return `False` |
206
+ | HTTP 4xx/5xx | Log status and body preview, return `False` |
207
+ | Lark/Feishu `code != 0` | Log code and message, return `False` |
208
+ | Invalid JSON response | Log warning, return `False` |
209
+ | Unexpected exception | Log exception, invoke `on_failure`, return `False` |
210
+
211
+ Avoid putting webhook URLs, secrets, tokens, passwords, or other sensitive data
212
+ inside card `message`, `details`, or logs.
213
+
214
+ ## Development
215
+
216
+ ```bash
217
+ uv sync --extra dev
218
+ uv run ruff check src tests
219
+ uv run pyright src
220
+ uv run pytest tests/unit/ --cov=feishu_notify --cov-report=term --cov-fail-under=85
221
+ uv run python -m compileall -q src/
222
+ ```
223
+
224
+ Real webhook smoke tests are skipped unless these environment variables are set:
225
+
226
+ - `FEISHU_TEST_WEBHOOK`
227
+ - `FEISHU_TEST_SECRET`
228
+ - `FEISHU_TEST_WEBHOOK_LIST`
229
+
230
+ Run them explicitly:
231
+
232
+ ```bash
233
+ uv run pytest tests/integration/ -v
234
+ ```
235
+
236
+ ## License
237
+
238
+ MIT