Metadata-Version: 2.4
Name: flasio
Version: 0.1.1
Summary: Flask simplicity. Async power.
Author: Your Name
License: MIT
Project-URL: Repository, https://github.com/umeshyenugula/flasio
Keywords: asgi,async,web,framework
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Framework :: AsyncIO
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: uvicorn[standard]>=0.29
Provides-Extra: jinja2
Requires-Dist: jinja2>=3.1; extra == "jinja2"
Provides-Extra: sqlite
Requires-Dist: aiosqlite>=0.19; extra == "sqlite"
Provides-Extra: postgres
Requires-Dist: asyncpg>=0.29; extra == "postgres"
Provides-Extra: full
Requires-Dist: jinja2>=3.1; extra == "full"
Requires-Dist: aiosqlite>=0.19; extra == "full"
Requires-Dist: asyncpg>=0.29; extra == "full"

# Flasio

<div align="center">

```
███████╗██╗      █████╗ ███████╗██╗ ██████╗ 
██╔════╝██║     ██╔══██╗██╔════╝██║██╔═══██╗
█████╗  ██║     ███████║███████╗██║██║   ██║
██╔══╝  ██║     ██╔══██║╚════██║██║██║   ██║
██║     ███████╗██║  ██║███████║██║╚██████╔╝
╚═╝     ╚══════╝╚═╝  ╚═╝╚══════╝╚═╝ ╚═════╝
```

**⚡ Flask simplicity. Async power.**

[![Python](https://img.shields.io/badge/Python-3.9%2B-blue)](https://python.org)
[![ASGI](https://img.shields.io/badge/ASGI-uvicorn-green)](https://www.uvicorn.org)
[![License](https://img.shields.io/badge/License-MIT-yellow)](LICENSE)

</div>

---

## What is Flasio?

Flasio is a **minimal, production-ready async web framework** for Python. It is designed for developers who love Flask's simplicity but need the performance and scalability of ASGI. Flasio lets you write clean, readable async handlers with zero boilerplate, while running on uvicorn for maximum throughput.

**Why Flasio instead of Flask?**

| Feature | Flask | Flasio |
|---|---|---|
| Protocol | WSGI (sync) | ASGI (async) |
| Handler style | `def` | `async def` |
| Concurrency | Thread-per-request | Coroutine-based |
| Built-in JWT auth | ❌ | ✅ |
| Built-in email | ❌ | ✅ |
| Built-in DB adapter | ❌ | ✅ |
| CLI runner | `flask run` | `flasio run` |
| Core dependencies | Werkzeug + more | uvicorn only |

---

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Project Structure](#project-structure)
- [Core Concepts](#core-concepts)
  - [Application](#application)
  - [Routing](#routing)
  - [Request Object](#request-object)
  - [Response System](#response-system)
  - [Blueprints](#blueprints)
  - [Middleware](#middleware)
  - [Error Handling](#error-handling)
  - [Lifecycle Hooks](#lifecycle-hooks)
  - [Static Files](#static-files)
  - [Templates](#templates)
- [Services](#services)
  - [JWT Auth](#jwt-auth-service)
  - [Email](#email-service)
  - [Database](#database-service)
  - [Cloudinary](#cloudinary-service)
- [CLI Reference](#cli-reference)
- [Configuration](#configuration)
- [Deployment](#deployment)
- [Performance](#performance)
- [FAQ](#faq)

---

## Installation

**Minimum install (uvicorn only):**

```bash
pip install flasio
```

**With Jinja2 templating:**

```bash
pip install "flasio[jinja2]"
```

**With async database support:**

```bash
pip install "flasio[sqlite]"      # SQLite via aiosqlite
pip install "flasio[postgres]"    # PostgreSQL via asyncpg
```

**Everything:**

```bash
pip install "flasio[full]"
```

**From source (development):**

```bash
git clone https://github.com/your-org/flasio.git
cd flasio
pip install -e ".[full]"
```

---

## Quickstart

Create `app.py`:

```python
from flasio import Flasio

app = Flasio()

@app.route("/")
async def index(req):
    return {"message": "Hello, Flasio!", "status": "running"}

@app.get("/ping")
async def ping(req):
    return "pong"
```

Run it:

```bash
flasio run
```

Visit `http://127.0.0.1:8000` or test with curl:

```bash
curl http://127.0.0.1:8000/
# {"message": "Hello, Flasio!", "status": "running"}
```

---

## Project Structure

```
flasio/                        <- root of the repository
│
├── flasio/                    <- installable Python package
│   ├── __init__.py            <- public API (Flasio, Blueprint, Response, etc.)
│   │
│   ├── core/                  <- framework internals (~400 lines total)
│   │   ├── app.py             <- Flasio class, ASGI __call__, dispatch
│   │   ├── router.py          <- two-tier router (static O(1) + dynamic regex)
│   │   ├── request.py         <- Request object wrapping ASGI scope/receive
│   │   ├── response.py        <- Response, JSONResponse, HTMLResponse, Redirect
│   │   ├── exceptions.py      <- HTTPException hierarchy
│   │   └── middleware.py      <- async call_next middleware chain
│   │
│   ├── blueprint/
│   │   └── blueprint.py       <- Blueprint class with URL prefix
│   │
│   ├── services/              <- optional batteries
│   │   ├── auth.py            <- JWT (HS256, pure stdlib)
│   │   ├── email.py           <- SMTP email sender
│   │   ├── database.py        <- AsyncSQLite + AsyncPostgres adapters
│   │   └── cloudinary.py      <- Cloudinary upload helper
│   │
│   ├── templating/
│   │   └── engine.py          <- Jinja2 auto-detect + micro-engine fallback
│   │
│   └── cli/
│       └── main.py            <- `flasio run` command with banner
│
├── examples/
│   └── app.py                 <- full demo application
│
├── benchmarks/
│   └── test.sh                <- wrk benchmark script
│
├── pyproject.toml
├── README.md                  <- you are here
├── CONTRIBUTING.md            <- contribution guidelines
└── SETUP.md                   <- build, test, dev environment setup
```

---

## Core Concepts

### Application

The `Flasio` class is the central object — create one per application:

```python
from flasio import Flasio

app = Flasio(
    static_dir   = "static",      # directory for static files
    template_dir = "templates",   # directory for templates
    debug        = False,         # full tracebacks in responses when True
)
```

In **debug mode** (`debug=True`), unhandled exceptions return a full Python traceback as plain text instead of a generic 500 JSON. Never enable this in production.

---

### Routing

Flasio uses a two-tier router:

- **Static routes** (`/`, `/about`, `/api/v1/status`) resolve in **O(1)** via a plain dict.
- **Dynamic routes** (`/users/{id}`) scan an ordered list of compiled regex patterns.

#### Decorator API

```python
# Explicit methods list
@app.route("/path", methods=["GET", "POST"])
async def handler(req):
    ...

# Shorthand decorators
@app.get("/items")
async def list_items(req): ...

@app.post("/items")
async def create_item(req): ...

@app.put("/items/{id}")
async def replace_item(req): ...

@app.patch("/items/{id}")
async def update_item(req): ...

@app.delete("/items/{id}")
async def remove_item(req): ...
```

#### Dynamic Path Parameters

```python
@app.get("/users/{user_id}/posts/{slug}")
async def get_post(req):
    user_id = req.path_params["user_id"]   # always a str
    slug    = req.path_params["slug"]
    return {"user": user_id, "post": slug}
```

Path params are always strings — cast explicitly when needed:

```python
uid = int(req.path_params["user_id"])
```

---

### Request Object

Every handler receives a `Request` as its first (and only) argument:

```python
@app.post("/submit")
async def submit(req):
    method  = req.method               # "POST"
    path    = req.path                 # "/submit"
    client  = req.client               # ("127.0.0.1", 54321) or None

    # Headers — dict with lowercase keys
    ct      = req.headers.get("content-type", "")
    auth    = req.headers.get("authorization", "")

    # Query params — /submit?page=2&tag=async&tag=python
    page    = req.query_params.get("page")     # "2"
    tags    = req.query_params.get("tag")      # ["async", "python"]

    # Path params (only present on dynamic routes)
    uid     = req.path_params.get("user_id")

    # Body reading — all async, body bytes cached after first read
    raw     = await req.body()   # bytes
    text    = await req.text()   # str (UTF-8)
    data    = await req.json()   # dict or list
    form    = await req.form()   # dict from application/x-www-form-urlencoded

    return {"ok": True}
```

---

### Response System

#### Auto-coercion from handler return values

```python
async def a(req): return {"key": "val"}        # 200 application/json
async def b(req): return [1, 2, 3]             # 200 application/json
async def c(req): return "<h1>Hi</h1>"         # 200 text/html
async def d(req): return b"\x89PNG\r\n..."     # 200 application/octet-stream
async def e(req): return {"ok": True}, 201     # 201 application/json
async def f(req): return "ok", 200, {"X-ID": "1"}  # with extra headers
```

#### Explicit Response classes

```python
from flasio import JSONResponse, HTMLResponse, PlainTextResponse, RedirectResponse, Response

return JSONResponse({"error": "not found"}, status_code=404)
return HTMLResponse("<html><body>Hello</body></html>")
return PlainTextResponse("Service unavailable", status_code=503)
return RedirectResponse("/login")
return Response(b"...", status_code=200, media_type="image/png")
```

---

### Blueprints

```python
# api/users.py
from flasio import Blueprint

users_bp = Blueprint("users", prefix="/users")

@users_bp.get("/")
async def list_users(req):
    return {"users": []}

@users_bp.get("/{user_id}")
async def get_user(req):
    return {"id": req.path_params["user_id"]}

@users_bp.post("/")
async def create_user(req):
    body = await req.json()
    return body, 201
```

```python
# app.py
from flasio import Flasio
from api.users import users_bp

app = Flasio()
app.register_blueprint(users_bp)

# Registered routes:
#   GET  /users/
#   GET  /users/{user_id}
#   POST /users/
```

---

### Middleware

```python
async def my_middleware(request, call_next):
    # runs before the handler
    response = await call_next(request)
    # runs after the handler
    return response

app.use(my_middleware)
```

Multiple middlewares execute in registration order (first registered = outermost wrapper):

```python
app.use(logging_middleware)   # outermost
app.use(auth_middleware)
app.use(cors_middleware)      # innermost
```

#### CORS example

```python
async def cors_middleware(request, call_next):
    response = await call_next(request)
    response.headers["Access-Control-Allow-Origin"]  = "*"
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
    response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
    return response
```

#### Request timing

```python
import time

async def timing_middleware(request, call_next):
    t0       = time.perf_counter()
    response = await call_next(request)
    ms       = (time.perf_counter() - t0) * 1000
    response.headers["X-Response-Time"] = f"{ms:.2f}ms"
    return response
```

---

### Error Handling

Raise any `HTTPException` subclass anywhere — it is caught and returned as JSON:

```python
from flasio import NotFound, BadRequest, Unauthorized, HTTPException

@app.get("/items/{id}")
async def get_item(req):
    item = db.get(int(req.path_params["id"]))
    if not item:
        raise NotFound(f"Item not found")
    return item

# Custom status code
raise HTTPException("Payment required", status_code=402)
```

Error JSON shape:

```json
{"error": "Item not found", "status": 404}
```

---

### Lifecycle Hooks

```python
@app.on_startup
async def startup():
    app.db = await create_db_pool()

@app.on_shutdown
async def shutdown():
    await app.db.close()
```

---

### Static Files

Files in `static/` are served at `/static/<filename>` automatically. MIME type is auto-detected.

```
GET /static/style.css   -> 200 text/css
GET /static/logo.png    -> 200 image/png
```

---

### Templates

```python
@app.get("/dashboard")
async def dashboard(req):
    html = app.render("dashboard.html", {"user": "Alice", "items": ["A", "B"]})
    return HTMLResponse(html)
```

Jinja2 is used when installed (`pip install "flasio[jinja2]"`), otherwise the built-in micro-engine handles `{{ var }}`, `{% if %}`, `{% for %}`.

---

## Services

### JWT Auth Service

```python
from flasio.services.auth import AuthService

auth = AuthService(secret="your-secret", expires_in=3600)

token   = auth.generate({"user_id": 42, "role": "admin"})
payload = auth.verify(token)    # raises ValueError if invalid/expired
```

---

### Email Service

```python
from flasio.services.email import EmailService

mailer = EmailService(
    host="smtp.gmail.com", port=587,
    username="you@gmail.com", password="app-password",
)

await mailer.send(
    to="friend@example.com",
    subject="Hello",
    body="Plain text body",
    html="<h1>HTML body</h1>",
)
```

---

### Database Service

```python
from flasio.services.database import AsyncSQLite, AsyncPostgres

async with AsyncSQLite("app.db") as db:
    await db.execute("INSERT INTO users (name) VALUES (?)", ["Alice"])
    rows = await db.fetch("SELECT * FROM users")

# PostgreSQL
db = AsyncPostgres("postgresql://user:pass@localhost/mydb")
await db.connect()
rows = await db.fetch("SELECT * FROM users")
await db.disconnect()
```

---

### Cloudinary Service

```python
from flasio.services.cloudinary import CloudinaryService

cdn = CloudinaryService(cloud_name="...", api_key="...", api_secret="...")

result = await cdn.upload("photo.jpg", folder="avatars")
print(result["secure_url"])

await cdn.delete("public_id_here")
```

---

## CLI Reference

```
flasio run [MODULE:APP] [--host HOST] [--port PORT] [--reload]
           [--no-reload] [--prod] [--log-level LEVEL]
```

| Flag | Default | Description |
|---|---|---|
| `MODULE:APP` | `app:app` | Module path and ASGI app attribute |
| `--host` | `127.0.0.1` | Bind address |
| `--port` | `8000` | Bind port |
| `--reload` | off | Force auto-reload on |
| `--no-reload` | off | Force auto-reload off |
| `--prod` | off | Production mode (no banner, no reload) |
| `--log-level` | `info`/`warning` | Uvicorn log verbosity |

---

## Configuration

Flasio has no built-in config system. Use environment variables:

```python
import os
from flasio import Flasio
from flasio.services.auth import AuthService

app  = Flasio(debug=os.getenv("DEBUG", "false") == "true")
auth = AuthService(secret=os.environ["JWT_SECRET"])
```

---

## Deployment

**uvicorn directly:**

```bash
uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
```

**gunicorn + uvicorn workers:**

```bash
gunicorn app:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
```

**Docker:**

```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir "flasio[full]"
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
```

---

## Performance

Flasio avoids overhead at every level:

- Static routes resolve in O(1)
- No automatic body parsing — body only read on demand
- No built-in request validation
- No ORM or reflection-heavy logic
- Middleware stack is a flat list, not a class hierarchy

Run the benchmark:

```bash
uvicorn examples.app:app --host 0.0.0.0 --port 8000 &
chmod +x benchmarks/test.sh && ./benchmarks/test.sh
```

---

## FAQ

**WebSocket support?** Not yet — planned for a future release.

**Can I use Pydantic?** Yes — call `await req.json()` and pass it to your Pydantic model.

**Sync handlers?** Handlers must be `async def`. Wrap sync code with `run_in_executor`.

**Multiple apps?** Each `Flasio()` instance is independent. Mount them behind a reverse proxy.

---

## License

MIT — see [LICENSE](LICENSE).

---

<div align="center">Built with ⚡ by the Flasio contributors</div>
