Metadata-Version: 2.4
Name: leap-feishu-webhook-notify
Version: 0.3.1
Summary: Async Lark/Feishu custom bot webhook notifications with CardKit v2 templates
Project-URL: Homepage, https://github.com/Llugaes/leap-feishu-webhook-notify
Project-URL: Repository, https://github.com/Llugaes/leap-feishu-webhook-notify
Project-URL: Issues, https://github.com/Llugaes/leap-feishu-webhook-notify/issues
Author-email: Llugaes <249094896@qq.com>
License: MIT License
        
        Copyright (c) 2026 Llugaes
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: cardkit,feishu,lark,notification,webhook
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: loguru>=0.7
Provides-Extra: dev
Requires-Dist: pyright; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# leap-feishu-webhook-notify

Async Python notifications for Lark/Feishu custom bot webhooks.

The PyPI distribution is `leap-feishu-webhook-notify`. The import package remains
`feishu_notify`.

## Features

- Async `httpx` transport with reusable connections.
- Lark/Feishu custom bot HMAC-SHA256 signing.
- CardKit v2 interactive cards.
- Built-in templates for lifecycle events, alerts, heartbeats, pings, and period reports.
- Payload splitting for large cards.
- Single-target and multi-target webhook fan-out.
- Runtime reconfiguration and reset support.
- Failure-safe sends: network and API failures are logged and returned as `False`, not raised.

## Installation

```bash
pip install leap-feishu-webhook-notify
```

With uv:

```bash
uv add leap-feishu-webhook-notify
```

## Quick Start

```python
import asyncio
import os

from feishu_notify import FeishuBot, ServiceContext


async def main() -> None:
    bot = FeishuBot(
        webhook_url=os.environ["FEISHU_WEBHOOK_URL"],
        secret=os.environ.get("FEISHU_WEBHOOK_SECRET", ""),
        context=ServiceContext(service="orders-api", env="prod"),
    )
    try:
        ok = await bot.send_heartbeat(message="notification channel is ready")
        print(ok)
    finally:
        await bot.aclose()


asyncio.run(main())
```

## Alert Cards

```python
await bot.send_warning(
    title="Payment provider timeout",
    message="The provider did not respond after 3 retries.",
    error_message="TimeoutError: request timed out",
    trace_id="trace-123",
    details={"provider": "example-pay", "retry": 3},
)
```

Set optional URLs on `ServiceContext` to render action buttons:

```python
context = ServiceContext(
    service="orders-api",
    env="prod",
    host="worker-1",
    log_url_template="https://logs.example.com/trace/{trace_id}",
    commit_url_template="https://git.example.com/repo/commit/{commit}",
    dashboard_url="https://metrics.example.com/d/orders",
)
```

## Multi-target Webhooks

Use `webhook_urls` when the same message should be sent to several groups.

```python
bot = FeishuBot(
    webhook_urls=[
        os.environ["FEISHU_PRIMARY_WEBHOOK"],
        os.environ["FEISHU_ONCALL_WEBHOOK"],
    ],
    secrets=[
        os.environ.get("FEISHU_PRIMARY_SECRET", ""),
        os.environ.get("FEISHU_ONCALL_SECRET", ""),
    ],
    context=ServiceContext(service="orders-api", env="prod"),
)

result = await bot.send_error(
    title="Database unavailable",
    message="All replicas failed the health check.",
    error_message="ConnectionError: no healthy upstream",
)

oks = result if isinstance(result, list) else [result]
failed_count = oks.count(False)
```

Return values:

| Mode | Return type |
|---|---|
| Single webhook | `bool` |
| Multiple webhooks | `list[bool]`, in the same order as `webhook_urls` |

## Runtime Reconfiguration

`reconfigure()` updates the active webhook(s) in memory. It does not persist
anything and does not read environment variables.

```python
bot.reconfigure(webhook_url=new_url, secret=new_secret)

bot.reconfigure(
    webhook_urls=[primary_url, oncall_url],
    secrets=[primary_secret, oncall_secret],
)
```

`reset()` restores the webhook(s) and secret(s) passed to `__init__`.

```python
if override_url:
    bot.reconfigure(webhook_url=override_url, secret=override_secret)
else:
    bot.reset()
```

## BotManager

Use `BotManager` when different messages should go to different bots.

```python
from feishu_notify import BotManager

manager = BotManager()
manager.register(ops_bot)
manager.register(alerts_bot)

await manager.broadcast_via("send_startup")
```

`BotManager.broadcast*()` folds a multi-target bot result with `all(...)`, so
each bot name maps to one success boolean.

## Period Reports

Any object implementing the `PeriodSummary` protocol can be sent as a report.

```python
from dataclasses import dataclass, field

from feishu_notify import FailureItem, StatGroup


@dataclass
class DailySummary:
    period_label: str = "2026-06-09"
    period_type: str = "daily"
    total_counts: dict[str, int] = field(default_factory=lambda: {"ok": 120, "failed": 2})
    groups: list[StatGroup] = field(default_factory=list)
    failures: list[FailureItem] = field(default_factory=list)
    prev_total_counts: dict[str, int] | None = None


await bot.send_period_report(summary=DailySummary())
```

## Low-level Card API

Use `feishu_notify.primitives` to build custom CardKit v2 payloads.

```python
from feishu_notify import primitives

payload = primitives.card(
    "Custom report",
    "blue",
    [
        primitives.md("**Status**: OK"),
        primitives.hr(),
        primitives.action_button("Open dashboard", "https://metrics.example.com"),
    ],
)

await bot.send(payload)
```

## Failure Behavior

All send methods are designed to be safe in application code:

| Failure | Behavior |
|---|---|
| Empty webhook URL | Return `False` |
| Network error or timeout | Log warning, invoke `on_failure`, return `False` |
| HTTP 4xx/5xx | Log status and body preview, return `False` |
| Lark/Feishu `code != 0` | Log code and message, return `False` |
| Invalid JSON response | Log warning, return `False` |
| Unexpected exception | Log exception, invoke `on_failure`, return `False` |

Avoid putting webhook URLs, secrets, tokens, passwords, or other sensitive data
inside card `message`, `details`, or logs.

## Development

```bash
uv sync --extra dev
uv run ruff check src tests
uv run pyright src
uv run pytest tests/unit/ --cov=feishu_notify --cov-report=term --cov-fail-under=85
uv run python -m compileall -q src/
```

Real webhook smoke tests are skipped unless these environment variables are set:

- `FEISHU_TEST_WEBHOOK`
- `FEISHU_TEST_SECRET`
- `FEISHU_TEST_WEBHOOK_LIST`

Run them explicitly:

```bash
uv run pytest tests/integration/ -v
```

## License

MIT
