Metadata-Version: 2.4
Name: flask-redantic
Version: 0.1.0
Summary: Automatic Pydantic validation and ReDoc documentation generation for Flask endpoints
Project-URL: Homepage, https://github.com/wagnercder/flask-redantic
Project-URL: Repository, https://github.com/wagnercder/flask-redantic
License: MIT
License-File: LICENSE
Keywords: documentation,flask,openapi,pydantic,redoc,validation
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: flask>=2.3
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: httpx; extra == 'dev'
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# flask-redantic

Automatic Pydantic validation and ReDoc API documentation for Flask — annotation-driven.

`redantic` is a lightweight Flask library that:
- **Validates** request bodies and query parameters by reading your **type annotations**
- **Injects** validated Pydantic model instances directly into your view functions
- **Auto-generates** interactive ReDoc API documentation - no YAML, no manual schema config
- **Requires zero boilerplate** - your annotations and docstrings are the source of truth

---

## Installation

```bash
uv add flask-redantic
```

---

## Quick Start

```python
from flask import Flask, jsonify
from pydantic import BaseModel, Field
from redantic import Redantic
from redantic.raises import HTTP_404, HTTP_422

app = Flask(__name__)
api = Redantic(title="Quark's Bar API", version="v1.0")

class ItemIn(BaseModel):
    name: str = Field(..., min_length=1)
    category: str
    price_slips: int = Field(..., ge=0)

class ItemOut(BaseModel):
    id: int
    name: str
    category: str
    price_slips: int

class ErrorOut(BaseModel):
    message: str

@app.post("/inventory")
@api.validate(
    tags=["inventory"],
    raises=[HTTP_422()],
    success_status=201,
)
def create_item(body: ItemIn) -> ItemOut:
    """
    Acquire new stock for Quark's.

    The body is validated and injected automatically.
    """
    return jsonify({"id": 1, **body.model_dump()}), 201

@app.get("/inventory/<int:item_id>")
@api.validate(tags=["inventory"], raises=[HTTP_404(ErrorOut)])
def get_item(item_id: int) -> ItemOut:
    """Retrieve an item by ID."""
    item = db.get(item_id)
    if not item:
        return jsonify({"message": "Not found"}), 404
    return jsonify(item)

api.init_app(app)
```

**Visit:**
- `http://localhost:5000/docs` → Interactive ReDoc documentation
- `http://localhost:5000/docs/openapi.json` → Raw OpenAPI 3.0 JSON spec

---

## How It Works

Everything is driven by **standard Python type annotations**:

| Annotation | Effect |
|---|---|
| `body: MyModel` | JSON body validated → `body` is a `MyModel` instance |
| `query: MyModel` | Query-string params validated → `query` is a `MyModel` instance |
| `item_id: int` *(Flask path param)* | Passed through unchanged by Flask |
| `-> MyModel` | Documents the success response schema |
| `raises=[HTTP_404(ErrorOut)]` | Documents error response schemas |

### Body injection

```python
@app.post("/inventory")
@api.validate(tags=["inventory"], success_status=201)
def create_item(body: ItemIn) -> ItemOut:
    # `body` is a fully validated ItemIn instance — ready to use
    return jsonify({"id": 1, **body.model_dump()}), 201
```

### Body + path parameter

Flask path parameters are passed normally; redantic injects body/query alongside them:

```python
@app.put("/inventory/<int:item_id>")
@api.validate(tags=["inventory"], raises=[HTTP_404(ErrorOut)])
def update_item(item_id: int, body: ItemIn) -> ItemOut:
    # item_id comes from Flask routing
    # body is injected by redantic
    ...
```

### Query parameters

Use a parameter named `query` annotated with a `BaseModel`:

```python
class ItemFilter(BaseModel):
    category: str | None = None
    max_price: int | None = Field(None, ge=0)

@app.get("/inventory")
@api.validate(tags=["inventory"])
def list_items(query: ItemFilter) -> ItemOut:
    if query.category:
        ...
```

---

## `@api.validate` Parameters

| Parameter | Type | Description |
|---|---|---|
| `raises` | `list[RaisesSpec]` | Error response declarations (see below) |
| `tags` | `list[str]` | Group endpoints in the ReDoc sidebar |
| `summary` | `str \| None` | One-line description. Auto-detected from first docstring line if omitted |
| `deprecated` | `bool` | Mark endpoint as deprecated in docs |
| `success_status` | `int` | Success HTTP status code (default: `200`). Override to `201` for creation endpoints |

### Validation errors

When a request body or query fails validation, redantic automatically returns:

```
HTTP 422 Unprocessable Entity

{
  "detail": [
    {
      "loc": ["name"],
      "msg": "String should have at least 1 character",
      "type": "string_too_short"
    }
  ]
}
```

---

## `raises` — Declaring Error Responses

Import the pre-built HTTP error helpers from `redantic.raises`:

```python
from redantic.raises import HTTP_400, HTTP_401, HTTP_403, HTTP_404, HTTP_409, HTTP_422, HTTP_500

@api.validate(raises=[
    HTTP_404(ErrorOut),   # 404 with ErrorOut schema
    HTTP_403(),           # 403 with no body
    HTTP_422,             # 422 with no body (no parentheses needed)
])
def my_endpoint(...): ...
```

Available helpers: `HTTP_400`, `HTTP_401`, `HTTP_403`, `HTTP_404`, `HTTP_409`, `HTTP_422`, `HTTP_429`, `HTTP_500`, `HTTP_503`

> **Note:** A 422 Unprocessable Entity response is **automatically added** to the spec whenever a body or query model is present. You don't need to add it manually.

---

## Application Factory Pattern

```python
from redantic import Redantic

api = Redantic(
    title="My API",
    version="v1.0",
    description="Supports **Markdown** in the overview section.",
    path="doc",   # /docs UI, /docs/openapi.json spec
)

from extensions import api
from flask import Flask

def create_app():
    app = Flask(__name__)
    # ... register routes ...
    api.init_app(app)   # call AFTER all routes are registered
    return app
```

---

## `Redantic` Configuration

```python
api = Redantic(
    app,               # optional Flask app; pass later via init_app()
    title="My API",    # shown in ReDoc header
    version="v2.0",    # shown in ReDoc version badge
    description="...", # Markdown shown in ReDoc overview
    path="doc",        # URL prefix; /docs UI, /docs/openapi.json spec
)
```

---

## Running the Example

```bash
pip install -e .
python examples/app.py
# → http://localhost:5050/docs
```

---

## Project Structure

```
flask-redantic/
├── src/redantic/
│   ├── __init__.py    # Public API: Redantic
│   ├── core.py        # Redantic class + Flask Blueprint
│   ├── decorator.py   # @validate decorator + signature inspection
│   ├── spec.py        # OpenAPI 3.0 spec builder
│   ├── raises.py      # HTTP_xxx error-response helpers
│   └── templates.py   # ReDoc HTML template
└── examples/
    └── app.py         # Runnable Quark's Bar demo
```

---

## License

MIT
