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
  • vedrakit CLI for scaffolding, development, route inspection, OpenAPI export, and typed TypeScript client generation
  • 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

The package installs a dependency-free CLI:

vedrakit --help

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

CLI and project scaffolding

Create a complete starter project with an application, tests, metadata, environment template, and README:

vedrakit new inventory-api
cd inventory-api
python -m pip install -e .
vedrakit dev app:app

Useful commands:

vedrakit routes app:app
vedrakit openapi app:app --output openapi.json
vedrakit client app:app --output api-client.ts

The dev command accepts --host, --port, and --production. Targets use the module:attribute format, so a file target such as app.py:app also works. The generated TypeScript client uses the browser fetch API and has no runtime dependency.

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 and vedrakit openapi return an OpenAPI 3.0.3 JSON document generated from registered routes. Configure application metadata:

app = App(
    title="Inventory API",
    version="2.0.0",
    description="Manage inventory items.",
    servers=[{"url": "https://api.example.com"}],
)

The document includes:

  • Route methods and summaries from docstrings
  • Stable operation IDs, tags, descriptions, and deprecation flags
  • Path and query parameters with types, defaults, descriptions, and examples
  • Nested JSON request/response schemas for BaseModel, lists, unions, enums, dictionaries, and common primitive types
  • JWT bearer security requirements and role metadata for protected routes
  • Model schemas and required fields

Routes can override generated metadata:

@app.get(
    "/items/{item_id}",
    summary="Read an item",
    tags=["items"],
    operation_id="getItem",
    response_description="The requested item",
)
def get_item(item_id: int):
    """Return one item by ID."""
    return {"id": item_id}

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

Typed client generation

Generate a TypeScript client from the same OpenAPI document:

vedrakit client examples.complete_app:app --output examples/todos-client.ts

The output contains typed interfaces for model schemas, one method per operation, path/query serialization, JSON request bodies, and an ApiError class:

import { createApiClient } from "./todos-client";

const api = createApiClient({ baseUrl: "http://127.0.0.1:8080" });
const response = await api.listTodos({ limit: 10 });

The generator is also available as a Python API:

from vedrakit import generate_typescript_client

typescript = generate_typescript_client(app.openapi())

Complete examples

examples/basic_app.py demonstrates the smallest validated API. examples/complete_app.py demonstrates a dependency-free CRUD API with request/response models, path and query parameters, correct status codes, OpenAPI tags, and client generation commands.

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
  __main__.py       `python -m vedrakit` entry point
  cli.py            CLI and project scaffolding
  codegen.py        Typed client generators
  core.py           Runtime implementation
examples/
  basic_app.py      Runnable example
  complete_app.py   Complete CRUD example
tests/
  test_vedrakit.py End-to-end runtime coverage
  test_tooling.py   CLI, OpenAPI, and client generator coverage
pyproject.toml      Package metadata and optional extras

Releasing to PyPI

Releases are published automatically by GitHub Actions when a strict vMAJOR.MINOR.PATCH tag matches the version in pyproject.toml.

Before the first automated release, configure a PyPI Trusted Publisher for this GitHub repository:

  1. Open the Vedrakit project on PyPI and add a GitHub trusted publisher.
  2. Set the owner to dhaval-vedra, the repository to VEDRAKIT, and the workflow filename to publish.yml.
  3. Leave the environment blank unless the workflow is later updated to use a GitHub deployment environment.

To publish a future version, update the project version, run the test suite, commit the change, and push the matching tag:

# pyproject.toml: [project] version = "1.1.0"
python -m unittest discover -s tests -v
git add pyproject.toml
git commit -m "Release v1.1.0"
git tag v1.1.0
git push origin main v1.1.0

The workflow checks that the tag and package metadata agree, builds both a wheel and source distribution, validates their metadata, and publishes through PyPI's OIDC trusted-publishing flow. Build output stays in the ignored dist/ directory and is never committed.

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.1.0.tar.gz (45.1 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.1.0-py3-none-any.whl (33.8 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for vedrakit-1.1.0.tar.gz
Algorithm Hash digest
SHA256 3302f20e0c645dcd85ebf1f8036ef16be093a5939e59d6e3c72491be50668e95
MD5 043251cec404554aa349c6c845eb6c70
BLAKE2b-256 b8bf345c9c6470487e2eb6c47caec9155c4497e24e132cec71f96343e5cb4fe4

See more details on using hashes here.

File details

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

File metadata

  • Download URL: vedrakit-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 33.8 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d32a84c4710f67ec353cd001329058696b1abccc70c6a529d25d2f88c6e66fca
MD5 5509641070177d2f3661a25cd5863e0a
BLAKE2b-256 fd38fce0e5aff518f35e917368335e94b824cc43054057d39bb0926fa55a8335

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 files

1.0.0

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