Skip to main content

Create HTTP endpoints quickly using files, without going through a framework.

Project description

Bitpoint

Create HTTP endpoints quickly using files, without going through a framework like Flask or FastAPI.

  • File-based routing: the directory structure defines the routes, the file name defines the HTTP method.
  • Hot reload: save the file and the server updates automatically. Deploy with a simple git pull.
  • Per-endpoint dependencies: each endpoint declares its dependencies with PEP 723 and they are installed automatically in an isolated environment.

Bitpoint is for when the API is not the product, just plumbing: a webhook, a mock for the frontend, a small internal service, a few endpoints on a personal server. You write one file and you get one endpoint.

There is nothing to register and nothing to configure: no app object, no decorators, no config files, just a handle function per file. Dependency isolation is delegated to uv, and responses follow the type you return: a string becomes plain text, a dict becomes JSON.

Some use cases:

  • Webhooks
  • Internal tools
  • API mocks
  • Glue between services
  • Personal server
  • Easy deployment

Installation

pip install bitpoint

Or, if you use uv:

uv tool install bitpoint

Python 3.9 or later. If you want per-endpoint dependencies, uv must be available on the server.

Quickstart

Create a directory called routes and, inside it, a subdirectory called hello (this will be the route). Inside hello, create a file called get.py with the following content:

# routes/hello/get.py

def handle(request):
    return "world"

Start the server with:

bitpoint

python -m bitpoint works too. And with uv you can even skip the installation step and run it directly:

uvx bitpoint

You can now try your endpoint:

curl 127.0.0.1:8000/hello

It will return:

world

Routing

The URL path maps to the directory path inside routes, and the HTTP method maps to the file name:

routes/
├── webhooks/
│   └── github/
│       └── post.py     → POST /webhooks/github (a webhook receiver)
├── invoices/
│   ├── get.py          → GET  /invoices (an internal service, or a mock)
│   ├── post.py         → POST /invoices
│   └── [id]/
│       ├── get.py      → GET  /invoices/42
│       └── delete.py   → DELETE /invoices/42
└── lib/                → shared code, does not generate routes
    └── db.py

Valid file names are the HTTP methods in lowercase: get.py, post.py, put.py, patch.py, delete.py, head.py and options.py. Any other file is ignored and does not generate routes, so you can keep helper modules next to your endpoints.

Disabling endpoints

Prefix a name with an underscore to disable it without deleting anything. Renaming get.py to _get.py stops serving that method, and renaming a directory to _invoices/ takes its whole subtree offline: requests respond with 404 (or 405 if the route still serves other methods). Rename it back and the endpoint is live again, no restart needed.

One detail: a [param] directory still captures URL segments that happen to start with an underscore, disabling only affects how your files are routed, not which URLs are valid.

Dynamic routes

A directory whose name is wrapped in brackets captures a URL segment:

# routes/invoices/[id]/get.py

def handle(request):
    return {"invoice_id": request.params["id"]}
curl 127.0.0.1:8000/invoices/42
# {"invoice_id": "42"}

Captured values always arrive as str. Type conversion is the endpoint's responsibility.

The handle contract

Each endpoint exposes a handle(request) function. It is the only symbol Bitpoint looks for in the file, the rest of the module is yours.

The request object

Attribute Type Description
request.method str HTTP method in uppercase, for example "GET"
request.path str Request path, for example /users/42
request.params dict[str, str] Dynamic path segments
request.args dict[str, str] Query string parameters
request.headers dict[str, str] Headers, case-insensitive keys
request.body bytes Raw request body
request.json() Any Body parsed as JSON, raises a 400 error if invalid

Example with a query string:

# routes/greet/get.py

def handle(request):
    name = request.args.get("name", "world")
    return f"Hello, {name}!"
curl "127.0.0.1:8000/greet?name=Bob"
# Hello, Bob!

Return values

The return type determines the response:

Return Response
str 200, text/plain; charset=utf-8
dict or list 200, application/json
bytes 200, application/octet-stream
(body, status) As above, with the given status code
(body, status, headers) Additionally with custom headers
None 204 No Content
# routes/users/post.py

def handle(request):
    data = request.json()
    return {"created": data["name"]}, 201

Errors

  • If handle raises an exception, Bitpoint responds with 500.
  • In development mode (the default) the body includes the traceback. In production (--production) the body is a generic message and the traceback goes to the log.
  • Path with no matching directory: 404. Directory exists but there is no file for that method: 405 with the Allow header listing the available methods.

Dependencies

Declare each endpoint's dependencies with a PEP 723 block at the top of the file, the same format understood by uv and pipx:

# routes/status/get.py

# /// script
# dependencies = ["requests"]
# ///

def handle(request):
    import requests
    return requests.get("https://api.github.com/repos/tanrax/bitpoint").json()

Bitpoint delegates resolution and installation to uv: each endpoint runs with its dependencies in an isolated, cached environment. Two endpoints can use incompatible versions of the same library without conflict.

  • An endpoint without a PEP 723 block runs in the base environment, with no extra cost.
  • Installation happens on first startup and whenever the dependency block changes, not on every request.

Security note: installing dependencies automatically after a git pull means running third-party code at deploy time. If you prefer to control that step, start with --no-install and Bitpoint will fail with a clear error on endpoints whose dependencies are not already installed.

Shared code

The routes/lib/ directory is reserved: it does not generate routes and is importable from any endpoint:

# routes/lib/db.py

def get_connection():
    ...
# routes/users/get.py

from lib.db import get_connection

def handle(request):
    conn = get_connection()
    ...

Configuration

Everything is controlled from the command line, there is no configuration file. The same options work with every launcher: bitpoint, python -m bitpoint or uvx bitpoint.

bitpoint [options]
Option Default Description
--port 8000 Listening port
--host 127.0.0.1 Listening address
--dir ./routes Root directory for endpoints
--production disabled Hides tracebacks and disables filesystem-based hot reload
--no-install disabled Does not install dependencies automatically

Hot reload

In development mode, Bitpoint watches the routes directory and reloads each module when its file changes. There is no shared state across reloads: if your endpoint keeps in-memory state (caches, connections), it is lost on reload. For persistent state use external resources (a database, Redis, files).

In production, filesystem-based reload is disabled. The recommended deploy flow is git pull followed by a reload signal:

git pull && kill -HUP $(cat bitpoint.pid)

A real example

A GitHub webhook that sends a Telegram message on every push:

# routes/webhooks/github/post.py

# /// script
# dependencies = ["requests"]
# ///
import os
import requests

def handle(request):
    if request.headers.get("x-github-event") != "push":
        return None
    payload = request.json()
    pusher = payload["pusher"]["name"]
    repo = payload["repository"]["name"]
    commits = len(payload["commits"])
    requests.post(
        f"https://api.telegram.org/bot{os.environ['TELEGRAM_TOKEN']}/sendMessage",
        json={
            "chat_id": os.environ["TELEGRAM_CHAT_ID"],
            "text": f"{pusher} pushed {commits} commit(s) to {repo}",
        },
    )
    return None

One file, deployed with a git pull. No project, no virtualenv, no route registration, no restart.

Small core

Some areas are explicitly not covered in order to keep the core small:

  • Middleware and global hooks
  • Authentication
  • WebSockets and streaming
  • async endpoints
  • Static files

If you need any of this today, Bitpoint is not your tool, you will have to use external tools.

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

bitpoint-1.1.0.tar.gz (17.3 kB view details)

Uploaded Source

Built Distribution

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

bitpoint-1.1.0-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file bitpoint-1.1.0.tar.gz.

File metadata

  • Download URL: bitpoint-1.1.0.tar.gz
  • Upload date:
  • Size: 17.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for bitpoint-1.1.0.tar.gz
Algorithm Hash digest
SHA256 54ac840de1fecde764475cdc14026142c247b9b1d1e667364c6f229552aaa0d9
MD5 65b29adf3b2b3796a21d99a95e0711ab
BLAKE2b-256 b208ca2268b60a7de4f170fa66ef09c54d019b8874bef3998bd707b450894565

See more details on using hashes here.

File details

Details for the file bitpoint-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: bitpoint-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for bitpoint-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 35c114a33ba2c758f55ade8945dd6d2277c69bbebfc15c4d3d4dff864f0ec432
MD5 1ca1ea0ed061afef8dcd25dd42d2800b
BLAKE2b-256 0dcad361204f560798134b39fce31cfbdddda9f2e4133443249d5860da3acfc2

See more details on using hashes here.

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