Metadata-Version: 2.4
Name: spweb
Version: 0.2.0
Summary: A library that is being built for secured servers.
License: MIT
Project-URL: Homepage, https://github.com/yourname/spweb
Keywords: web,wsgi,framework,security
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# SPWeb v0.2.0

A secure, Flask-like Python WSGI micro-framework. Zero dependencies beyond the standard library.

## Quick start

```python
from spweb import SPWeb, Response

app = SPWeb(secret_key="your-long-random-secret")

@app.route("/")
def index(request):
    return Response.html("<h1>Hello from SPWeb!</h1>")

@app.route("/api/users/<int:user_id>")
def get_user(request, user_id):
    return Response.json({"id": user_id, "name": "Alice"})

@app.post("/api/login")
def login(request):
    data = request.json
    # ... validate credentials ...
    return Response.json({"token": "..."})

app.run()
```

## URL parameters

Supports typed URL parameters — `<int:id>`, `<float:n>`, `<str:name>`, `<uuid:uid>`, `<path:rest>`.

```python
@app.route("/files/<path:filepath>")
def serve_file(request, filepath):
    ...
```

## Response helpers

```python
Response("plain text")
Response.html("<h1>Hi</h1>")
Response.json({"key": "value"})
Response.redirect("/new-url")
Response.empty(204)

# Chaining
Response.json(data).set_cookie("session", token, max_age=3600)
```

## Request helpers

```python
request.json        # parsed JSON body
request.form        # parsed form body
request.text        # raw body string
request.cookies     # dict of cookies
request.query       # dict of query params
request.url_params  # dict of matched URL params
request.remote_addr # client IP (X-Forwarded-For aware)
request.is_json     # bool
request.get_header("Authorization")
```

## Hooks & error handlers

```python
@app.before_request
def auth_check(request):
    if request.path.startswith("/admin"):
        if not request.cookies.get("session"):
            return Response.redirect("/login")

@app.after_request
def add_cors(request, response):
    response.set_header("Access-Control-Allow-Origin", "https://myapp.com")
    return response

@app.error_handler(404)
def not_found(request):
    return Response.html("<h1>Page not found</h1>", status=404)
```

## HTTP exceptions

Raise from any handler:

```python
from spweb import HTTPForbidden, HTTPNotFound

@app.get("/secret")
def secret(request):
    if not is_admin(request):
        raise HTTPForbidden("Admins only")
    raise HTTPNotFound("No such resource")
```

## Security features (automatic)

Every response gets these headers out of the box:

| Header | Value |
|---|---|
| `Content-Security-Policy` | strict `default-src 'self'` policy |
| `Strict-Transport-Security` | 1-year HSTS + subdomains |
| `X-Content-Type-Options` | `nosniff` |
| `X-Frame-Options` | `DENY` |
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Permissions-Policy` | blocks camera, mic, geolocation, etc. |
| `Cache-Control` | `no-store` |

Plus:
- **CSRF protection** — HMAC-signed, timestamped tokens; auto-rotated on expiry.
- **Rate limiting** — per-IP sliding window (default 100 req/60s).
- **Host validation** — optional allowlist.
- **Origin validation** — optional allowlist.
- **Body size cap** — 16 MB default.

## Configuration

```python
app = SPWeb(
    debug=False,
    secret_key="...",                   # HMAC key for CSRF tokens
    allowed_hosts=["example.com"],      # block host-header injection
    trusted_origins=["https://example.com"],
    csrf_exempt_paths=["/webhook"],     # skip CSRF for specific paths
    rate_limit=200,                     # requests per window
    rate_limit_window=60,               # seconds
    hsts_max_age=63_072_000,            # 2 years
    hsts_preload=True,
    csp_policy="default-src 'self'",    # override CSP
)
```
