Metadata-Version: 2.4
Name: bwsgi
Version: 0.1.1
Summary: Ultra-lightweight, zero-dependency Python WSGI framework
Author-email: Baraa Ahmed <baraa.runtime@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/BaraaByte/bwsgi
Project-URL: Repository, https://github.com/BaraaByte/bwsgi
Project-URL: Issues, https://github.com/BaraaByte/bwsgi/issues
Keywords: wsgi,framework,zero-dependency,serv00,freebsd,flask-like
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: BSD :: FreeBSD
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Bwsgi

Ultra-lightweight, **zero-dependency** Python WSGI framework.
Flask-style routing. Django-style auth. Built on the Python Standard Library.

## Why Bwsgi?

- **Zero dependencies** — pure Python stdlib
- **~5-8 MB idle RAM** — runs happily on 512MB hosts like Serv00
- **Flask-style routing** — decorator-based, familiar syntax
- **Secure auth built in** — PBKDF2 password hashing + DB-backed sessions
- **WSGI native** — works with Passenger, Gunicorn, uWSGI

## Install

```bash
pip install bwsgi
```

## Quick Start

```python
from bwsgi import Bwsgi, Response, login_required

app = Bwsgi(db_path="app.db")

@app.route("/")
def home(req):
    return Response.html("<h1>Hello from Bwsgi</h1>")

@app.route("/dashboard")
@login_required
def dashboard(req):
    return Response.html(f"<h1>Hi {req.user['username']}</h1>")

if __name__ == "__main__":
    app.run()
```

## Routing and responses

Routes accept named path parameters and one or more HTTP methods:

```python
@app.route("/users/<username>", methods=("GET",))
def user(req, username):
    return Response.json({"username": username})

profile_url = app.url_for("user", username="Jane Doe")
```

Path values generated by `url_for` are URL-encoded. Requests to a known path
with an unsupported method receive `405 Method Not Allowed` and an `Allow`
header. Static files are served from `static/` at `/static/`; files are
streamed in chunks and paths are checked against the configured static root.

`Request` provides `query`, `headers`, `cookies`, `body`, `form()`, and `json()`.
Malformed JSON request bodies receive `400 Bad Request`.
`Response` provides `text()`, `html()`, `json()`, `redirect()`, and `file()`.

## JSON request validation

Use `Request.validate_json()` to require a JSON object with specific required
fields and Python types. Malformed JSON and invalid values return a JSON
`400 Bad Request` response:

```python
@app.route("/api/items", methods=("POST",))
def create_item(req):
    item = req.validate_json({"name": str, "quantity": int})
    return Response.json(item, "201 Created")
```

## Sessions and CSRF

`req.session` is a server-side dictionary persisted in SQLite and identified by
a signed, HTTP-only cookie. Set a stable secret key when creating the app so
session cookies survive process restarts:

```python
app = Bwsgi(db_path="app.db", secret_key="load-this-from-your-environment")
```

Protect state-changing routes with `@csrf_protect`. Render `req.csrf_token()`
into a hidden form field named `_csrf_token`, or send it in the
`X-Csrf-Token` header for JSON requests:

```python
@app.route("/submit", methods=("POST",))
@csrf_protect
def submit(req):
    req.session["submitted"] = True
    return Response.json({"ok": True})
```

Safe methods (`GET`, `HEAD`, `OPTIONS`, and `TRACE`) do not require a token.
Keep state-changing actions on non-safe methods and use CSRF protection for
browser-session routes.

## Templates, middleware, and errors

Templates are UTF-8 files under `templates/` by default. `{{ name }}` values
are HTML-escaped automatically; this intentionally supports variable
substitution only, not template logic:

```html
<h1>Hello, {{ username }}</h1>
```

```python
return app.render_template("hello.html", username="Ada")
```

Register standard WSGI middleware with a factory that accepts and returns a
WSGI callable:

```python
def request_id_middleware(next_app):
    def middleware(environ, start_response):
        environ["HTTP_X_REQUEST_ID"] = "example"
        return next_app(environ, start_response)
    return middleware

app.add_middleware(request_id_middleware)
```

Error handlers may be registered for HTTP status codes or exception classes:

```python
@app.errorhandler(ValueError)
def invalid_value(req):
    return Response.json({"error": "invalid value"}, "400 Bad Request")
```

## License

MIT
