Pluggable Python notification utility with Discord webhook support
Project description
🚀 PingCast
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:
from pingcast import Discord, Notifier→Notifier(Discord(WEBHOOK_URL)).everything() - ✅ Covers the basics: success, failure (uncaught exceptions), periodic heartbeats, and optional log forwarding
- 🔁 Sync & async:
Notifier(sync) andAsyncNotifier(async) - 🔌 Extensible: clean plugin interface; future discovery via entry points
🧭 Table of contents
- Why
- Install
- Quickstart
- Usage
- Documentation
- How it works (under the hood)
- Plugins & extensibility
- Configuration
- Supported Python versions
- Troubleshooting
- Roadmap
- Contributing
- Security
- Code of Conduct
- 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
logginghandlers (no invasive framework) ([Python documentation][3])
📦 Install
pip install pingcast
🚀 Quickstart
-
Create a Discord webhook in your channel (Server Settings → Integrations → Webhooks), then copy the URL.
-
Use it in code:
from pingcast import Discord, Notifier
discord = Discord("https://discord.com/api/webhooks/XXXXXXXX/XXXXXXXX")
notify = Notifier(discord)
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
loggingmessages (respecting the filter), - a final ✅ on normal exit, or ❌ on crash.
🧰 Usage
✉️ Manual sends
notify.send("Checkpoint saved", {"epoch": 12, "val_loss": 0.217})
🔭 Track “everything”
notify.everything() # start log capture, heartbeats, success/exception hooks
This wires:
- a
logging.Handlerto forward records, - a heartbeat thread for periodic pings,
atexithook for success on normal interpreter termination,sys.excepthookto report uncaught exceptions before exit. (Functions registered withatexitrun automatically on normal interpreter termination.) ([Python documentation][5])
⏱️ Filtering & heartbeat interval
# 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
import asyncio
from pingcast import AsyncNotifier, Discord
async def run():
discord = Discord("https://discord.com/api/webhooks/…")
notify = AsyncNotifier(discord)
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:
🛠️ How it works (under the hood)
- Logging: attaches a
logging.Handlerto forward formatted records; you keep using Python’s standard logging API. ([Python documentation][3]) - Success on exit: registers an
atexithandler that fires on normal termination. (Not triggered on hard kills, fatal internal errors, oros._exit().) ([Python documentation][5]) - Crash reporting: installs a custom
sys.excepthookso 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
BasePlugininterface with.send()/.send_async() Notifierholds 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):
# pyproject.toml of a third-party plugin package
[project.entry-points."pingcast.plugins"]
"slack" = "pingcast_slack:SlackPlugin"
Consumers can then do:
notify = Notifier(slack="xoxb-…", discord="https://discord.com/api/webhooks/…")
⚙️ Configuration
Minimal constructor:
from pingcast import Discord, Notifier
notify = Notifier(Discord("https://discord.com/api/webhooks/…"))
Common options:
filter={"level": "INFO"}— limit forwarded logs by levelinterval=900— heartbeat interval in seconds (default 15 min)notify.send(message, details: dict | None)— manual push- Legacy keyword-style initialization (
Notifier(discord="…")) still works, but new plugin-style construction is preferred so multiple channels can be combined.
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 withnotify.send(...). -
Script was killed (SIGKILL / machine reboot) •
atexitonly 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=3600for 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 for details.
🔐 Security
If you believe you’ve found a security issue, please follow the process in 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.
📝 License
This project is licensed under the MIT License - see the LICENSE file for details.
Citation
If you use this library in your research, please cite:
@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}
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pingcast-0.1.0a0.tar.gz.
File metadata
- Download URL: pingcast-0.1.0a0.tar.gz
- Upload date:
- Size: 20.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
147fcf56478089029cdbf1552b97f74c363e05edd03537b84b927a179c8c24da
|
|
| MD5 |
b295d7fb7d64b086d1a6957cf0da85bd
|
|
| BLAKE2b-256 |
f93e365b2f3720d421acae8ba0884de0ce244816e38e5995f45acac28fb92c8b
|
Provenance
The following attestation bundles were made for pingcast-0.1.0a0.tar.gz:
Publisher:
publish.yml on Tarikul-Islam-Anik/PingCast
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pingcast-0.1.0a0.tar.gz -
Subject digest:
147fcf56478089029cdbf1552b97f74c363e05edd03537b84b927a179c8c24da - Sigstore transparency entry: 658998088
- Sigstore integration time:
-
Permalink:
Tarikul-Islam-Anik/PingCast@4035ec5bdb093eb247f425228260517fe2c84f7c -
Branch / Tag:
refs/tags/0.1.0-alpha - Owner: https://github.com/Tarikul-Islam-Anik
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4035ec5bdb093eb247f425228260517fe2c84f7c -
Trigger Event:
release
-
Statement type:
File details
Details for the file pingcast-0.1.0a0-py3-none-any.whl.
File metadata
- Download URL: pingcast-0.1.0a0-py3-none-any.whl
- Upload date:
- Size: 16.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e9b054490ef29da974782ac0ea4083de0dc6ac960d1057288c48aa2bc67af1b
|
|
| MD5 |
2f4c1a6d240625fe26ea14328dbe1bc0
|
|
| BLAKE2b-256 |
759a34bbbeb2a600f32f736a097fb8b2bfa2a4b364b02a663e8568d979f35325
|
Provenance
The following attestation bundles were made for pingcast-0.1.0a0-py3-none-any.whl:
Publisher:
publish.yml on Tarikul-Islam-Anik/PingCast
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pingcast-0.1.0a0-py3-none-any.whl -
Subject digest:
1e9b054490ef29da974782ac0ea4083de0dc6ac960d1057288c48aa2bc67af1b - Sigstore transparency entry: 658998094
- Sigstore integration time:
-
Permalink:
Tarikul-Islam-Anik/PingCast@4035ec5bdb093eb247f425228260517fe2c84f7c -
Branch / Tag:
refs/tags/0.1.0-alpha - Owner: https://github.com/Tarikul-Islam-Anik
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4035ec5bdb093eb247f425228260517fe2c84f7c -
Trigger Event:
release
-
Statement type: