Skip to main content

FastAPI wrapper to create Django like experience which uses Piccolo ORM under the hood.

Project description

⚡ Fastique

Django-like developer experience, powered by FastAPI + Piccolo ORM.

Fastique gives you the familiar Django workflow — startproject, startapp, migrate, createsuperuser, class-based views, an auto-generated admin panel — all running on the blazing-fast async FastAPI + Piccolo ORM stack.


Feature Overview

Django feature Fastique equivalent
django-admin startproject fq startproject
python manage.py startapp fq startapp
python manage.py runserver fq runserver
python manage.py makemigrations fq makemigrations
python manage.py migrate fq migrate
python manage.py createsuperuser fq createsuperuser
python manage.py shell fq shell
python manage.py test fq test
models.Model fastique.Model (Piccolo Table)
admin.site.register @register(MyModel)
ModelAdmin fastique.ModelAdmin
ListView, DetailView, etc. Same names, async
settings.py Same — loaded via FASTIQUE_SETTINGS_MODULE
urls.py / include() Router + include()
@login_required Same
JWT tokens Built-in via /auth/token
Session auth Built-in via /auth/login

Installation

pip install fastique

Quickstart

1. Create a project

fq startproject myblog
cd myblog

Generated structure:

myblog/
├── manage.py              ← entry point (like Django's)
├── myblog/
│   ├── __init__.py
│   ├── settings.py        ← all settings in one place
│   ├── asgi.py            ← ASGI app (points uvicorn here)
│   └── urls.py            ← project-level router hints
├── templates/
│   └── base.html
├── static/
├── media/
├── piccolo_conf.py        ← Piccolo migration config
├── .env                   ← environment variables
├── requirements.txt
└── README.md

2. Create an app

fq startapp blog

Generated app structure:

blog/
├── __init__.py
├── models.py       ← your Piccolo Table models
├── routes.py       ← FastAPI router (like views.py + urls.py)
├── admin.py        ← register models with the admin
├── schemas.py      ← Pydantic request/response schemas
├── services.py     ← business logic layer
├── tests.py        ← pytest tests
├── piccolo_app.py  ← Piccolo migration config for this app
└── migrations/     ← auto-generated migration files

3. Register the app

# myblog/settings.py
INSTALLED_APPS = [
    "blog",
]

4. Define models

# blog/models.py
from fastique import Model, CharField, TextField, BooleanField, DateTimeField, ForeignKey
from fastique.auth.models import User
from datetime import datetime, timezone

class Article(Model):
    title = CharField(max_length=200)
    slug = CharField(max_length=220, unique=True)
    body = TextField()
    author = ForeignKey(references=User)
    is_published = BooleanField(default=False)
    created_at = DateTimeField(default=datetime.now(tz=timezone.utc))

    class Meta:
        tablename = "blog_articles"
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

5. Register with admin

# blog/admin.py
from fastique import register, ModelAdmin
from blog.models import Article

@register(Article)
class ArticleAdmin(ModelAdmin):
    list_display = ["id", "title", "author", "is_published", "created_at"]
    list_filter = ["is_published"]
    search_fields = ["title", "body"]
    ordering = ["-created_at"]
    readonly_fields = ["created_at"]

6. Write routes

# blog/routes.py
from fastique import Router, login_required
from starlette.requests import Request
from starlette.responses import JSONResponse
from blog.models import Article
from blog.schemas import ArticleSchema, ArticleCreateSchema

router = Router(prefix="/blog", tags=["Blog"])

@router.get("/")
async def list_articles(request: Request, page: int = 1) -> JSONResponse:
    articles = await Article.select().output(load_json=True)
    return JSONResponse({"articles": articles})

@router.get("/{slug}")
async def get_article(slug: str) -> JSONResponse:
    rows = await Article.select().where(Article.slug == slug).output(load_json=True)
    if not rows:
        return JSONResponse({"detail": "Not found."}, status_code=404)
    return JSONResponse(rows[0])

@router.post("/", status_code=201)
@login_required
async def create_article(request: Request, data: ArticleCreateSchema) -> JSONResponse:
    article = Article(**data.model_dump())
    await article.save().run()
    return JSONResponse({"id": article.id, "detail": "Created."})

7. Run migrations

fq makemigrations blog   # generate migration file
fq migrate blog          # apply it

8. Create a superuser

fq createsuperuser
# Username: admin
# Email: admin@example.com
# Password: ••••••••

9. Start the server

fq runserver
╭──────────────────────────────────────────╮
│  Starting Fastique development server    │
│  http://127.0.0.1:8000                   │
│  Admin: http://127.0.0.1:8000/admin/     │
│  API Docs: http://127.0.0.1:8000/docs    │
│  Quit with CTRL+C                        │
╰──────────────────────────────────────────╯

Models

from fastique import (
    Model,
    CharField, TextField, IntegerField, FloatField, BooleanField,
    DateField, DateTimeField, ForeignKey, ManyToManyField,
    EmailField, URLField, UUIDField, JSONField,
)

QuerySet API

# ORM-style (async)
articles = await Article.objects.all()
article  = await Article.objects.get(id=1)          # raises Http404 if missing
articles = await Article.objects.filter(is_published=True)
article, created = await Article.objects.get_or_create(slug="hello", defaults={"title": "Hello"})
article  = await Article.objects.create(title="New Post", slug="new-post")

# Piccolo native (more expressive)
articles = await Article.select().where(Article.is_published == True).order_by(Article.created_at, ascending=False).output(load_json=True)

Class-Based Views

from fastique import ListView, DetailView, CreateView, UpdateView, DeleteView

class ArticleList(ListView):
    model = Article
    template_name = "blog/list.html"
    paginate_by = 10
    ordering = "-created_at"

class ArticleDetail(DetailView):
    model = Article
    template_name = "blog/detail.html"
    lookup_field = "slug"

# Register with router:
router.get("/")(ArticleList.as_route())
router.get("/{slug}")(ArticleDetail.as_route())

Authentication

Session-based

POST /auth/login      {"username": "...", "password": "..."}
POST /auth/logout
GET  /auth/me
POST /auth/register   {"username": "...", "email": "...", "password": "..."}

JWT / Token

POST /auth/token      {"username": "...", "password": "..."}
# Returns: {"access_token": "eyJ...", "token_type": "bearer"}

# Use in headers:
Authorization: Bearer eyJ...

Decorators

from fastique import login_required, staff_required, superuser_required, permission_required

@router.get("/dashboard")
@login_required
async def dashboard(request): ...

@router.get("/admin-only")
@staff_required
async def admin_view(request): ...

@router.delete("/nuke")
@superuser_required
async def dangerous(request): ...

@router.post("/publish")
@permission_required("blog.publish_article")
async def publish(request): ...

Settings Reference

# settings.py

SECRET_KEY = "your-secret-key"
DEBUG = True
ALLOWED_HOSTS = ["*"]

INSTALLED_APPS = ["blog", "shop"]

DATABASES = {
    "default": {
        "ENGINE": "sqlite",          # or "postgresql"
        "NAME": "db.sqlite3",
    }
}

# PostgreSQL:
# DATABASES = {
#     "default": {
#         "ENGINE": "postgresql",
#         "NAME": "mydb",
#         "USER": "postgres",
#         "PASSWORD": "secret",
#         "HOST": "localhost",
#         "PORT": 5432,
#     }
# }

TEMPLATES = [{"BACKEND": "jinja2", "DIRS": ["templates"], "APP_DIRS": True}]

STATIC_URL = "/static/"
STATIC_ROOT = "staticfiles"
MEDIA_URL = "/media/"
MEDIA_ROOT = "media"

CORS_ALLOW_ALL_ORIGINS = True    # dev only
CORS_ALLOWED_ORIGINS = ["https://myfrontend.com"]

ADMIN_URL = "admin/"
ADMIN_SITE_HEADER = "My Site Admin"

ACCESS_TOKEN_EXPIRE_MINUTES = 1440  # 24 hours

CLI Reference

fq startproject <name>          Create a new project
fq startapp <name>              Create a new app
fq runserver [host:port]        Run dev server (default: 127.0.0.1:8000)
fq makemigrations [app]         Generate migration files
fq migrate [app]                Apply pending migrations
fq createsuperuser              Create a superuser interactively
fq shell                        Interactive Python shell with context
fq dbshell                      Open the database CLI
fq collectstatic                Copy static files to STATIC_ROOT
fq check                        Run system checks
fq showmigrations [app]         Show migration status
fq routes                       List all registered routes
fq test [args...]               Run pytest
fq version                      Show version info

Project Layout (full example)

myproject/
├── manage.py
├── myproject/
│   ├── settings.py
│   ├── asgi.py
│   └── urls.py
├── blog/
│   ├── models.py
│   ├── routes.py
│   ├── admin.py
│   ├── schemas.py
│   ├── services.py
│   ├── tests.py
│   ├── piccolo_app.py
│   ├── migrations/
│   └── templates/
│       └── blog/
│           ├── list.html
│           └── detail.html
├── templates/
│   └── base.html
├── static/
├── media/
├── piccolo_conf.py
├── .env
└── requirements.txt

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastique-0.1.0.tar.gz (33.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastique-0.1.0-py3-none-any.whl (46.2 kB view details)

Uploaded Python 3

File details

Details for the file fastique-0.1.0.tar.gz.

File metadata

  • Download URL: fastique-0.1.0.tar.gz
  • Upload date:
  • Size: 33.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fastique-0.1.0.tar.gz
Algorithm Hash digest
SHA256 61a24e383cc88b170a05cd988071a147e5e31d2b656b005c442205b59c9bfdfb
MD5 06c43bd5134b0d3862e526cd3af1bfee
BLAKE2b-256 63fd91cc730138e3099fc0e650e04322643b78c585248192e9540693d7160e7a

See more details on using hashes here.

File details

Details for the file fastique-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fastique-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 46.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for fastique-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fadd265af33d929899fd391ee25a2a34a0100f22b8b3a012aa980eaba87c3034
MD5 03da534b7be8a25f93da727f58ea76c3
BLAKE2b-256 c343773b3b581588a20c5436d4014069429430ae28d905799a4da3d9bfb1b98c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page