Skip to main content

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, 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.Meta class for model configuration
  • Easily convert your query results to pydantic models via Model.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

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


Download files

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

Source Distribution

ohmyapi-0.1.23.tar.gz (15.2 kB view details)

Uploaded Source

Built Distribution

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

ohmyapi-0.1.23-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file ohmyapi-0.1.23.tar.gz.

File metadata

  • Download URL: ohmyapi-0.1.23.tar.gz
  • Upload date:
  • Size: 15.2 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

Hashes for ohmyapi-0.1.23.tar.gz
Algorithm Hash digest
SHA256 48e5e0d5a17da0f6c4e2c8af8e6e85026b93d366897a3895583ce112f441b168
MD5 a825f4c05d36778783530d3f05104467
BLAKE2b-256 3e215cec381ca2ccfce5ffb1a5850f06ee1d63d2b16a8f95e2fb52a2126ad023

See more details on using hashes here.

File details

Details for the file ohmyapi-0.1.23-py3-none-any.whl.

File metadata

  • Download URL: ohmyapi-0.1.23-py3-none-any.whl
  • Upload date:
  • Size: 19.5 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

Hashes for ohmyapi-0.1.23-py3-none-any.whl
Algorithm Hash digest
SHA256 31db0633e3f25b00782f01b0bdcd701957886447882bb325529c6c95e460bd8a
MD5 252a057956fe2844f92010bb7a3a53e6
BLAKE2b-256 63aa9bd3fb935aa9e02845738f6aaba39adbaf97ea23f2b30138fa74105c8584

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