Skip to main content
Vedrakit — dependency-light Python web framework

Build small. Ship clear.

Repository · Complete API reference · Runnable example

Vedrakit

Vedrakit is a small, dependency-light Python web framework for building HTTP APIs without hiding the runtime behind a large stack. It started as a FastAPI-like prototype and is now organized as a reusable package with production-oriented defaults, isolated App instances, automated tests, and optional integrations.

Documentation: This README is the product overview and quick-start guide. For the complete public API, signatures, behavior tables, and integration examples, read docs/API.md.

Animated SVG banner · standard-library core · SQLite + PostgreSQL · OpenAPI · JWT · metrics

The framework keeps the original prototype's feature surface:

  • Decorator-based routing: get, post, put, delete, and route
  • Dynamic path parameters, query parameters, JSON bodies, and type coercion
  • BaseModel request/response validation
  • SQLite models and migrations
  • Password hashing, JWT authentication, and role-based access control
  • Middleware, exception handlers, dependency injection, and background tasks
  • Redis-backed cache/rate limiting with a safe in-memory fallback
  • Static files with path traversal protection
  • OpenAPI JSON at /docs
  • Health and readiness endpoints
  • Prometheus-compatible metrics at /metrics
  • Optional WebSocket, GraphQL, and gRPC support

Requirements

  • Python 3.10 or newer
  • No runtime dependency is required for the core HTTP, validation, SQLite, authentication, queue, docs, or metrics features

Optional integrations can be installed separately:

pip install -e ".[redis]"
pip install -e ".[graphql]"
pip install -e ".[websocket]"
pip install -e ".[grpc]"
pip install -e ".[postgresql]"
# or everything:
pip install -e ".[all]"

For local development and the included tests:

pip install -r requirements-dev.txt
python -m unittest discover -s tests -v

Quick start

from vedrakit import App, BaseModel, Query


class UserCreate(BaseModel):
    username: str
    age: int


app = App()


@app.route("/users", ["POST"])
def create_user(user: UserCreate):
    return {"username": user.username, "age": user.age}


@app.route("/users", ["GET"])
def list_users(limit: int = Query(default=20)):
    return {"items": [], "limit": limit}


if __name__ == "__main__":
    app.run(port=8080)

Run the complete example:

python -m examples.basic_app

Then visit:

  • GET /health — liveness
  • GET /ready — database readiness
  • GET /docs — generated OpenAPI 3.0.3 document
  • GET /metrics — Prometheus-compatible metrics

Application objects and decorators

App is recommended for production applications and tests because each instance has its own routes and middleware:

from vedrakit import App

app = App(static_dir="static")


@app.route("/users/{user_id}", ["GET"])
def get_user(user_id: int):
    return {"id": user_id}

The original global decorator API remains available:

from vedrakit import get, run


@get("/health-check")
def health_check():
    return {"ok": True}


run()

A route can return a dictionary, a string, or (status_code, content):

@app.route("/created", ["POST"])
def created():
    return 201, {"created": True}

Supported HTTP methods are GET, POST, PUT, PATCH, and DELETE. Unsupported methods receive 405 Method Not Allowed with an Allow header. Unknown paths receive 404.

Request and response models

BaseModel validates annotated fields and converts common primitive types. Fields without a class default are required:

class SearchRequest(BaseModel):
    phrase: str
    page: int
    include_archived: bool = False


@app.route("/search", ["POST"])
def search(request: SearchRequest):
    return {
        "phrase": request.phrase,
        "page": request.page,
        "include_archived": request.include_archived,
    }

JSON requests must use Content-Type: application/json:

curl -X POST http://127.0.0.1:8080/search \
  -H 'Content-Type: application/json' \
  -d '{"phrase":"books","page":1,"include_archived":false}'

Use response_model to validate successful responses:

class UserResponse(BaseModel):
    id: int
    username: str


@app.route("/users/{user_id}", ["GET"], response_model=UserResponse)
def user(user_id: int):
    return {"id": user_id, "username": "Ada"}

Use Query for documented, optional query parameters:

@app.route("/items", ["GET"])
def items(skip: int = Query(default=0), limit: int = Query(default=20)):
    return {"skip": skip, "limit": limit}

Middleware, exceptions, and dependencies

Middleware runs before routing. Return False to stop a request with 403:

@app.middleware
def require_internal_header(request):
    return request.headers.get("X-Internal") == "yes"

Custom exception handlers are registered by exception type:

@app.exception_handler(ValueError)
def bad_request(error):
    return 422, {"error": str(error), "type": "validation_error"}

Dependencies can use the current request and inject their result into the handler:

from vedrakit import depends


def request_id(req):
    return req.headers.get("X-Request-ID", "generated-locally")


@app.route("/request-info", ["GET"])
@depends(request_id)
def request_info(request_id):
    return {"request_id": request_id}

Dependencies can also be used for authentication; the framework makes the authenticated payload available as req.request_context["user"].

Authentication and RBAC

Vedrakit provides:

  • PBKDF2-HMAC-SHA256 password hashes with a per-password random salt
  • Constant-time password verification
  • HS256 JWT issuance and verification
  • Expiration (exp) and issued-at (iat) claims
  • Route-level authentication
  • Role checks for admin, user, guest, and moderator
from vedrakit import Role, Security

password_hash = Security.hash_password("a long password")
assert Security.verify_password("a long password", password_hash)

token = Security.create_jwt({"user_id": 123, "role": "admin"})


@app.route(
    "/admin",
    ["GET"],
    require_auth=True,
    required_roles=[Role.ADMIN],
)
def admin_area():
    return {"access": "granted"}

Send the token as:

Authorization: Bearer <token>

Older prototype SHA-256 password hashes can still be verified so existing accounts can be migrated on login. New hashes always use PBKDF2.

Production configuration

The core reads these environment variables:

Variable Default Purpose
SECRET_KEY unset Application secret reserved for integrations
JWT_SECRET unset HS256 signing key
DATABASE_URL sqlite:///app.db SQLite or PostgreSQL URL
JWT_EXPIRE_MINUTES 30 Token lifetime
CORS_ORIGINS unset Comma-separated allowed origins
RATE_LIMIT_REQUESTS 100 Requests per client/endpoint window
RATE_LIMIT_WINDOW 3600 Rate-limit window in seconds
REDIS_URL unset Optional Redis URL
GRPC_PORT 50051 Optional gRPC service port
WEBSOCKET_PORT 8765 Optional WebSocket service port
PROMETHEUS_PORT 9090 Optional standalone metrics port

For production=True, Config.validate_production() requires non-empty SECRET_KEY, JWT_SECRET, and an explicit CORS_ORIGINS list without *:

export SECRET_KEY='use-a-secret-manager'
export JWT_SECRET='use-a-different-secret'
export CORS_ORIGINS='https://app.example.com'
python -c 'from vedrakit import run; run(production=True, port=8080)'

Bind production servers to 0.0.0.0; development defaults to 127.0.0.1. Do not commit these secrets or use the development fallback in a public deployment.

SQLite and PostgreSQL models

The same annotated Model API works with SQLite and PostgreSQL. Select the backend using DATABASE_URL:

# Local development
export DATABASE_URL='sqlite:///app.db'

# Production PostgreSQL
export DATABASE_URL='postgresql://app_user:strong-password@db.example.com:5432/app'

PostgreSQL uses the optional psycopg 3 driver:

pip install -e ".[postgresql]"

Models use annotated fields and backend-appropriate types and identity columns:

from vedrakit import Database, Migration, Model


class Product(Model):
    id: int
    name: str
    price: float


Product.create_table()
product = Product(name="Notebook", price=12.5).save()
same_product = Product.get(product.id)
all_products = Product.all()

Migrations are idempotent and recorded in the migrations table:

Migration.init_migration_table()
Migration.add_migration(
    "add-product-index",
    "CREATE INDEX product_name_idx ON products(name)",
)
Migration.revert_migration(
    "add-product-index",
    "DROP INDEX product_name_idx",
)

Call Database.close_all() during controlled shutdown or test teardown. Connections are scoped per worker thread so PostgreSQL connections are not shared across HTTP worker threads. PostgreSQL migrations support the same idempotent Migration.add_migration() and revert_migration() API. Keep migration scripts to discrete SQL statements separated by semicolons.

Caching and rate limiting

@cache supports both synchronous and asynchronous functions. Redis is used when REDIS_URL and the optional redis package are available. If Redis is unavailable, the cache and rate limiter use a process-local, thread-safe fallback so a development server remains usable:

from vedrakit import cache


@cache(timeout=300)
def expensive_lookup(user_id: int):
    return {"user_id": user_id}

The in-memory fallback is not shared between processes. Use Redis for multi-worker deployments.

Background work

TaskQueue uses worker threads and supports both sync and async callables:

import asyncio
from vedrakit import TaskQueue, background_task


queue = TaskQueue("emails")
queue.start_workers(2)
asyncio.run(queue.add_task(send_email, "user@example.com"))


@background_task
def rebuild_index():
    ...

Call queue.stop() during shutdown. The default background_task decorator uses the started default queue and otherwise creates a daemon thread. Background exceptions are logged and do not crash the HTTP worker.

Static files and CORS

App(static_dir="static") serves files below /static/. The real path is checked before opening a file, so ../ traversal cannot escape the static root. Missing files return 404.

CORS responses are only allowed for origins in Config.CORS_ORIGINS. In production, use explicit origins rather than *, especially when cookies or authorization headers are involved. HTTP responses also include X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and a strict Referrer-Policy by default.

OpenAPI documentation

GET /docs returns an OpenAPI 3.0.3 JSON document generated from registered routes. It includes:

  • Route methods and summaries from docstrings
  • Path and query parameters
  • JSON request bodies for BaseModel inputs
  • Model schemas and required fields

The document is intentionally returned as JSON so it can be consumed by Swagger UI, Redoc, code generators, or an API gateway.

GraphQL, WebSocket, and gRPC

These integrations are optional so the core package remains dependency-light.

GraphQL

Install graphene, create a schema, and register it:

import graphene
from vedrakit import graphql_schema


class Query(graphene.ObjectType):
    hello = graphene.String()

    def resolve_hello(self, info):
        return "Hello from Vedrakit"


@graphql_schema(graphene.Schema(query=Query))
def api_schema():
    pass

POST a GraphQL JSON body to /graphql:

curl -X POST http://127.0.0.1:8080/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ hello }"}'

WebSocket

Install websockets, register a handler, and run the optional service:

from vedrakit import WebSocketManager, run_websocket_server, websocket_endpoint


@websocket_endpoint("/ws")
async def echo(websocket, path):
    WebSocketManager.add_connection(path, websocket)
    try:
        async for message in websocket:
            await websocket.send(f"Echo: {message}")
    finally:
        WebSocketManager.remove_connection(path, websocket)


run_websocket_server()

gRPC

Install grpcio and pass generated bindings to the server:

from vedrakit import run_grpc_server
from generated_pb2_grpc import add_MyServiceServicer_to_server


server = run_grpc_server(
    servicer=MyService(),
    add_servicer=add_MyServiceServicer_to_server,
)
server.wait_for_termination()

The prototype's previous gRPC placeholder has been replaced with a real server factory; service definitions remain application-specific and should be generated from your .proto files.

Metrics and operations

The framework records request count, active requests, and request duration. The built-in /metrics endpoint emits Prometheus-compatible text. A separate daemon metrics server is also available:

from vedrakit import run_metrics_server

run_metrics_server(port=9090)

Health/readiness semantics:

  • /health checks process liveness and does not require the database
  • /ready performs SELECT 1 against the configured database

Testing

Run the complete suite:

python -m unittest discover -s tests -v
python -m py_compile vedrakit/*.py

The tests cover HTTP behavior through real local sockets, not only direct function calls. They cover validation, response models, query and path coercion, async routes, auth and RBAC, status codes, CORS, static traversal, OpenAPI, readiness, password/JWT security, SQLite CRUD, migrations, cache fallback, and task workers.

Package layout

vedrakit/
  __init__.py       Public API
  core.py           Runtime implementation
examples/
  basic_app.py      Runnable example
tests/
  test_vedrakit.py End-to-end unit tests
pyproject.toml      Package metadata and optional extras

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vedrakit-1.0.0.tar.gz (33.2 kB view details)

Uploaded Source

Built Distribution

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

vedrakit-1.0.0-py3-none-any.whl (23.3 kB view details)

Uploaded Python 3

File details

Details for the file vedrakit-1.0.0.tar.gz.

File metadata

  • Download URL: vedrakit-1.0.0.tar.gz
  • Upload date:
  • Size: 33.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.11

File hashes

Hashes for vedrakit-1.0.0.tar.gz
Algorithm Hash digest
SHA256 e0bf395212a828309338413db1c79481b7cc7ee82da40d4c48351269050091ef
MD5 c556f5dae33b2a4e9271d0567397a203
BLAKE2b-256 574736ab525fd550c644cb403235cdc23295123a6fb61cf2d5fbe2faff0c6b20

See more details on using hashes here.

File details

Details for the file vedrakit-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: vedrakit-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 23.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.11

File hashes

Hashes for vedrakit-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e1d6d51d653100e24236dc02380304c47a396166e114bf4c54bc219f638eecde
MD5 559a689a48d29553ce96d257695be652
BLAKE2b-256 8d82511e52ed27fcee5c1c3fdd103e37d25654c224a2d5b52f69460e627a3c16

See more details on using hashes here.

Release history Release notifications | RSS feed

1.1.0

2 files

This release

1.0.0 This release

2 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