| src/ohmyapi | ||
| tests | ||
| .gitignore | ||
| poetry.lock | ||
| pyproject.toml | ||
| README.md | ||
OhMyAPI
Think: Django RestFramework, but less clunky and 100% async.
OhMyAPI is a Django-flavored web-application scaffolding framework and management layer. Built around FastAPI and TortoiseORM, it is 100% async.
It is blazingly fast, fun to use and comes with batteries included!
Features
- Django-like project-layout and -structure
- Django-like project-level settings.py
- Django-like models via TortoiseORM
- Django-like
Model.Metaclass for model configuration - Easily convert your query results to
pydanticmodels viaModel.Schema - Django-like migrations (
makemigrations&migrate) via Aerich - Django-like CLI tooling (
startproject,startapp,shell,serve, etc) - Various optional builtin apps you can hook into your project
- Highly configurable and customizable
- 100% async
Goals
- combine FastAPI, TortoiseORM and Aerich migrations into a high-productivity web-application framework
- tie everything neatly together into a concise API
- while AVOIDING any additional abstractions ontop of Tortoise's model-system or FastAPI's routing system
Getting started
Creating a Project
pip install ohmyapi
ohmyapi startproject myproject
cd myproject
This will create the following directory structure:
myproject/
- pyproject.toml
- README.md
- settings.py
Run your project with:
ohmyapi serve
In your browser go to:
Creating an App
Create a new app by:
ohmyapi startapp tournament
This will create the following directory structure:
myproject/
- tournament/
- __init__.py
- models.py
- routes.py
- pyproject.toml
- README.md
- settings.py
Add 'tournament' to your INSTALLED_APPS in settings.py.
Models
Write your first model in turnament/models.py:
from ohmyapi.db import Model, field
class Tournament(Model):
id = field.data.UUIDField(primary_key=True)
name = field.TextField()
created = field.DatetimeField(auto_now_add=True)
def __str__(self):
return self.name
class Event(Model):
id = field.data.UUIDField(primary_key=True)
name = field.TextField()
tournament = field.ForeignKeyField('tournament.Tournament', related_name='events')
participants = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
modified = field.DatetimeField(auto_now=True)
prize = field.DecimalField(max_digits=10, decimal_places=2, null=True)
def __str__(self):
return self.name
class Team(Model):
id = field.data.UUIDField(primary_key=True)
name = field.TextField()
def __str__(self):
return self.name
API Routes
Next, create your endpoints in tournament/routes.py:
from ohmyapi.router import APIRouter, HTTPException
from ohmyapi.db.exceptions import DoesNotExist
from .models import Tournament
# Expose your app's routes via `router = fastapi.APIRouter`.
# Use prefixes wisely to avoid cross-app namespace-collisions.
# Tags improve the UX of the OpenAPI docs at /docs.
router = APIRouter(prefix="/tournament", tags=['Tournament'])
@router.get("/")
async def list():
queryset = Tournament.all()
return await Tournament.Schema.many.from_queryset(queryset)
@router.get("/:id")
async def get(id: str):
try:
tournament = await Tournament.get(pk=id)
return await Tournament.Schema.one.form_orm(tournament)
except DoesNotExist:
raise HTTPException(status_code=404, detail="item not found")
...
Migrations
Before we can run the app, we need to create and initialize the database.
Similar to Django, first run:
ohmyapi makemigrations [ <app> ] # no app means all INSTALLED_APPS
This will create a migrations/ folder in you project root.
myproject/
- tournament/
- __init__.py
- models.py
- routes.py
- migrations/
- tournament/
- pyproject.toml
- README.md
- settings.py
Apply your migrations via:
ohmyapi migrate [ <app> ] # no app means all INSTALLED_APPS
Run your project:
ohmyapi serve
Authentication
A builtin auth app is available.
Simply add ohmyapi_auth to your INSTALLED_APPS and define a JWT_SECRET in your settings.py.
Remember to makemigrations and migrate for the necessary tables to be created in the database.
settings.py:
INSTALLED_APPS = [
'ohmyapi_auth',
...
]
JWT_SECRET = "t0ps3cr3t"
After restarting your project you will have access to the ohmyapi_auth app.
It comes with a User and Group model, as well as endpoints for JWT auth.
You can use the models as ForeignKeyField in your application models:
class Team(Model):
[...]
members = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams')
[...]
Remember to run makemigrations and migrate in order for your model changes to take effect in the database.
Create a super-user:
ohmyapi createsuperuser
Permissions
API-Level Permissions
Use FastAPI's Depends pattern to implement API-level access-control.
In your routes.py:
from ohmyapi.router import APIRouter, Depends
from ohmyapi_auth import (
models as auth,
permissions,
)
from .models import Tournament
router = APIRouter(prefix="/tournament", tags=["Tournament"])
@router.get("/")
async def list(user: auth.User = Depends(permissions.require_authenticated)):
queryset = Tournament.all()
return await Tournament.Schema.many.from_queryset(queryset)
...
Model-Level Permissions
Use Tortoise's Manager to implement model-level permissions.
from ohmyapi.db import Manager
from ohmyapi_auth.models import User
class TeamManager(Manager):
async def for_user(self, user: User):
return await self.filter(members=user).all()
class Team(Model):
[...]
class Meta:
manager = TeamManager()
Use the custom manager in your FastAPI route handler:
from ohmyapi.router import APIRouter
from ohmyapi_auth import (
models as auth,
permissions,
)
router = APIRouter(prefix="/tournament", tags=["Tournament"])
@router.get("/teams")
async def teams(user: auth.User = Depends(permissions.require_authenticated)):
queryset = Team.for_user(user)
return await Tournament.Schema.many.from_queryset(queryset)
Shell
Similar to Django, you can attach to an interactive shell with your project already loaded inside.
ohmyapi shell
Python 3.13.7 (main, Aug 15 2025, 12:34:02) [GCC 15.2.1 20250813]
Type 'copyright', 'credits' or 'license' for more information
IPython 9.5.0 -- An enhanced Interactive Python. Type '?' for help.
OhMyAPI Shell | Project: {{ project_name }} [{{ project_path }}]
Find your loaded project singleton via identifier: `p`
In [1]: p
Out[1]: <ohmyapi.core.runtime.Project at 0xdeadbeefc0febabe>
In [2]: p.apps
Out[2]:
{'ohmyapi_auth': App: ohmyapi_auth
Models:
- Group
- User
Routes:
- APIRoute(path='/auth/login', name='login', methods=['POST'])
- APIRoute(path='/auth/refresh', name='refresh_token', methods=['POST'])
- APIRoute(path='/auth/introspect', name='introspect', methods=['GET'])
- APIRoute(path='/auth/me', name='me', methods=['GET']),
'tournament': App: tournament
Models:
- Tournament
- Event
- Team
Routes:
- APIRoute(path='/tournament/', name='list', methods=['GET'])}
In [3]: from tournament.models import Tournament