# 🚀 PingCast
[](https://pypi.org/project/pingcast/)
[](https://pypi.org/project/pingcast/)
[](LICENSE)
[](docs/index.md)
`PingCast` is a lightweight Python library that sends status updates from your scripts (e.g., ML training, data pipelines, ETL jobs) to external channels. It starts with **Discord webhooks** and is built to be **plugin‑friendly** for future channels.
* **🔧 Simple API:** `notify = Notifier(discord=WEBHOOK_URL)` → `notify.everything()`
* **✅ Covers the basics:** success, failure (uncaught exceptions), periodic heartbeats, and optional log forwarding
* **🔁 Sync & async:** `Notifier` (sync) and `AsyncNotifier` (async)
* **🔌 Extensible:** clean plugin interface; future discovery via entry points
---
## 🧭 Table of contents
* [Why](#why)
* [Install](#install)
* [Quickstart](#quickstart)
* [Usage](#usage)
  * [Manual sends](#manual-sends)
  * [Track “everything”](#track-everything)
  * [Filtering & heartbeat interval](#filtering--heartbeat-interval)
  * [Async usage](#async-usage)
* [Documentation](#documentation)
* [How it works (under the hood)](#how-it-works-under-the-hood)
* [Plugins & extensibility](#plugins--extensibility)
* [Configuration](#configuration)
* [Supported Python versions](#supported-python-versions)
* [Troubleshooting](#troubleshooting)
* [Roadmap](#roadmap)
* [Contributing](#contributing)
* [Security](#security)
* [Code of Conduct](#code-of-conduct)
* [License](#license)
---
## ❓ Why
Training LLMs and running heavy experiments takes hours. People don’t babysit terminals the whole time, and then return to discover the job failed hours ago. `PingCast` keeps you in the loop:
* ✅ **Completion notices** when the script exits normally
* ❌ **Crash alerts** when an uncaught exception terminates your program (via `sys.excepthook`) ([Python documentation][2])
* ⏱️ **Periodic heartbeats** (default every 15 minutes) so you know it’s still running
* 🧾 **Optional log forwarding** using the standard `logging` handlers (no invasive framework) ([Python documentation][3])
---
## 📦 Install
```bash
pip install pingcast
```
---
## 🚀 Quickstart
1. Create a **Discord webhook** in your channel (Server Settings → Integrations → Webhooks), then copy the URL.
2. Use it in code:
```python
from notifier import Notifier
notify = Notifier(discord="https://discord.com/api/webhooks/XXXXXXXX/XXXXXXXX")
def main():
    # one line to wire everything:
    notify.everything(filter={"level": "INFO"}, interval=900)  # 15 min heartbeats
    # your long job
    import logging, time
    logging.info("training started")
    for epoch in range(5):
        time.sleep(5)
        logging.info("epoch %d done", epoch + 1)
    # if an uncaught exception happens, you'll get a ❌ alert
if __name__ == "__main__":
    main()
```
This sends:
* periodic “still running” messages,
* your `logging` messages (respecting the filter),
* a final ✅ on normal exit, or ❌ on crash.
---
## 🧰 Usage
### ✉️ Manual sends
```python
notify.send("Checkpoint saved", {"epoch": 12, "val_loss": 0.217})
```
### 🔭 Track “everything”
```python
notify.everything()  # start log capture, heartbeats, success/exception hooks
```
This wires:
* a `logging.Handler` to forward records,
* a heartbeat thread for periodic pings,
* `atexit` hook for success on normal interpreter termination,
* `sys.excepthook` to report uncaught exceptions before exit.
  (Functions registered with `atexit` run automatically on **normal interpreter termination**.) ([Python documentation][5])
### ⏱️ Filtering & heartbeat interval
```python
# only ERROR+ logs, heartbeat every 10 minutes
notify.everything(filter={"level": "ERROR"}, interval=600)
```
Supported filter keys (initially):
* `level`: `"DEBUG"|"INFO"|"WARNING"|"ERROR"|"CRITICAL"`
### ⚙️ Async usage
```python
import asyncio
from notifier import AsyncNotifier
async def run():
    notify = AsyncNotifier(discord="https://discord.com/api/webhooks/…")
    await notify.send("Job kicked off")
    await notify.everything(filter={"level": "WARNING"}, interval=900)
    # … your async workload …
asyncio.run(run())
```
---
## 📚 Documentation
See the docs site for a brief overview and getting started guide:
[docs/index.md](docs/index.md)
## 🛠️ How it works (under the hood)
* **Logging**: attaches a `logging.Handler` to forward formatted records; you keep using Python’s standard logging API. ([Python documentation][3])
* **Success on exit**: registers an `atexit` handler that fires on normal termination. (Not triggered on hard kills, fatal internal errors, or `os._exit()`.) ([Python documentation][5])
* **Crash reporting**: installs a custom `sys.excepthook` so uncaught exceptions send a ❌ message before the interpreter exits. You can always restore the original hook. ([Python documentation][2])
---
## 🔌 Plugins & extensibility
`PingCast` ships with a **Discord** plugin first. The architecture is intentionally simple:
* A tiny `BasePlugin` interface with `.send()` / `.send_async()`
* `Notifier` holds a list of plugins and broadcasts to each
* New channels (Slack, Email, Telegram, etc.) can be added with small plugin classes
For **automatic plugin discovery** later, we’ll adopt **entry points** so third-party packages can register plugins that `PingCast` discovers at runtime—this is the recommended approach in the Packaging User Guide. ([Python Packaging][9])
**Example entry point (future):**
```toml
# pyproject.toml of a third-party plugin package
[project.entry-points."pingcast.plugins"]
"slack" = "pingcast_slack:SlackPlugin"
```
Consumers can then do:
```python
notify = Notifier(slack="xoxb-…", discord="https://discord.com/api/webhooks/…")
```
---
## ⚙️ Configuration
Minimal constructor:
```python
notify = Notifier(discord="https://discord.com/api/webhooks/…")
```
Common options:
* `filter={"level": "INFO"}` — limit forwarded logs by level
* `interval=900` — heartbeat interval in seconds (default 15 min)
* `notify.send(message, details: dict | None)` — manual push
> Treat webhook URLs as **secrets**. Don’t commit them; prefer env vars or secret managers.
---
## 🐍 Supported Python versions
We target **Python 3.8+**. (Python 3.7 reached **end-of-life on 2023-06-27**; upgrading is strongly recommended.) ([Python Developer's Guide][10])
---
## 🛟 Troubleshooting
* **No messages arrive**
  • Double-check the webhook URL and channel permissions.
  • Look for HTTP 429s (rate limit) and back off before retrying. ([Discord][7])
* **Too many log messages**
  • Raise your filter level: `filter={"level": "ERROR"}`.
  • Consider sending only summary metrics with `notify.send(...)`.
* **Script was killed (SIGKILL / machine reboot)**
  • `atexit` only runs on **normal termination**; if the process is killed, the success hook won’t fire. ([Python documentation][5])
* **I want fewer heartbeats**
  • Increase `interval` (e.g., `interval=3600` for hourly).
---
## 🗺️ Roadmap
* Additional plugins (Slack/Telegram/Email)
* Optional batching & backoff for rate limits (429) ([Discord][7])
* Automatic plugin discovery via entry points ([Python Packaging][11])
* Structured embeds for richer Discord messages ([Discord][8])
---
## 🤝 Contributing
Issues and PRs welcome! Please:
* Open an issue to discuss larger changes before submitting a PR.
* Follow modern packaging practices (PEP 621 / `pyproject.toml`) and ensure tests pass.
* Keep changes focused and add/adjust tests where appropriate.
If you’re adding a new plugin, start with a tiny class implementing `.send()` and consider registering via entry points. Useful references: **Packaging Projects** tutorial and **Writing your `pyproject.toml`**. ([Python Packaging][12])
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
---
## 🔐 Security
If you believe you’ve found a security issue, please follow the process in [SECURITY.md](SECURITY.md) rather than opening a public issue.
---
## 📜 Code of Conduct
This project adheres to a Contributor Code of Conduct. By participating, you agree to uphold its terms. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
---
## 📝 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
---
## Citation
If you use this library in your research, please cite:
```bibtex
@article{pingcast,
  title={PingCast: A lightweight Python library for sending status updates to Discord},
  author={Islam, MD. Tarikul}
  year={2025}
  url={https://github.com/Tarikul-Islam-Anik/PingCast}
}
```
            
         
        Raw data
        
            {
    "_id": null,
    "home_page": null,
    "name": "pingcast",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "notifications, discord, webhook, logging, async",
    "author": "Project Notification",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/0d/2e/9d28e05b1432f4a4f13160307b3114cb341ce341f807a0a737f5c7932ffe/pingcast-0.1.0.tar.gz",
    "platform": null,
    "description": "# \ud83d\ude80 PingCast\n\n[](https://pypi.org/project/pingcast/)\n[](https://pypi.org/project/pingcast/)\n[](LICENSE)\n[](docs/index.md)\n\n`PingCast` is a lightweight Python library that sends status updates from your scripts (e.g., ML training, data pipelines, ETL jobs) to external channels. It starts with **Discord webhooks** and is built to be **plugin\u2011friendly** for future channels.\n\n* **\ud83d\udd27 Simple API:** `notify = Notifier(discord=WEBHOOK_URL)` \u2192 `notify.everything()`\n* **\u2705 Covers the basics:** success, failure (uncaught exceptions), periodic heartbeats, and optional log forwarding\n* **\ud83d\udd01 Sync & async:** `Notifier` (sync) and `AsyncNotifier` (async)\n* **\ud83d\udd0c Extensible:** clean plugin interface; future discovery via entry points\n\n---\n\n## \ud83e\udded Table of contents\n\n* [Why](#why)\n* [Install](#install)\n* [Quickstart](#quickstart)\n* [Usage](#usage)\n  * [Manual sends](#manual-sends)\n  * [Track \u201ceverything\u201d](#track-everything)\n  * [Filtering & heartbeat interval](#filtering--heartbeat-interval)\n  * [Async usage](#async-usage)\n* [Documentation](#documentation)\n* [How it works (under the hood)](#how-it-works-under-the-hood)\n* [Plugins & extensibility](#plugins--extensibility)\n* [Configuration](#configuration)\n* [Supported Python versions](#supported-python-versions)\n* [Troubleshooting](#troubleshooting)\n* [Roadmap](#roadmap)\n* [Contributing](#contributing)\n* [Security](#security)\n* [Code of Conduct](#code-of-conduct)\n* [License](#license)\n\n---\n\n## \u2753 Why\n\nTraining LLMs and running heavy experiments takes hours. People don\u2019t babysit terminals the whole time, and then return to discover the job failed hours ago. `PingCast` keeps you in the loop:\n\n* \u2705 **Completion notices** when the script exits normally\n* \u274c **Crash alerts** when an uncaught exception terminates your program (via `sys.excepthook`) ([Python documentation][2])\n* \u23f1\ufe0f **Periodic heartbeats** (default every 15 minutes) so you know it\u2019s still running\n* \ud83e\uddfe **Optional log forwarding** using the standard `logging` handlers (no invasive framework) ([Python documentation][3])\n\n---\n\n## \ud83d\udce6 Install\n\n```bash\npip install pingcast\n```\n\n---\n\n## \ud83d\ude80 Quickstart\n\n1. Create a **Discord webhook** in your channel (Server Settings \u2192 Integrations \u2192 Webhooks), then copy the URL.\n\n2. Use it in code:\n\n```python\nfrom notifier import Notifier\n\nnotify = Notifier(discord=\"https://discord.com/api/webhooks/XXXXXXXX/XXXXXXXX\")\n\ndef main():\n    # one line to wire everything:\n    notify.everything(filter={\"level\": \"INFO\"}, interval=900)  # 15 min heartbeats\n\n    # your long job\n    import logging, time\n    logging.info(\"training started\")\n    for epoch in range(5):\n        time.sleep(5)\n        logging.info(\"epoch %d done\", epoch + 1)\n\n    # if an uncaught exception happens, you'll get a \u274c alert\n\nif __name__ == \"__main__\":\n    main()\n```\n\nThis sends:\n\n* periodic \u201cstill running\u201d messages,\n* your `logging` messages (respecting the filter),\n* a final \u2705 on normal exit, or \u274c on crash.\n\n---\n\n## \ud83e\uddf0 Usage\n\n### \u2709\ufe0f Manual sends\n\n```python\nnotify.send(\"Checkpoint saved\", {\"epoch\": 12, \"val_loss\": 0.217})\n```\n\n### \ud83d\udd2d Track \u201ceverything\u201d\n\n```python\nnotify.everything()  # start log capture, heartbeats, success/exception hooks\n```\n\nThis wires:\n\n* a `logging.Handler` to forward records,\n* a heartbeat thread for periodic pings,\n* `atexit` hook for success on normal interpreter termination,\n* `sys.excepthook` to report uncaught exceptions before exit.\n  (Functions registered with `atexit` run automatically on **normal interpreter termination**.) ([Python documentation][5])\n\n### \u23f1\ufe0f Filtering & heartbeat interval\n\n```python\n# only ERROR+ logs, heartbeat every 10 minutes\nnotify.everything(filter={\"level\": \"ERROR\"}, interval=600)\n```\n\nSupported filter keys (initially):\n\n* `level`: `\"DEBUG\"|\"INFO\"|\"WARNING\"|\"ERROR\"|\"CRITICAL\"`\n\n### \u2699\ufe0f Async usage\n\n```python\nimport asyncio\nfrom notifier import AsyncNotifier\n\nasync def run():\n    notify = AsyncNotifier(discord=\"https://discord.com/api/webhooks/\u2026\")\n    await notify.send(\"Job kicked off\")\n    await notify.everything(filter={\"level\": \"WARNING\"}, interval=900)\n    # \u2026 your async workload \u2026\n\nasyncio.run(run())\n```\n\n---\n\n## \ud83d\udcda Documentation\n\nSee the docs site for a brief overview and getting started guide:\n\n[docs/index.md](docs/index.md)\n\n## \ud83d\udee0\ufe0f How it works (under the hood)\n\n* **Logging**: attaches a `logging.Handler` to forward formatted records; you keep using Python\u2019s standard logging API. ([Python documentation][3])\n* **Success on exit**: registers an `atexit` handler that fires on normal termination. (Not triggered on hard kills, fatal internal errors, or `os._exit()`.) ([Python documentation][5])\n* **Crash reporting**: installs a custom `sys.excepthook` so uncaught exceptions send a \u274c message before the interpreter exits. You can always restore the original hook. ([Python documentation][2])\n\n---\n\n## \ud83d\udd0c Plugins & extensibility\n\n`PingCast` ships with a **Discord** plugin first. The architecture is intentionally simple:\n\n* A tiny `BasePlugin` interface with `.send()` / `.send_async()`\n* `Notifier` holds a list of plugins and broadcasts to each\n* New channels (Slack, Email, Telegram, etc.) can be added with small plugin classes\n\nFor **automatic plugin discovery** later, we\u2019ll adopt **entry points** so third-party packages can register plugins that `PingCast` discovers at runtime\u2014this is the recommended approach in the Packaging User Guide. ([Python Packaging][9])\n\n**Example entry point (future):**\n\n```toml\n# pyproject.toml of a third-party plugin package\n[project.entry-points.\"pingcast.plugins\"]\n\"slack\" = \"pingcast_slack:SlackPlugin\"\n```\n\nConsumers can then do:\n\n```python\nnotify = Notifier(slack=\"xoxb-\u2026\", discord=\"https://discord.com/api/webhooks/\u2026\")\n```\n\n---\n\n## \u2699\ufe0f Configuration\n\nMinimal constructor:\n\n```python\nnotify = Notifier(discord=\"https://discord.com/api/webhooks/\u2026\")\n```\n\nCommon options:\n\n* `filter={\"level\": \"INFO\"}` \u2014 limit forwarded logs by level\n* `interval=900` \u2014 heartbeat interval in seconds (default 15 min)\n* `notify.send(message, details: dict | None)` \u2014 manual push\n\n> Treat webhook URLs as **secrets**. Don\u2019t commit them; prefer env vars or secret managers.\n\n---\n\n## \ud83d\udc0d Supported Python versions\n\nWe target **Python 3.8+**. (Python 3.7 reached **end-of-life on 2023-06-27**; upgrading is strongly recommended.) ([Python Developer's Guide][10])\n\n---\n\n## \ud83d\udedf Troubleshooting\n\n* **No messages arrive**\n  \u2022 Double-check the webhook URL and channel permissions.\n  \u2022 Look for HTTP 429s (rate limit) and back off before retrying. ([Discord][7])\n\n* **Too many log messages**\n  \u2022 Raise your filter level: `filter={\"level\": \"ERROR\"}`.\n  \u2022 Consider sending only summary metrics with `notify.send(...)`.\n\n* **Script was killed (SIGKILL / machine reboot)**\n  \u2022 `atexit` only runs on **normal termination**; if the process is killed, the success hook won\u2019t fire. ([Python documentation][5])\n\n* **I want fewer heartbeats**\n  \u2022 Increase `interval` (e.g., `interval=3600` for hourly).\n\n---\n\n## \ud83d\uddfa\ufe0f Roadmap\n\n* Additional plugins (Slack/Telegram/Email)\n* Optional batching & backoff for rate limits (429) ([Discord][7])\n* Automatic plugin discovery via entry points ([Python Packaging][11])\n* Structured embeds for richer Discord messages ([Discord][8])\n\n---\n\n## \ud83e\udd1d Contributing\n\nIssues and PRs welcome! Please:\n\n* Open an issue to discuss larger changes before submitting a PR.\n* Follow modern packaging practices (PEP 621 / `pyproject.toml`) and ensure tests pass.\n* Keep changes focused and add/adjust tests where appropriate.\n\nIf you\u2019re adding a new plugin, start with a tiny class implementing `.send()` and consider registering via entry points. Useful references: **Packaging Projects** tutorial and **Writing your `pyproject.toml`**. ([Python Packaging][12])\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for details.\n\n---\n\n## \ud83d\udd10 Security\n\nIf you believe you\u2019ve found a security issue, please follow the process in [SECURITY.md](SECURITY.md) rather than opening a public issue.\n\n---\n\n## \ud83d\udcdc Code of Conduct\n\nThis project adheres to a Contributor Code of Conduct. By participating, you agree to uphold its terms. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).\n\n---\n\n## \ud83d\udcdd License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n---\n\n## Citation\n\nIf you use this library in your research, please cite:\n\n```bibtex\n@article{pingcast,\n  title={PingCast: A lightweight Python library for sending status updates to Discord},\n  author={Islam, MD. Tarikul}\n  year={2025}\n  url={https://github.com/Tarikul-Islam-Anik/PingCast}\n}\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Pluggable Python notification utility with Discord webhook support",
    "version": "0.1.0",
    "project_urls": {
        "Homepage": "https://example.com/pingcast",
        "Repository": "https://example.com/pingcast/repo"
    },
    "split_keywords": [
        "notifications",
        " discord",
        " webhook",
        " logging",
        " async"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d5164eda4439f9c867b63f14132e99975f23dd8576def8ed3aecdd5058be9c27",
                "md5": "ba843587c92ed0742c71b1e249be2dc1",
                "sha256": "d5b17f34026c07cf2c6d327610b945f9d6e0f6ac2e452e09be432d82ca0dedc7"
            },
            "downloads": -1,
            "filename": "pingcast-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ba843587c92ed0742c71b1e249be2dc1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 15083,
            "upload_time": "2025-10-30T15:38:52",
            "upload_time_iso_8601": "2025-10-30T15:38:52.939519Z",
            "url": "https://files.pythonhosted.org/packages/d5/16/4eda4439f9c867b63f14132e99975f23dd8576def8ed3aecdd5058be9c27/pingcast-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "0d2e9d28e05b1432f4a4f13160307b3114cb341ce341f807a0a737f5c7932ffe",
                "md5": "b74750618ead23da37a39f15877416d6",
                "sha256": "d3310c44dd0c118814b82ee968b069470a8c3cbce0639287141738d58823f95e"
            },
            "downloads": -1,
            "filename": "pingcast-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "b74750618ead23da37a39f15877416d6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 19459,
            "upload_time": "2025-10-30T15:38:54",
            "upload_time_iso_8601": "2025-10-30T15:38:54.338662Z",
            "url": "https://files.pythonhosted.org/packages/0d/2e/9d28e05b1432f4a4f13160307b3114cb341ce341f807a0a737f5c7932ffe/pingcast-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-30 15:38:54",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "pingcast"
}