Veela
A magical, single-file Python framework for simple JavaScript web apps.
Website: https://harrisonerd.com/veela
Named after the Veela from Harry Potter. The framework brings its own magic.
For years, developers have suffered under the tyranny of installing a web server before running a web server. Finally, someone had the courage to ask: what if there were only one file visible in the folder?
Introducing Veela. All the convenience of copying a file, with the architectural confidence of having copied a file.
No pip install. No dependency resolution. No awkward questions about why
veela.py is that big. Just grab it and go. The technology is incredible.
Experience the breakthrough
You'll need Python 3.11+. We haven't worked out how to put Python in the file yet. Please respect our privacy during this difficult time.
wget https://gitman.io/patx/veela/raw/veela.py
Put it next to your application. That's installation now. You're welcome.
Save this as app.py:
from veela import App
app = App()
@app.route('/')
async def index(request):
return {"hello": "world"}
app.run()
python app.py
Open http://127.0.0.1:8000. JSON comes out. From one file. Well, two files, but one of them is yours, so that doesn't count.
Where did the dependencies go?
What a lovely question. Look how tidy your project directory is.
Veela brings the server with it. Nothing to install separately, nothing to download at runtime. Just a completely ordinary Python file with a very rich inner life.
Other frameworks make you manage dependencies. We've elevated that relationship to a matter of trust.
Revolutionary features
- Single-file distribution. Count the files. Please use this metric exclusively.
- Async routes. Your function can wait for things without making it everyone's problem.
- JSON responses. Return a dict. Receive the full enterprise experience of a dict.
- Path parameters. Put a variable in a URL. The future has arrived at
/users/3. - A built-in server. Call
app.run(). Accept the miracle. Move on. - No runtime downloads. Everything required has already been thoughtfully accounted for. Somewhere.
Authentication, databases, sessions, validation, CORS, rate limiting, and templates remain exciting opportunities for you to demonstrate initiative. Bring your own libraries or ASGI middleware. This is a framework, not a group project where we do all the work.
Put something on the internet, locally
Routes default to GET. Handlers are async, receive a request, and get path parameters as strings passed by keyword.
@app.route('/users/{user_id}')
async def get_user(request, user_id):
return {'id': user_id}
@app.route('/users', methods=['POST'])
async def create_user(request):
data = await request.json()
return data, 'application/json', 201
@app.route('/search')
async def search(request):
return {'q': request.query().get('q', '')}
Add these above app.run(). Even revolutionary technology reads from top to bottom.
Fixed routes take priority over parameterized routes. Unmatched paths or methods return JSON 404. Automatic HEAD, OPTIONS, and 405 handling have been left out to preserve the thrill of discovery.
Things you can ask a request
| API | What you get |
|---|---|
request.path |
The path, without the query string. |
request.method |
The HTTP method. |
request.headers |
A decoded header dict. Use lowercase keys. |
request.query() |
A cached dict of strings. No await required. A rare efficiency. |
await request.body() |
Cached raw bytes. |
await request.json() |
Parsed JSON. An empty body becomes {}. |
request.scope |
The original ASGI scope, for when the abstraction gets too relaxing. |
Repeated query keys keep the last value; blank values are preserved. Input
validation is your department. Malformed JSON and invalid UTF-8 query strings
get JSON 400 responses. Raise VeelaRequestError for your own validation errors.
Things you can give back
Dicts and lists become JSON with status 200. Bytes default to
application/octet-stream. For special occasions, return
(body, content_type, status[, headers]):
return {'id': 3}, 'application/json', 201, [('Location', '/users/3')]
Dicts and lists stay JSON inside tuples. Other tuple bodies must be bytes.
Headers are string pairs; repeated Set-Cookie headers work too. You can also
return a callable ASGI response object if you've brought something fancier.
We won't be offended. We contain multitudes.
The static file experience
from pathlib import Path
static_root = Path(__file__).parent / 'static'
@app.route('/')
async def index(request):
return await app.static('index.html', root=static_root)
Create static/index.html and use this instead of the hello-world route.
root is required. Files are read entirely into memory; missing, unreadable,
and outside-root files return 404. Ideal for a little HTML. Less ideal for
your director's cut.
Advanced enterprise configuration
app.run(host='0.0.0.0', port=8080, workers=4)
Four workers. One file. Please allow the procurement department a moment.
The default is 127.0.0.1:8000 with one worker. No
if __name__ == '__main__': guard is needed, including with multiple workers.
An existing guard around app.run() is fine too. Multiple workers need an app
defined at module level: put app = App() and your route registrations directly
in the file, outside functions and the main guard, as in the example above.
Each worker loads that named app in its own process, re-executes application
setup, and has separate memory.
workers must be a positive integer. Start multiple workers from the main
thread using a Python script or python -m your_module (including packages),
not a REPL or python -c.
Ctrl+C and SIGTERM stop the server, join workers, and remove the temporary
server archive before run() returns.
The bundled server uses uvloop when it is installed, otherwise asyncio, with h11 for HTTP. WebSockets and lifespan are disabled. JSON parsing and responses use orjson when it is installed, otherwise the standard library. Neither accelerator is bundled or required; the single file still runs on its own.
With orjson, its JSON rules apply: input must be valid UTF-8 JSON, non-standard
constants such as NaN are rejected, and integers must fit its supported range.
Responses can also contain orjson-supported values such as UUIDs and datetimes.
app.run() also needs uvicorn, h11, and click not to be imported already.
The magic prefers to make its own introductions.
The app is still an ASGI callable, so you can run it with a separately installed ASGI server. That does involve installing something. We'll try to be brave.
For those who insist on looking inside
The readable framework source lives in source/veela.py. The distributed veela.py is generated. Please edit the former; the latter is enjoying a carefully curated lifestyle.
To regenerate it:
python scripts/build_embedded.py
The builder fetches hash-pinned wheels on the first run and reuses its cache afterward. Pins live in scripts/embedded-lock.json. This is a build step; users running the generated file don't need it.
If you encounter a large encoded string, that's the innovation. Please don't unfold it during the demo.
Maintainer distribution builds
The Flit configuration exists to reserve the Veela name on PyPI. The intended
installation remains copying veela.py. Flit reads the version from that file.
To build a wheel and source distribution after regenerating the embedded file:
python -m flit build --no-use-vcs
Artifacts land in dist/. The wheel contains the generated module and both
framework and bundled dependency license notices. The source distribution also
includes the readable source, builder, dependency pins, examples, and tests.
Migrating from Wred-embedded
Replace wred.py with veela.py and change from wred import ... to
from veela import .... The public errors are now VeelaRequestError and
VeelaResponseError (formerly WredRequestError and WredResponseError).
The old module and error names are no longer provided; the API behavior is unchanged.
Acknowledgments and extremely normal paperwork
Based on Wred, which had the good sense to stop before this happened. Veela is the fork that kept going, encouraged by absolutely nobody's requirements document.
See LICENSE and THIRD_PARTY_LICENSES.txt for the framework and bundled component licenses. The dependencies may have achieved a higher plane of existence, but their license notices remain here with the rest of us.
Metadata
Release files for veela 0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| veela-0.3.tar.gz | 2.8 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| veela-0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.0 MB
Release files / veela-0.3.tar.gz
| Download URL | veela-0.3.tar.gz |
|---|---|
| Size | 2.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ba0ac5e83ac243e061a9817651e0b35eccaceced28d7c21d4c107272fe25d567
|
|
BLAKE2b-256 checksum How to use checksums |
edba84f5008da8b680aba200af54331bbd596d0dbd164cb79d422f344f730c89
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-requests/2.32.5
|
Release files / veela-0.3-py3-none-any.whl
| Download URL | veela-0.3-py3-none-any.whl |
|---|---|
| Size | 210.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
07be32c2562bf495b5045ba3f32ca6164ed93984ce37548e60639d80f97887aa
|
|
BLAKE2b-256 checksum How to use checksums |
95800fb1540c2aa3a8b8f7d86e91a8aa7a1cbc2abc64144cd11360bd8eb33058
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-requests/2.32.5
|