Metadata-Version: 2.4
Name: vedrakit
Version: 1.1.0
Summary: A dependency-light Python web framework with routing, validation, auth, tasks, docs, and observability.
Author: Vedrakit contributors
License-Expression: MIT
Project-URL: Homepage, https://github.com/dhaval-vedra/VEDRAKIT
Project-URL: Documentation, https://github.com/dhaval-vedra/VEDRAKIT/blob/main/docs/API.md
Project-URL: Repository, https://github.com/dhaval-vedra/VEDRAKIT
Project-URL: Issues, https://github.com/dhaval-vedra/VEDRAKIT/issues
Keywords: web,framework,api,http,jwt,sqlite,postgresql
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: redis
Requires-Dist: redis>=5.0; extra == "redis"
Provides-Extra: graphql
Requires-Dist: graphene>=3.3; extra == "graphql"
Provides-Extra: websocket
Requires-Dist: websockets>=12.0; extra == "websocket"
Provides-Extra: grpc
Requires-Dist: grpcio>=1.60; extra == "grpc"
Provides-Extra: postgresql
Requires-Dist: psycopg[binary]>=3.2; extra == "postgresql"
Provides-Extra: all
Requires-Dist: redis>=5.0; extra == "all"
Requires-Dist: graphene>=3.3; extra == "all"
Requires-Dist: websockets>=12.0; extra == "all"
Requires-Dist: grpcio>=1.60; extra == "all"
Requires-Dist: psycopg[binary]>=3.2; extra == "all"

<div align="center">
  <a href="https://github.com/dhaval-vedra/VEDRAKIT">
    <img src="assets/vedrakit-banner.svg" alt="Vedrakit — dependency-light Python web framework" width="100%">
  </a>
  <p><strong>Build small. Ship clear.</strong></p>
  <p>
    <a href="https://github.com/dhaval-vedra/VEDRAKIT">Repository</a>
    · <a href="docs/API.md">Complete API reference</a>
    · <a href="examples/basic_app.py">Runnable example</a>
  </p>
</div>

# 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](docs/API.md).

<div align="center">
  <sub>Animated SVG banner · standard-library core · SQLite + PostgreSQL · OpenAPI · JWT · metrics</sub>
</div>

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:

```bash
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:

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

The package installs a dependency-free CLI:

```bash
vedrakit --help
```

## Quick start

```python
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:

```bash
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:

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

Useful commands:

```bash
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:

```python
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:

```python
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)`:

```python
@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:

```python
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`:

```bash
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:

```python
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:

```python
@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`:

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

Custom exception handlers are registered by exception type:

```python
@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:

```python
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`

```python
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:

```text
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 `*`:

```bash
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`:

```bash
# 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:

```bash
pip install -e ".[postgresql]"
```

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

```python
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:

```python
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:

```python
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:

```python
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:

```python
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:

```python
@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:

```bash
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:

```typescript
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:

```python
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:

```python
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`:

```bash
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:

```python
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:

```python
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:

```python
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:

```bash
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

```text
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:

```bash
# 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
