bitpoint 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: astral-sh/setup-uv@v6
17
+
18
+ - name: Build distributions
19
+ run: uv build
20
+
21
+ - name: Publish to PyPI
22
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ bitpoint.pid
8
+ uv.lock
9
+ CLAUDE.md
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.20
4
+ hooks:
5
+ - id: ruff-check
6
+ args: [--fix]
7
+ - id: ruff-format
bitpoint-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andros Fenollosa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,299 @@
1
+ Metadata-Version: 2.4
2
+ Name: bitpoint
3
+ Version: 0.1.0
4
+ Summary: Create HTTP endpoints quickly using files, without going through a framework.
5
+ Project-URL: Homepage, https://github.com/tanrax/bitpoint
6
+ Project-URL: Repository, https://github.com/tanrax/bitpoint
7
+ Project-URL: Issues, https://github.com/tanrax/bitpoint/issues
8
+ Author-email: Andros Fenollosa <andros@fenollosa.email>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: endpoints,file-based-routing,http,microframework,pep723,webhooks
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
23
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
+ Requires-Python: >=3.9
25
+ Requires-Dist: flask>=3.0
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Bitpoint
29
+
30
+ Create HTTP endpoints quickly using files, without going through a framework like Flask or FastAPI.
31
+
32
+ - **File-based routing**: the directory structure defines the routes, the file name defines the HTTP method.
33
+ - **Hot reload**: save the file and the server updates automatically. Deploy with a simple `git pull`.
34
+ - **Per-endpoint dependencies**: each endpoint declares its dependencies with [PEP 723](https://peps.python.org/pep-0723/) and they are installed automatically in an isolated environment.
35
+
36
+ Bitpoint is for the moments when a full project feels like too much: exposing a script as a webhook, mocking an API so the frontend can move forward, publishing a small internal service for your team, or keeping a handful of endpoints alive on a personal server. In all these cases the API is not the product, it is just plumbing, and spending an afternoon on scaffolding, virtual environments and deploy pipelines is out of proportion. With Bitpoint you write one file and you get one endpoint.
37
+
38
+ Under the hood, Bitpoint stays out of your way. 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](https://docs.astral.sh/uv/), with a cached environment per endpoint, so two endpoints can use incompatible versions of the same library without ever conflicting. Responses follow the type you return: a string becomes plain text, a dict becomes JSON, a tuple adds a status code and headers, and the sensible thing happens by default.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install bitpoint
44
+ ```
45
+
46
+ Or, if you use [uv](https://docs.astral.sh/uv/):
47
+
48
+ ```bash
49
+ uv tool install bitpoint
50
+ ```
51
+
52
+ Python 3.9 or later. If you want per-endpoint dependencies, `uv` must be available on the server.
53
+
54
+ ## Quickstart
55
+
56
+ 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:
57
+
58
+ ```python
59
+ # routes/hello/get.py
60
+
61
+ def handle(request):
62
+ return "world"
63
+ ```
64
+
65
+ Start the server with:
66
+
67
+ ```bash
68
+ python -m bitpoint
69
+ ```
70
+
71
+ You can now try your endpoint:
72
+
73
+ ```bash
74
+ curl localhost:8000/hello
75
+ ```
76
+
77
+ It will return:
78
+
79
+ ```
80
+ world
81
+ ```
82
+
83
+ ## Routing
84
+
85
+ The URL path maps to the directory path inside `routes`, and the HTTP method maps to the file name:
86
+
87
+ ```
88
+ routes/
89
+ ├── hello/
90
+ │ └── get.py → GET /hello
91
+ ├── users/
92
+ │ ├── get.py → GET /users
93
+ │ ├── post.py → POST /users
94
+ │ └── [id]/
95
+ │ ├── get.py → GET /users/42
96
+ │ └── delete.py → DELETE /users/42
97
+ └── lib/ → shared code, does not generate routes
98
+ └── db.py
99
+ ```
100
+
101
+ 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.
102
+
103
+ ### Dynamic routes
104
+
105
+ A directory whose name is wrapped in brackets captures a URL segment:
106
+
107
+ ```python
108
+ # routes/users/[id]/get.py
109
+
110
+ def handle(request):
111
+ return {"user_id": request.params["id"]}
112
+ ```
113
+
114
+ ```bash
115
+ curl localhost:8000/users/42
116
+ # {"user_id": "42"}
117
+ ```
118
+
119
+ Captured values always arrive as `str`. Type conversion is the endpoint's responsibility.
120
+
121
+ ## The `handle` contract
122
+
123
+ 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.
124
+
125
+ ### The `request` object
126
+
127
+ | Attribute | Type | Description |
128
+ |---|---|---|
129
+ | `request.method` | `str` | HTTP method in uppercase, for example `"GET"` |
130
+ | `request.path` | `str` | Request path, for example `/users/42` |
131
+ | `request.params` | `dict[str, str]` | Dynamic path segments |
132
+ | `request.args` | `dict[str, str]` | Query string parameters |
133
+ | `request.headers` | `dict[str, str]` | Headers, case-insensitive keys |
134
+ | `request.body` | `bytes` | Raw request body |
135
+ | `request.json()` | `Any` | Body parsed as JSON, raises a 400 error if invalid |
136
+
137
+ Example with a query string:
138
+
139
+ ```python
140
+ # routes/greet/get.py
141
+
142
+ def handle(request):
143
+ name = request.args.get("name", "world")
144
+ return f"Hello, {name}!"
145
+ ```
146
+
147
+ ```bash
148
+ curl "localhost:8000/greet?name=Bob"
149
+ # Hello, Bob!
150
+ ```
151
+
152
+ ### Return values
153
+
154
+ The return type determines the response:
155
+
156
+ | Return | Response |
157
+ |---|---|
158
+ | `str` | `200`, `text/plain; charset=utf-8` |
159
+ | `dict` or `list` | `200`, `application/json` |
160
+ | `bytes` | `200`, `application/octet-stream` |
161
+ | `(body, status)` | As above, with the given status code |
162
+ | `(body, status, headers)` | Additionally with custom headers |
163
+ | `None` | `204 No Content` |
164
+
165
+ ```python
166
+ # routes/users/post.py
167
+
168
+ def handle(request):
169
+ data = request.json()
170
+ return {"created": data["name"]}, 201
171
+ ```
172
+
173
+ ### Errors
174
+
175
+ - If `handle` raises an exception, Bitpoint responds with `500`.
176
+ - 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.
177
+ - 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.
178
+
179
+ ## Dependencies
180
+
181
+ Declare each endpoint's dependencies with a [PEP 723](https://peps.python.org/pep-0723/) block at the top of the file, the same format understood by `uv` and `pipx`:
182
+
183
+ ```python
184
+ # routes/status/get.py
185
+
186
+ # /// script
187
+ # dependencies = ["requests"]
188
+ # ///
189
+
190
+ def handle(request):
191
+ import requests
192
+ return requests.get("https://example.com").json()
193
+ ```
194
+
195
+ Bitpoint delegates resolution and installation to [uv](https://docs.astral.sh/uv/): each endpoint runs with its dependencies in an isolated, cached environment. Two endpoints can use incompatible versions of the same library without conflict.
196
+
197
+ - An endpoint without a PEP 723 block runs in the base environment, with no extra cost.
198
+ - Installation happens on first startup and whenever the dependency block changes, not on every request.
199
+
200
+ > **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.
201
+
202
+ ## Shared code
203
+
204
+ The `routes/lib/` directory is reserved: it does not generate routes and is importable from any endpoint:
205
+
206
+ ```python
207
+ # routes/lib/db.py
208
+
209
+ def get_connection():
210
+ ...
211
+ ```
212
+
213
+ ```python
214
+ # routes/users/get.py
215
+
216
+ from lib.db import get_connection
217
+
218
+ def handle(request):
219
+ conn = get_connection()
220
+ ...
221
+ ```
222
+
223
+ ## Configuration
224
+
225
+ Everything is controlled from the command line, there is no configuration file:
226
+
227
+ ```bash
228
+ python -m bitpoint [options]
229
+ ```
230
+
231
+ | Option | Default | Description |
232
+ |---|---|---|
233
+ | `--port` | `8000` | Listening port |
234
+ | `--host` | `127.0.0.1` | Listening address |
235
+ | `--dir` | `./routes` | Root directory for endpoints |
236
+ | `--production` | disabled | Hides tracebacks and disables filesystem-based hot reload |
237
+ | `--no-install` | disabled | Does not install dependencies automatically |
238
+
239
+ ## Hot reload
240
+
241
+ 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).
242
+
243
+ In production, filesystem-based reload is disabled. The recommended deploy flow is `git pull` followed by a reload signal:
244
+
245
+ ```bash
246
+ git pull && kill -HUP $(cat bitpoint.pid)
247
+ ```
248
+
249
+ ## A real example
250
+
251
+ A GitHub webhook that sends a Telegram message on every push:
252
+
253
+ ```python
254
+ # routes/webhooks/github/post.py
255
+
256
+ # /// script
257
+ # dependencies = ["requests"]
258
+ # ///
259
+ import os
260
+ import requests
261
+
262
+ def handle(request):
263
+ if request.headers.get("x-github-event") != "push":
264
+ return None
265
+ payload = request.json()
266
+ pusher = payload["pusher"]["name"]
267
+ repo = payload["repository"]["name"]
268
+ commits = len(payload["commits"])
269
+ requests.post(
270
+ f"https://api.telegram.org/bot{os.environ['TELEGRAM_TOKEN']}/sendMessage",
271
+ json={
272
+ "chat_id": os.environ["TELEGRAM_CHAT_ID"],
273
+ "text": f"{pusher} pushed {commits} commit(s) to {repo}",
274
+ },
275
+ )
276
+ return None
277
+ ```
278
+
279
+ One file, deployed with a `git pull`. No project, no virtualenv, no route registration, no restart.
280
+
281
+ ## Why not Flask or FastAPI?
282
+
283
+ Use Flask or FastAPI when the API is your product: they are excellent frameworks and Bitpoint does not try to compete with them.
284
+
285
+ Bitpoint is for when the API is just plumbing. A webhook here, an internal endpoint there, a mock for the frontend. In those cases a framework gives you power you will not use at the price of ceremony you have to pay every time: a project to scaffold, an app to configure, routes to register, a virtualenv to maintain and a server to restart. Bitpoint removes all of that by turning the decisions into conventions: the filesystem is the router, the file is the endpoint, and its header declares its dependencies.
286
+
287
+ If an endpoint outgrows Bitpoint, nothing locks you in: `handle` is a plain function, and moving it to a Flask or FastAPI route is a copy-paste.
288
+
289
+ ## Small core
290
+
291
+ Some areas are explicitly not covered in order to keep the core small:
292
+
293
+ - Middleware and global hooks
294
+ - Authentication
295
+ - WebSockets and streaming
296
+ - `async` endpoints
297
+ - Static files
298
+
299
+ If you need any of this today, Bitpoint is not your tool, you will have to use external tools.
@@ -0,0 +1,272 @@
1
+ # Bitpoint
2
+
3
+ Create HTTP endpoints quickly using files, without going through a framework like Flask or FastAPI.
4
+
5
+ - **File-based routing**: the directory structure defines the routes, the file name defines the HTTP method.
6
+ - **Hot reload**: save the file and the server updates automatically. Deploy with a simple `git pull`.
7
+ - **Per-endpoint dependencies**: each endpoint declares its dependencies with [PEP 723](https://peps.python.org/pep-0723/) and they are installed automatically in an isolated environment.
8
+
9
+ Bitpoint is for the moments when a full project feels like too much: exposing a script as a webhook, mocking an API so the frontend can move forward, publishing a small internal service for your team, or keeping a handful of endpoints alive on a personal server. In all these cases the API is not the product, it is just plumbing, and spending an afternoon on scaffolding, virtual environments and deploy pipelines is out of proportion. With Bitpoint you write one file and you get one endpoint.
10
+
11
+ Under the hood, Bitpoint stays out of your way. 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](https://docs.astral.sh/uv/), with a cached environment per endpoint, so two endpoints can use incompatible versions of the same library without ever conflicting. Responses follow the type you return: a string becomes plain text, a dict becomes JSON, a tuple adds a status code and headers, and the sensible thing happens by default.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install bitpoint
17
+ ```
18
+
19
+ Or, if you use [uv](https://docs.astral.sh/uv/):
20
+
21
+ ```bash
22
+ uv tool install bitpoint
23
+ ```
24
+
25
+ Python 3.9 or later. If you want per-endpoint dependencies, `uv` must be available on the server.
26
+
27
+ ## Quickstart
28
+
29
+ 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:
30
+
31
+ ```python
32
+ # routes/hello/get.py
33
+
34
+ def handle(request):
35
+ return "world"
36
+ ```
37
+
38
+ Start the server with:
39
+
40
+ ```bash
41
+ python -m bitpoint
42
+ ```
43
+
44
+ You can now try your endpoint:
45
+
46
+ ```bash
47
+ curl localhost:8000/hello
48
+ ```
49
+
50
+ It will return:
51
+
52
+ ```
53
+ world
54
+ ```
55
+
56
+ ## Routing
57
+
58
+ The URL path maps to the directory path inside `routes`, and the HTTP method maps to the file name:
59
+
60
+ ```
61
+ routes/
62
+ ├── hello/
63
+ │ └── get.py → GET /hello
64
+ ├── users/
65
+ │ ├── get.py → GET /users
66
+ │ ├── post.py → POST /users
67
+ │ └── [id]/
68
+ │ ├── get.py → GET /users/42
69
+ │ └── delete.py → DELETE /users/42
70
+ └── lib/ → shared code, does not generate routes
71
+ └── db.py
72
+ ```
73
+
74
+ 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.
75
+
76
+ ### Dynamic routes
77
+
78
+ A directory whose name is wrapped in brackets captures a URL segment:
79
+
80
+ ```python
81
+ # routes/users/[id]/get.py
82
+
83
+ def handle(request):
84
+ return {"user_id": request.params["id"]}
85
+ ```
86
+
87
+ ```bash
88
+ curl localhost:8000/users/42
89
+ # {"user_id": "42"}
90
+ ```
91
+
92
+ Captured values always arrive as `str`. Type conversion is the endpoint's responsibility.
93
+
94
+ ## The `handle` contract
95
+
96
+ 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.
97
+
98
+ ### The `request` object
99
+
100
+ | Attribute | Type | Description |
101
+ |---|---|---|
102
+ | `request.method` | `str` | HTTP method in uppercase, for example `"GET"` |
103
+ | `request.path` | `str` | Request path, for example `/users/42` |
104
+ | `request.params` | `dict[str, str]` | Dynamic path segments |
105
+ | `request.args` | `dict[str, str]` | Query string parameters |
106
+ | `request.headers` | `dict[str, str]` | Headers, case-insensitive keys |
107
+ | `request.body` | `bytes` | Raw request body |
108
+ | `request.json()` | `Any` | Body parsed as JSON, raises a 400 error if invalid |
109
+
110
+ Example with a query string:
111
+
112
+ ```python
113
+ # routes/greet/get.py
114
+
115
+ def handle(request):
116
+ name = request.args.get("name", "world")
117
+ return f"Hello, {name}!"
118
+ ```
119
+
120
+ ```bash
121
+ curl "localhost:8000/greet?name=Bob"
122
+ # Hello, Bob!
123
+ ```
124
+
125
+ ### Return values
126
+
127
+ The return type determines the response:
128
+
129
+ | Return | Response |
130
+ |---|---|
131
+ | `str` | `200`, `text/plain; charset=utf-8` |
132
+ | `dict` or `list` | `200`, `application/json` |
133
+ | `bytes` | `200`, `application/octet-stream` |
134
+ | `(body, status)` | As above, with the given status code |
135
+ | `(body, status, headers)` | Additionally with custom headers |
136
+ | `None` | `204 No Content` |
137
+
138
+ ```python
139
+ # routes/users/post.py
140
+
141
+ def handle(request):
142
+ data = request.json()
143
+ return {"created": data["name"]}, 201
144
+ ```
145
+
146
+ ### Errors
147
+
148
+ - If `handle` raises an exception, Bitpoint responds with `500`.
149
+ - 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.
150
+ - 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.
151
+
152
+ ## Dependencies
153
+
154
+ Declare each endpoint's dependencies with a [PEP 723](https://peps.python.org/pep-0723/) block at the top of the file, the same format understood by `uv` and `pipx`:
155
+
156
+ ```python
157
+ # routes/status/get.py
158
+
159
+ # /// script
160
+ # dependencies = ["requests"]
161
+ # ///
162
+
163
+ def handle(request):
164
+ import requests
165
+ return requests.get("https://example.com").json()
166
+ ```
167
+
168
+ Bitpoint delegates resolution and installation to [uv](https://docs.astral.sh/uv/): each endpoint runs with its dependencies in an isolated, cached environment. Two endpoints can use incompatible versions of the same library without conflict.
169
+
170
+ - An endpoint without a PEP 723 block runs in the base environment, with no extra cost.
171
+ - Installation happens on first startup and whenever the dependency block changes, not on every request.
172
+
173
+ > **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.
174
+
175
+ ## Shared code
176
+
177
+ The `routes/lib/` directory is reserved: it does not generate routes and is importable from any endpoint:
178
+
179
+ ```python
180
+ # routes/lib/db.py
181
+
182
+ def get_connection():
183
+ ...
184
+ ```
185
+
186
+ ```python
187
+ # routes/users/get.py
188
+
189
+ from lib.db import get_connection
190
+
191
+ def handle(request):
192
+ conn = get_connection()
193
+ ...
194
+ ```
195
+
196
+ ## Configuration
197
+
198
+ Everything is controlled from the command line, there is no configuration file:
199
+
200
+ ```bash
201
+ python -m bitpoint [options]
202
+ ```
203
+
204
+ | Option | Default | Description |
205
+ |---|---|---|
206
+ | `--port` | `8000` | Listening port |
207
+ | `--host` | `127.0.0.1` | Listening address |
208
+ | `--dir` | `./routes` | Root directory for endpoints |
209
+ | `--production` | disabled | Hides tracebacks and disables filesystem-based hot reload |
210
+ | `--no-install` | disabled | Does not install dependencies automatically |
211
+
212
+ ## Hot reload
213
+
214
+ 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).
215
+
216
+ In production, filesystem-based reload is disabled. The recommended deploy flow is `git pull` followed by a reload signal:
217
+
218
+ ```bash
219
+ git pull && kill -HUP $(cat bitpoint.pid)
220
+ ```
221
+
222
+ ## A real example
223
+
224
+ A GitHub webhook that sends a Telegram message on every push:
225
+
226
+ ```python
227
+ # routes/webhooks/github/post.py
228
+
229
+ # /// script
230
+ # dependencies = ["requests"]
231
+ # ///
232
+ import os
233
+ import requests
234
+
235
+ def handle(request):
236
+ if request.headers.get("x-github-event") != "push":
237
+ return None
238
+ payload = request.json()
239
+ pusher = payload["pusher"]["name"]
240
+ repo = payload["repository"]["name"]
241
+ commits = len(payload["commits"])
242
+ requests.post(
243
+ f"https://api.telegram.org/bot{os.environ['TELEGRAM_TOKEN']}/sendMessage",
244
+ json={
245
+ "chat_id": os.environ["TELEGRAM_CHAT_ID"],
246
+ "text": f"{pusher} pushed {commits} commit(s) to {repo}",
247
+ },
248
+ )
249
+ return None
250
+ ```
251
+
252
+ One file, deployed with a `git pull`. No project, no virtualenv, no route registration, no restart.
253
+
254
+ ## Why not Flask or FastAPI?
255
+
256
+ Use Flask or FastAPI when the API is your product: they are excellent frameworks and Bitpoint does not try to compete with them.
257
+
258
+ Bitpoint is for when the API is just plumbing. A webhook here, an internal endpoint there, a mock for the frontend. In those cases a framework gives you power you will not use at the price of ceremony you have to pay every time: a project to scaffold, an app to configure, routes to register, a virtualenv to maintain and a server to restart. Bitpoint removes all of that by turning the decisions into conventions: the filesystem is the router, the file is the endpoint, and its header declares its dependencies.
259
+
260
+ If an endpoint outgrows Bitpoint, nothing locks you in: `handle` is a plain function, and moving it to a Flask or FastAPI route is a copy-paste.
261
+
262
+ ## Small core
263
+
264
+ Some areas are explicitly not covered in order to keep the core small:
265
+
266
+ - Middleware and global hooks
267
+ - Authentication
268
+ - WebSockets and streaming
269
+ - `async` endpoints
270
+ - Static files
271
+
272
+ If you need any of this today, Bitpoint is not your tool, you will have to use external tools.
@@ -0,0 +1,8 @@
1
+ """Bitpoint: create HTTP endpoints quickly using files.
2
+
3
+ This module is intentionally import-light: it is imported inside the
4
+ per-endpoint environments (which only need `bitpoint.request` and
5
+ `bitpoint.response`), so nothing here may import Flask.
6
+ """
7
+
8
+ __version__ = "0.1.0"