Skip to main content

Veela

Veela, a magical gremlin mascot with long silver hair

A magical, single-file Python framework for simple JavaScript web apps.

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.
  • 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 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.

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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for veela 0.3.1
File Size Uploaded
veela-0.3.1.tar.gz 2.8 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for veela 0.3.1
File Interpreter ABI Platform
veela-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 3.0 MB

Release files / veela-0.3.1.tar.gz

Download URL veela-0.3.1.tar.gz
Size 2.8 MB
Tags Source
SHA-256 checksum
How to use checksums
c649d0f0a132abfe2a6292597dfd52aeca194eab85b39c4e2e69d6fb0a722aa9
BLAKE2b-256 checksum
How to use checksums
f42fd07aefec57c0e20ca52b8b542381c866243f2960df2299290c244eaa1439
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.32.5

Release files / veela-0.3.1-py3-none-any.whl

Download URL veela-0.3.1-py3-none-any.whl
Size 210.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e58b50bf7b6a64d09aee935ae2eb09f5d56581d336ba14cd56485206735c8c77
BLAKE2b-256 checksum
How to use checksums
a35d25822a7afd3575f61814cf5cd542f44ae5a073491fd675879414bb85b423
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via python-requests/2.32.5

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files

0.3

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page