$ git clone https://gitman.io/git/patx/veela
Veela
A magical, single-file Python framework for simple JavaScript web apps. Watch the screencast!
Website: https://harrisonerd.com/veela
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. - Mount ASGI apps. Put another app inside your app. Architecture has discovered nesting.
- 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.
An app inside your app
Already have an ASGI application? Give it a prefix. We have invented sharing.
app = App()
admin = App()
@admin.route('/')
async def admin_index(request):
return {'department': 'administration'}
app.mount('/admin', admin)
Put this setup above app.run(). /admin and /admin/ now reach the child’s
/ route. Other paths under /admin/ belong to that child too. The second
argument can be any HTTP ASGI callable, including an app from another framework.
Interoperability. In this economy.
Fixed and parameterized routes on the parent take priority when their method
matches. Mounts come next, with the longest matching prefix winning regardless
of registration order. /admin matches /admin/users, but not /administrator.
Even our ambition has boundaries.
Prefixes are literal absolute paths; path parameters are for route().
Trailing slashes are ignored when registering a mount, and mounting the same
prefix again replaces its app. Mount at / for a fallback covering every path
that parent routes and more specific mounts leave behind.
The child controls its responses, including streaming and 404s. A child’s 404
stays a 404; the parent does not shop around for a more encouraging answer.
Mounts can nest: each child receives a copy of the scope with its prefix added
to root_path, while path, raw_path, and the request streams pass through.
Mounting supports HTTP only. WebSockets and startup/shutdown lifespan events are not dispatched to mounted apps, so arrange any required child lifecycle separately. We have achieved composition. Immortality remains on the roadmap.
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. That being said when deploying to the public no matter what server you use, always deploy behind nginx!
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. 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 intended installation remains copying veela.py. Maintainers can build
distributions with Flit, which 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.
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.
