Downloads PyPI version License Swagger UI ReDoc Scalar

Introduction

FlaskNova is a productivity-first framework extension built for the modern Python era. It was designed with a single goal: to make API development in Flask as seamless and automated as possible.

Core Tech: Pydantic V2 OpenAPI 3.2

Improving developers experience, FlaskNova brings modern features to the Flask ecosystem. By leveraging Pydantic for data validation, ensures your request payloads and response models are strictly typed, validated, and self-documenting.

Zero Migration Debt

No need to rewrite your entire legacy Flask app. Get route metadata features while keeping your existing architecture intact.

Ecosystem Friendly

Fully compatible with your favorite extensions like Flask-Login, Flask-Admin, and Flask-SQLAlchemy.

Development shouldn't involve manually updating JSON schemas or writing tedious validation logic for every route. FlaskNova handles the heavy lifting, providing automatic OpenAPI documentation, type-safe routing, and built-in serialization. It transforms Flask from a micro-framework into a robust, full-featured API engine without losing the simplicity that Flask developers love.

Installation

Install the core package via pip:

pip install flask-nova

Getting Started

Basics: Simple GET Request

Define your routes using the @app.get decorator. FlaskNova automatically populates your documentation UI.

Docstring Support

If you don't provide a summary or description, FlaskNova intelligently parses your docstring: The first line becomes the Summary, and the following lines become the Description.

@app.get("/welcome")
def welcome():
    """Welcome User.

    This text will show up as the long description in Swagger/Redoc.
    """
    return {"message": "Welcome to FlaskNova!"}
Check Sample Code
Swagger UI Preview /docs
GET /welcome
Welcome User

Respecting Flask Internals

FlaskNova doesn't take away Flask's internals. You can still use make_response, jsonify, Response and custom headers exactly as you do in standard Flask.

from flask import make_response, jsonify

@api.route("/custom", methods=["GET"])
def custom():
    data = {"message": "Custom response"}
    response = make_response(jsonify(data), 201)
    response.headers['X-Custom-Header'] = 'Value'
    return response

Request/Response Handling

FlaskNova infers request bodies and response models from annotations, so your routes stay compact while still generating schema and validation.

Tip: Request and response signatures are generated when the app starts. That means route bindings are resolved up front, before the first request is handled.

JSON body + response model

from flask_nova import status
from pydantic import BaseModel, Field

class User(BaseModel):
    name: str = Field(..., max_length=5)
    age: str = Field(..., max_length=5)

@app.post("/")
def home(user: User) -> tuple[User, int]: # return type
    return user, 200
Notice the return type was specified when returning a model like object.

Form data parsing

The same model can be bound from form data using Form(). This is useful for HTML forms and multipart submissions. Use application/x-www-form-urlencoded for ordinary fields and multipart/form-data when files are included.

from typing import Annotated
from flask_nova import Form

@app.post("/", response_model=Home)
def home(user: Annotated[User, Form()]):
    return {"message": "Welcome Home!"}

@app.post("/", response_model=Home)
def home(user: User = Form()):
    return {"message": "Welcome Home!"}

File uploads

Use File("profile") with FileStorage to receive the uploaded field named profile. The client must send a multipart/form-data request.

from flask_nova import File, FileStorage

@app.post("/")
def upload(profile: FileStorage = File("profile")) -> dict[str, str]:
    return {"filename": profile.filename}

@app.post("/upload")
def upload_file(profile: Annotated[FileStorage, File("profile")]):
    return {"filename": profile.filename}
curl -X POST http://127.0.0.1:5000/upload \
  -F "profile=@avatar.png"

Response serialization

Use response_model when you want output validated and serialized as a model schema.

from typing import Literal

@app.get("/author/<int:author_id>/books/<string:genre>/")
def author(author: Author, author_id: int, genre: str, year: int, month) -> tuple[Author, Literal[201]]:
    return author, 201
Native response dispatcher: Prefer Literal[201] over int for the status value. int only says that some integer may be returned; Literal[201] tells FlaskNova and its OpenAPI generator that this route returns the specific success status 201 Created.
Tip: When a route has both response_model and a native response dispatcher, FlaskNova chooses the response_model path. If you use a route response dispatcher or response model, do not return Flask response objects like jsonify, make_response, or Response from that route.

Return model-like values directly

When a route returns a Pydantic model, dataclass, or custom class with to_dict(), let FlaskNova serialize it. Do not wrap it in jsonify() or make_response() unless you intentionally need a raw Flask response.

from pydantic import BaseModel

class Profile(BaseModel):
    username: str

@app.get("/profile", response_model=Profile)
def profile():
    return Profile(username="alice")

When raw Flask responses are useful

Use a native Response for downloads, streaming, static content, or custom headers that do not belong to a model-based response.

from flask import Response

@app.get("/download")
def download():
    return Response("hello", mimetype="text/plain")

Template serialization

render_template from flask_nova support template ctx obj serialization

from flask_nova import render_template
class DashboardResponse:
    id: int
    name: str
    role: Literal["admin", "user"]

@api.get("/render", response_model=DashboardResponse)
def admin_dashboard():
    user = {"name": "Mani", "role": "admin", "id": 12345}
    return render_template("index.html", **user)

The user context object will be serialized before passing it to the template

{
    "name": "Mani",
    "role": "admin"
}

Query and path parameters

FlaskNova infers primitive query and path values from function parameters.

@api.get("/t", response_model=Home)
def query_req(id: int, name: str, age: int):
    return {"message": f"Welcome Home! {id}-{name}-{age}"}

Path segments like <int:author_id> and query values are both supported in the same handler.

support Field from pydantic for validation

Custom classes

Note: FlaskNova detects custom response objects by checking for a to_dict() attribute. That attribute is used to identify the type, not to automatically generate the response data for you.
class User2:
    age: int | None
    name: str

    def to_dict(self):...

Colored JSON logging

Enable JSON log formatting with config: app.config["ANSI_COLOR_JSON_LOG"] = True.

app.config["ANSI_COLOR_JSON_LOG"] = True
app.logger.error("/register", exc_info=True)

When this setting is enabled, app.logger emits colored JSON output suitable for terminal consoles and structured logging.

from flask_nova import status
status.OK (200)
status.CREATED (201)
status.BAD_REQUEST (400)
status.UNAUTHORIZED (401)

Why Pydantic?

Unlike standard Flask dictionaries, Pydantic offers:

How request validators differ

FlaskNova supports Pydantic models, dataclasses, and custom classes for request binding. Each option has different validation behavior.

Pydantic

Invalid data is rejected before business logic runs. For example, an age value such as "thirty" fails validation because the field expects an integer. Extra fields are ignored by default. Missing required fields raise a validation error.

Dataclass

The request shape is strict: extra fields or missing fields raise a TypeError. Dataclasses check the object structure, but they do not validate field types at runtime.

Custom class

FlaskNova uses the class attributes to build the request object. Every declared field is expected explicitly, including optional fields; an omitted optional field is set to None.

Current custom-class behavior: This explicit behavior avoids guessing, but its error messages will be improved in a future version.
{
  "username": "mani",
  "age": "thirty"
}

With a Pydantic model such as age: int, this request fails during validation and never reaches the route's business logic.

Create Your First App

FlaskNova is a Flask subclass, so a new application follows the workflow you already know. Define a model, attach it to a route, and return the model directly.

from pydantic import BaseModel
from flask_nova import FlaskNova, status

app = FlaskNova(__name__)

class User(BaseModel):
    username: str
    age: int

@app.post("/register", response_model=User)
def register(user: User):
    return user, status.CREATED

if __name__ == "__main__":
    app.run(debug=True)

The request body is validated before register runs. A valid request looks like this:

{
  "username": "alice",
  "age": 30
}
Tip: Use a virtual environment for each new service so its FlaskNova and Pydantic versions stay isolated.

Organize Routes with Blueprints

NovaBlueprint groups related endpoints in a feature module while keeping Flask's registration model. This is useful when an API grows beyond one file.

from flask_nova import FlaskNova, NovaBlueprint

app = FlaskNova(__name__)
api = NovaBlueprint("api")

@api.get("/health")
def health():
    return {"status": "ok"}

app.register_blueprint(api)

Keep models, dependencies, services, and route groups separate so each handler stays focused on translating an HTTP request into an application operation.

Dependency Injection (Depend)

Depend( dependency=None)

Modularize your logic by injecting dependencies directly into your function signatures.

from flask_nova import Depend

def get_token_header(x_token: str = "secret"):
    return x_token

@app.get("/secure")
def secure_route(token = Depend(get_token_header)):
    return {"token": token}

Depend by nature give you scoped life time

Providers may be synchronous or asynchronous. FlaskNova resolves an async provider before calling the route, so both styles can be used for request-scoped authentication or service setup.

from flask_nova import Depend

async def get_current_user():
    return {"username": "alice"}

@app.get("/me")
def me(user=Depend(get_current_user)):
    return user

The @guard Decorator

Combine multiple security layers or filters into one clean decorator. Avoid: Decorator stacking which makes code hard to read.

from flask_jwt_extended import jwt_required
from flask_nova import guard

@app.get("/admin")
@guard(jwt_required(), admin_only_check)
def admin_dashboard():
    return {"data": "confidential"}

Error Handling & Color Logging

FlaskNova uses a robust HTTPException class to handle errors consistently.

from flask_nova import HTTPException, status

raise HTTPException(
    status_code=status.NOT_FOUND,
    detail="User not found",
    title="Resource Error"
)
from flask_nova import get_flasknova_logger
logger = get_flasknova_logger()
logger.info("FlaskNova app started!")
[INFO] FlaskNova app started!
[201] POST /register - 0.045s
[ERROR] 422 Unprocessable Entity: "age" must be integer

Tracing (trace id & traceparent)

FlaskNova assigns a per-request trace id and exposes it on the Flask request context as g.trace_id. This id can be used in logs to correlate requests. Error responses produced by FlaskNova include a W3C traceparent header so clients and observability tools can correlate traces across services.

Task helpers: to_thread and to_process

For offloading work from request handlers, FlaskNova exposes two small helpers:

# example: use to_thread in an async handler
result = await to_thread(some_io_bound_function, 1000)

# example: offload cpu work
result = await to_process(app, cpu_heavy_function, 4, arg1)

Set a limit around a long-running task with asyncio.wait_for:

import asyncio

try:
    result = await asyncio.wait_for(
        to_thread(long_running_task),
        timeout=30.0,
    )
except asyncio.TimeoutError:
    print("Task exceeded 30 seconds")
Process reminder: Functions and arguments passed to to_process must be compatible with process-pool serialization. Keep process work self-contained and pass simple, serializable values where possible.

Ecosystem Integrations

Flask-SQLAlchemy (Full CRUD)

FlaskNova does not automatically serialize SQLAlchemy models. Convert the database model into a Pydantic model, dataclass, or custom class before returning it from a route.

from pydantic import BaseModel
from flask_nova import status

class UserResponse(BaseModel):
    username: str
    age: int

@app.post("/users", response_model=UserResponse)
def create_db_user(data: UserSchema) -> tuple[UserResponse, int]:
    new_user = User(username=data.username, age=data.age)
    db.session.add(new_user)
    db.session.commit()

    # Explicitly map the SQLAlchemy model to a FlaskNova response model.
    response = UserResponse(
        username=new_user.username,
        age=new_user.age,
    )
    return response, status.CREATED
Important: Keep database-only fields, such as internal IDs or password hashes, out of the response model unless you explicitly want to expose them.
View SQL Integration Example

Flask-JWT-Extended

Handling Refresh Tokens and Access Tokens with ease using Depend.

@app.get("/me")
def get_me(user = Depend(current_user)):
    return user
View JWT Integration Example

Configuration

FlaskNova currently exposes the following application configuration switch. The documentation routes use the standard paths /docs, /redoc, /scalar, and /openapi.json.

Variable Default Description
FLASKNOVA_ENABLED_DOCS True Set to False to hide Swagger/Redoc/Scalar in production.
FLASKNOVA_SWAGGER_ROUTE /docs Path for Swagger UI.
FLASKNOVA_REDOC_ROUTE /redoc Path for ReDoc.
FLASKNOVA_SCALAR_ROUTE /scalar Path for Scalar.
ANSI_COLOR_JSON_LOG False Enable colored JSON log formatting for app.logger.

OpenAPI and Route Metadata

FlaskNova builds the OpenAPI document from the route signatures, models, and metadata you provide. The generated document is available at /openapi.json and is used by the interfaces at /docs, /redoc, and /scalar.

Application-level metadata describes the API as a whole:

from flask_nova import FlaskNova

app = FlaskNova(
    __name__,
    version="1.0.0",
    summary="User management API",
    description="An API for managing users.",
    contact={"name": "API team", "email": "api@example.com"},
    external_docs={"description": "Project guide", "url": "https://example.com/guide"},
)

Route metadata describes an individual operation. Use it to make generated documentation useful to both people and client tooling.

@app.get(
    "/users",
    tags=["Users"],
    summary="List users",
    description="Return all visible users.",
    deprecated=False,
    response_model=list[UserResponse],
)
def list_users() -> list[UserResponse]:
    return [UserResponse(username="alice", age=30)]

Project Structure

A small separation of responsibilities makes model-driven APIs easier to test and maintain.

project/
├── app/
│   ├── __init__.py
│   ├── models.py
│   ├── routes.py
│   ├── deps.py
│   ├── services.py
│   └── errors.py
├── requirements.txt
├── run.py
└── tests/
    └── test_users.py

Use separate input and output models when their contracts differ. For example, a create request may accept a password while the response must never expose it.

from pydantic import BaseModel, Field

class CreateUserInput(BaseModel):
    username: str = Field(min_length=3, max_length=40)
    age: int = Field(ge=18)

class UserResponse(BaseModel):
    username: str
    age: int
    is_active: bool = True

Testing and Debugging

Use Flask's test client to check validation, response structure, and error behavior without starting a server.

def test_create_user(client):
    response = client.post(
        "/users",
        json={"username": "alice", "age": 30},
    )

    assert response.status_code == 200
    assert response.json["username"] == "alice"

When a request fails, inspect the request model first, then the dependency layer, and finally the route logic. Validation happens before the route body, which makes failures easier to isolate.

Production Checklist

CLI Mastery

Flask-Nova provides a powerful CLI tool to automatically generate artifacts for your routes.

Command

flask_nova gen --app <your_app_path> [OPTIONS]

Options

Option Description
--app TEXT Required. App path, e.g. examples.form_ex:app
--format [http|py|all] File format to generate (default: all)
--base-url TEXT Base URL for requests (default: http://127.0.0.1:5000)
--output PATH Directory to save files (default: current directory)
Generate both HTTP and Python requests:
$ flask_nova gen --app main:app --format all
[SCAN] 14 routes detected...
[DONE] Created 14 .http requests in ./docs/requests
[DONE] Created 14 pytest scripts in ./tests/gen
Generate HTTP requests only flag --output path:
$ flask_nova gen --app examples.form_ex:app --format http --rest
Generate Python requests only:
$ flask_nova gen --app examples.form_ex:app --format py

Command

flask_nova info --app <your_app_path>

The info command display's a browser summary of your app

Project Evolution

FlaskNova has moved fast, evolving from a core routing engine to a full-featured API framework in record time.