Skip to main content

Keep your Python bots alive with a zero-config, production-ready background web server.

Project description

StayPresent

StayPresent Logo

PyPI version Python versions License: MIT

🛖 About

A lightweight Python package designed to keep your bots and background scripts alive by running a dedicated Flask web server alongside your main application.

Perfect for deploying on platforms like Render, Railway, Koyeb, Heroku, or any host that requires an active HTTP port to keep your service running.


Contents


🚀 Features

  • Zero-Friction Setup: Get running with just one line of code.
  • Production-Ready by Default: Automatically detects and uses waitress to avoid Flask's "development server" warnings.
  • Auto-Restarts & Crash Recovery: Automatically respawns your bot process if it crashes, complete with customizable delays and max-restart limits.
  • Flexible Responses: Serve custom plain text, JSON (default), or full HTML templates.
  • Static Asset Serving: Automatically serves CSS, JS, and images located next to your HTML templates.
  • Advanced Control: Easily pass custom command-line arguments and environment variables directly to your bot process.
  • Fail-Safe Logging: Logs a clear error if the underlying web server dies unexpectedly.
  • Optional Self-Ping / Keep-Warm: Periodically ping your own public URL in the background to prevent free-tier hosts from spinning your service down due to inactivity — fully opt-in, off by default.

📦 Installation

Install via pip:

pip install staypresent

Recommended for Production: To automatically use a production WSGI server and suppress Flask development warnings, install the prod extra. This pulls in waitress.

pip install staypresent[prod]

(Note: If waitress isn't installed, StayPresent gracefully falls back to Flask's built-in development server and logs a one-time warning.)


💻 Usage Guide

Basic Usage (Text Response)

import staypresent

staypresent.web.text("Made With ❤️")
staypresent.run("bot.py")

*Navigating to http://localhost:8080 will return plain text: Made With ❤️*

Note: If you don't configure a response, StayPresent defaults to a JSON response of {"message": "I'm Present"} at the root / route.

JSON Response

A safe copy of your dictionary is stored. If you need to update live data, just call staypresent.web.json() again.

import staypresent

staypresent.web.json({
    "status": "online",
    "developer": "John",
    "message": "Made With Love ❤️"
})

staypresent.run("bot.py")

HTML Response (with Static Files)

Serve a full HTML page. The file is read fresh on every request, allowing you to edit your HTML on disk without restarting your bot.

import staypresent

# The path is validated immediately and will raise a FileNotFoundError if missing
staypresent.web.html("template/index.html")
staypresent.run("bot.py")

Serving Static Assets: Any files (CSS, JS, images) in the same directory as your HTML file are automatically served. Path traversal is strictly blocked for security.

<!-- template/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>My Bot</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <h1>I'm alive!</h1>
    <img src="images/logo.png">
  </body>
</html>

Custom Host, Port, and Threads (Complete Example)

import staypresent

staypresent.web.json({
    "status": "Running",
    "version": "1.0.0"
})

staypresent.run(
    "bot.py",
    host="0.0.0.0",
    port=8080,
    threads=8  # Increase if receiving real web traffic
)

📡 Self-Ping / Keep-Warm

Some free hosting tiers (Render, Railway, Replit, etc.) spin your service down after a period of inactivity, and only wake it back up on the next incoming request. staypresent.ping() and staypresent.cron() are a completely optional way to work around this by having your app periodically hit its own public URL — nothing runs unless you call one of them yourself.

⚠️ Ping your public URL, not 127.0.0.1/0.0.0.0. Traffic that never leaves the machine doesn't count as activity to the hosting platform. staypresent.cron("https://your-app.onrender.com") works for that; staypresent.cron("0.0.0.0", port=8080) is only useful for locally smoke-testing that your own server is responding.

staypresent.ping(...) — one-off check

Synchronous — fires a single HTTP GET and returns immediately with the result.

result = staypresent.ping("https://my-app.onrender.com")
# -> {"url": "...", "ok": True, "status_code": 200, "elapsed": 0.31, "error": None}

if not result["ok"]:
    print("Something's wrong:", result["error"])

staypresent.cron(...) — repeat on a schedule

Non-blocking — starts a background thread that calls ping() on a schedule. Call it before staypresent.run().

import staypresent

# Ping our own public URL every 4 minutes to keep the free-tier instance awake
staypresent.cron("https://my-app.onrender.com", interval=240)

staypresent.run("bot.py")

With callbacks, e.g. to log failures somewhere more visible:

staypresent.cron(
    "https://my-app.onrender.com",
    interval=240,
    on_success=lambda r: print(f"warm ping ok ({r['elapsed']}s)"),
    on_failure=lambda r: print(f"warm ping failed: {r['error']}"),
)

host accepts a bare domain ("my-app.onrender.com"), a full URL ("https://my-app.onrender.com/health"), or a local bind address ("0.0.0.0", treated as 127.0.0.1) — same rules for both ping() and cron().

cron() returns a handle if you ever need to cancel it:

handle = staypresent.cron("https://my-app.onrender.com", interval=240)
...
handle.stop()          # stop pinging
handle.is_running       # True/False

Starting and stopping are both logged (Started cron: pinging ... every 240s), so you can confirm it's actually active. Pings run one at a time — if a ping takes a while to time out, the actual gap before the next one grows accordingly rather than piling up requests in parallel.

Parameter Type Default Description
host str Required Bare domain, full URL, or bind address (see above).
port int None Port to connect to. Ignored if host is already a full URL.
path str "/" Path to request. Ignored if host is already a full URL.
timeout float 10.0 Seconds to wait for a response before treating the ping as failed.
https bool None Force http/https. Auto-detected by default (local addresses → http, everything else → https).
interval (cron only) float 300.0 Seconds between pings.
repeat (cron only) bool True Keep pinging forever, or just once in the background.
on_success (cron only) callable None fn(result) called after each successful ping.
on_failure (cron only) callable None fn(result) called after each failed ping.

staypresent.run(...)

Launch your bot script alongside the web server.

Parameter Type Default Description
bot_file str Required Path to the Python script to run alongside the server.
host str "0.0.0.0" Host to bind the web server to.
port int 8080 Port to bind the web server to.
production bool True Uses waitress if installed. Set to False to force the Flask dev server.
threads int 4 Number of worker threads for waitress. Increase this if serving real web traffic rather than just keep-alive pings. (Requires production=True and waitress).
restart_on_crash bool True Relaunch the bot process if it exits with a non-zero exit code.
max_restarts int 5 Maximum restart attempts after a crash before giving up.
restart_delay float 2.0 Seconds to wait before relaunching the bot process after a crash.
restart_reset_after float 60.0 Seconds the bot must stay alive to reset the consecutive crash counter back to 0.
bot_args list None Extra command-line arguments to pass to bot_file (e.g., ["--verbose"]). Must be a list — a bare string like "--flag" raises a clear error instead of silently exploding into individual characters.
env dict None Extra environment variables for the bot process. Merges over the current environment.

Note: port, threads, max_restarts, restart_delay, and restart_reset_after are validated up front — passing an invalid value (e.g. threads=0, a negative port) raises a ValueError immediately instead of failing silently or deep inside waitress.

Crash Recovery Details

StayPresent automatically monitors your bot process. If it exits with a non-zero exit code, StayPresent restarts it based on your configuration:

  • Clean Exits: An exit code of 0 is considered intentional and will not trigger a restart.
  • Manual Shutdowns: Stopping StayPresent via Ctrl+C (SIGINT) or SIGTERM shuts down both the server and the bot cleanly.
  • Smart Counters: The max_restarts limit applies to consecutive crashes. If your bot runs successfully for the duration of restart_reset_after (default 60 seconds), the crash counter resets.
  • Non-Zero Exit on Giving Up: If the bot ultimately fails to stay up — restarts disabled and it crashed, or max_restarts was exhausted — staypresent.run() exits the whole process with the bot's last exit code instead of returning normally. This lets a hosting platform's own restart-on-crash policy (Render, Railway, Docker, systemd, etc.) kick in as a last resort, instead of the process quietly exiting 0 as if nothing went wrong.

Built-in Health Check

A dedicated /health endpoint is automatically exposed, returning {"status": "ok"}. This is incredibly useful for platform pingers and uptime monitors that require a dedicated health-check path separate from your root / response.

Inspecting the Current Response

staypresent.web.get()
# -> {"type": "json", "value": {"message": "I'm Present"}}

Returns whatever text() / json() / html() last configured, as {"type": ..., "value": ...}. Mainly useful for debugging or unit-testing your own code around StayPresent.


📝 Logging

StayPresent logs to its own "staypresent" logger, not the root logger — it never calls logging.basicConfig() globally. This means it won't clobber, duplicate, or reformat logging you've already set up elsewhere in your script for unrelated loggers.

To change the log level or format, configure it like any other logger:

import logging
logging.getLogger("staypresent").setLevel(logging.WARNING)

🛠 Requirements

  • Python 3.8+
  • Flask
  • waitress (optional, but highly recommended for production)

💡 Use Cases

  • Discord & Telegram Bots
  • Background Workers & Automation Scripts
  • Keeping deployments alive on Render, Railway, Koyeb, and Heroku

License: MIT License

Made with ❤️ using Python.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

staypresent-1.4.2.tar.gz (20.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

staypresent-1.4.2-py3-none-any.whl (18.3 kB view details)

Uploaded Python 3

File details

Details for the file staypresent-1.4.2.tar.gz.

File metadata

  • Download URL: staypresent-1.4.2.tar.gz
  • Upload date:
  • Size: 20.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for staypresent-1.4.2.tar.gz
Algorithm Hash digest
SHA256 d5d0935bf3180818f9272d85ecf12519fd9a32bb1ebb57354696894d1ef32ad3
MD5 b879460d4ecc6258a0328b84d0adb11e
BLAKE2b-256 9365cb726c17ce756e097f890fa84324cf6583b099a922b8851ae76054d41eaa

See more details on using hashes here.

Provenance

The following attestation bundles were made for staypresent-1.4.2.tar.gz:

Publisher: python-publish.yml on NTDevLops/StayPresent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file staypresent-1.4.2-py3-none-any.whl.

File metadata

  • Download URL: staypresent-1.4.2-py3-none-any.whl
  • Upload date:
  • Size: 18.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for staypresent-1.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 13b32435f83650faf1e39aced2c6fb3ef732a610aadb633fdb5d4434c0017f16
MD5 d4089edf42de65a74c9487183969032b
BLAKE2b-256 68b33d0c068570cde78bea1b19877eb10c2159125b47264e91e4a0f82765ae5d

See more details on using hashes here.

Provenance

The following attestation bundles were made for staypresent-1.4.2-py3-none-any.whl:

Publisher: python-publish.yml on NTDevLops/StayPresent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page