A Django-like but async web-framework based on FastAPI and TortoiseORM.
Project description
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 and is thus 100% async.
It is blazingly fast, extremely fun to use and comes with batteries included!
Features
- Django-like project structure and application directories
- Django-like per-app migrations (
makemigrations&migrate) via Aerich - Django-like CLI tooling (
startproject,startapp,shell,serve, etc) - Customizable pydantic model serializer built-in
- Various optional built-in apps you can hook into your project
- Highly configurable and customizable
- 100% async
Goals
- combine
FastAPI,TortoiseORM,Aerichmigrations andPydanticinto a high-productivity web-application framework - tie everything neatly together into a concise and straight-forward API
- AVOID adding any abstractions on top, unless they make things extremely convenient
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
from datetime import datetime
from decimal import Decimal
from uuid import UUID
class Tournament(Model):
id: UUID = field.data.UUIDField(primary_key=True)
name: str = field.TextField()
created: datetime = field.DatetimeField(auto_now_add=True)
def __str__(self):
return self.name
class Event(Model):
id: UUID = field.data.UUIDField(primary_key=True)
name: str = field.TextField()
tournament: UUID = field.ForeignKeyField('tournament.Tournament', related_name='events')
participants: field.ManyToManyRelation[Team] = field.ManyToManyField('tournament.Team', related_name='events', through='event_team')
modified: datetime = field.DatetimeField(auto_now=True)
prize: Decimal = field.DecimalField(max_digits=10, decimal_places=2, null=True)
def __str__(self):
return self.name
class Team(Model):
id: UUID = field.data.UUIDField(primary_key=True)
name: str = field.TextField()
def __str__(self):
return self.name
API Routes
Next, create your endpoints in tournament/routes.py:
from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
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.model.from_queryset(queryset)
@router.post("/", status_code=HTTPStatus.CREATED)
async def post(tournament: Tournament.Schema.readonly):
queryset = Tournament.create(**payload.model_dump())
return await Tournament.Schema.model.from_queryset(queryset)
@router.get("/:id")
async def get(id: str):
try:
queryset = Tournament.get(id=id)
return await Tournament.Schema.model.from_queryset_single(tournament)
except DoesNotExist:
raise HTTPException(status_code=404, detail="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:
from ohmyapi.db import Model, field
from ohmyapi_auth.models import User
class Team(Model):
[...]
members: field.ManyToManyRelation[User] = 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.model.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.model.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 0x7f00c43dbcb0>
In [2]: p.apps
Out[2]:
{'ohmyapi_auth': {
"models": [
"Group",
"User"
],
"routes": [
{
"path": "/auth/login",
"name": "login",
"methods": [
"POST"
],
"endpoint": "login",
"response_model": null,
"tags": [
"auth"
]
},
{
"path": "/auth/refresh",
"name": "refresh_token",
"methods": [
"POST"
],
"endpoint": "refresh_token",
"response_model": null,
"tags": [
"auth"
]
},
{
"path": "/auth/introspect",
"name": "introspect",
"methods": [
"GET"
],
"endpoint": "introspect",
"response_model": null,
"tags": [
"auth"
]
},
{
"path": "/auth/me",
"name": "me",
"methods": [
"GET"
],
"endpoint": "me",
"response_model": null,
"tags": [
"auth"
]
}
]
}}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ohmyapi-0.1.24.tar.gz.
File metadata
- Download URL: ohmyapi-0.1.24.tar.gz
- Upload date:
- Size: 15.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.0 CPython/3.13.7 Linux/6.16.8-zen3-1-zen
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
121456efc2508476003a909df530ee0767449aa3c03842d09e09c5e1c3ea1de9
|
|
| MD5 |
0dd7c5a03233edb7fa22466b454c6309
|
|
| BLAKE2b-256 |
1f18d4f816c2c4b6042280cdec6eb433b7a8b51fdb504209aac250ce803bb591
|
File details
Details for the file ohmyapi-0.1.24-py3-none-any.whl.
File metadata
- Download URL: ohmyapi-0.1.24-py3-none-any.whl
- Upload date:
- Size: 19.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.0 CPython/3.13.7 Linux/6.16.8-zen3-1-zen
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
549383241f34e1245b912920d44121d8f95d492a02bc0b874f229e8ce48faf18
|
|
| MD5 |
07b83bddc3b389169d08cb1efcba6d55
|
|
| BLAKE2b-256 |
a2701ab7df8442cd7825f9e3a9c421636fd236bb22fb827decfff54faa878e63
|