📝 Add mkdocs

This commit is contained in:
Brian Wiborg 2025-10-02 00:49:53 +02:00
parent a3d9862c4e
commit 91baf968d7
No known key found for this signature in database
10 changed files with 498 additions and 380 deletions

34
docs/apps.md Normal file
View file

@ -0,0 +1,34 @@
# Apps
Apps are a way to group database models and API routes that contextually belong together.
For example, OhMyAPI comes bundled with an `auth` app that carries a `User` and `Group` model and provides API endpoints for JWT authentication.
Apps help organizing projects by isolating individual components (or "features") from one another.
## Create an App
Create a new app by: `ohmyapi startapp <name>`, i.e.:
```
ohmyapi startapp restaurant
```
This will create the following directory structure:
```
myproject/
- restaurant/
- __init__.py
- models.py
- routes.py
- pyproject.toml
- README.md
- settings.py
```
Add `restaurant` to your `INSTALLED_APPS` in `settings.py` and restart you project runtime:
```
ohmyapi serve
```

30
docs/index.md Normal file
View file

@ -0,0 +1,30 @@
# Welcome to OhMyAPI
OhMyAPI is a web-application scaffolding framework and management layer built around `FastAPI`, `TortoiseORM` and `Aerich` migrations.
> *Think: Django RestFramework, but less clunky and 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, Aerich migrations and Pydantic into 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
## Installation
```
pipx install ohmyapi
```

22
docs/migrations.md Normal file
View file

@ -0,0 +1,22 @@
# Migrations
OhMyAPI uses [Aerich](https://github.com/tortoise/aerich) - a database migrations tool for TortoiseORM.
## Making migrations
Whenever you add, remove or change fields of a database model, you need to create a migration for the change.
```
ohmyapi makemigrations [ <app> ] # no app indicates all INSTALLED_APPS
```
This will create a `migrations/` directory with subdirectories for each of your apps.
## Migrating
When the migrations are create, they need to be applied:
```
ohmyapi migrate [ <app> ] # no app indicates all INSTALLED_APPS
```

102
docs/models.md Normal file
View file

@ -0,0 +1,102 @@
# Models
OhMyAPI uses [Tortoise](https://tortoise.github.io/) - an easy-to-use asyncio ORM (Object Relational Mapper) inspired by Django.
Models are exposed via a Python module named `models` in the app's directory.
OhMyAPI auto-detects all models exposed this way.
If the `models` module is a package, OhMyAPI will search through its submodules recursively.
## Writing models
### Your first simple model
```python
from ohmyapi.db import Model, field
class Restaurant(Model):
id: int = field.IntField(pk=True)
name: str = field.CharField(max_length=255)
description: str = field.TextField()
location: str = field.CharField(max_length=255)
```
### ForeignKeyRelations
You can define relationships between models.
```python
from ohmyapi.db import Model, field
from decimal import Decimal
class Restaurant(Model):
id: int = field.IntField(pk=True)
name: str = field.CharField(max_length=255)
description: str = field.TextField()
location: str = field.CharField(max_length=255)
class Dish(Model):
id: int = field.IntField(pk=True)
restaurant: field.ForeignKeyRelation[Restaurant] = field.ForeignKeyField(
"restaurant.Restaurant",
related_name="dishes",
)
name: str = field.CharField(max_length=255)
price: Decimal = field.DecimalField(max_digits=10, decimal_places=2)
class Order(Model):
id: int = field.IntField(pk=True)
restaurant: field.ForeignKeyRelation[Restaurant] = field.ForeignKeyField(
'restaurant.Restaurant',
related_name="dishes",
)
dishes: field.ManyToManyRelation[Dish] = field.ManyToManyField(
"restaurant.Dish",
relatated_name="orders",
through="dishesordered",
)
```
## Pydantic Schema Validation
Each model has a builtin `Schema` class that provides easy access to Pydantic models for schema validation.
Each model provides a default schema and a readonly schema, which you can obtain via the `get` method or by calling Schema() directly.
The default schema contains the full collection of fields of the Tortoise model.
The readonly schema excludes the primary-key field, as well as all readonly fields.
```python
In [1]: from restaurant.models import Restaurant
In [2]: Restaurant.Schema.get()
Out[2]: tortoise.contrib.pydantic.creator.RestaurantSchema
In [3]: Restaurant.Schema.get(readonly=True)
Out[3]: tortoise.contrib.pydantic.creator.RestaurantSchemaReadonly
In [4]: data = {
...: "name": "My Pizzeria",
...: "description": "Awesome Pizza!",
...: "location": "Berlin",
...: }
In [5]: Restaurant.Schema.get(readonly=True)(**data)
Out[5]: RestaurantSchemaReadonly(name='My Pizzeria', description='Awesome Pizza!', location='Berlin')
In [6]: Restaurant(**_.model_dump())
Out[6]: <Restaurant>
```
You can customize the fields to be include in the Pydantic schema:
```python
class MyModel(Model):
[...]
class Schema:
include: List[str] = [] # list of fields to include
exclude: List[str] = [] # list of fields to exclude
```

34
docs/projects.md Normal file
View file

@ -0,0 +1,34 @@
# Projects
OhMyAPI organizes projects in a diretory tree.
The root directory contains the `settings.py`, which carries global configuration for your project, such as your `DATABASE_URL` and `INSTALLED_APPS`.
Each project is organized into individual apps, which in turn may provide some database models and API handlers.
Each app is isolated in its own subdirectory within your project.
You can control which apps to install and load via `INSTALLED_APPS` in your `settings.py`.
## Create a Project
To create a projects, simply run:
```
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: [http://localhost:8000/docs](http://localhost:8000/docs)

68
docs/routes.md Normal file
View file

@ -0,0 +1,68 @@
# Routes
OhMyAPI uses [FastAPI](https://fastapi.tiangolo.com/) - a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.
Routes are exposed via a module named `routes` in the app's directory.
OhMyAPI auto-detects all `fastapi.APIRouter` instances exposed this way.
If the `routes` module is a package, OhMyAPI will search through its submodules recursively.
When creating an app via `startapp`, OhMyAPI will provide CRUD boilerplate to help your get started.
## Example CRUD API endpoints
```python
from ohmyapi.db.exceptions import DoesNotExist
from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
from .models import Restaurant
from typing import List
router = APIRouter(prefix="/restaurant", tags=['restaurant'])
@router.get("/", response_model=List[Restaurant])
async def list():
"""List all restaurants."""
queryset = Restaurant.all()
schema = Restaurant.Schema()
return await schema.from_queryset(queryset)
# or in one line:
# return await Restaurant.Schema().from_queryset(Restaurant.all())
@router.post("/", status_code=HTTPStatus.CREATED)
async def post(restaurant: Restaurant.Schema(readonly=True)):
"""Create a new restaurant."""
return await Restaurant(**restaurant.model_dump()).create()
@router.get("/{id}", response_model=Restaurant)
async def get(id: str):
"""Get restaurant by ID."""
return await Restaurant.Schema().from_queryset(Restaurant.get(id=id))
@router.put("/{id}", status_code=HTTPStatus.ACCEPTED)
async def put(restaurant: Restaurant):
"""Update restaurant."""
try:
db_restaurant = await Restaurant.get(id=id)
except DoesNotExist:
return HTTPException(status_code=HTTPStatus.NOT_FOUND)
db_restaurant.update_from_dict(restaurant.model_dump())
return await db_restaurant.save()
@router.delete("/{id}", status_code=HTTPStatus.ACCEPTED)
async def delete(id: str):
try:
db_restaurant = await Restaurant.get(id=id)
except DoesNotExist:
return HTTPException(status_code=HTTPStatus.NOT_FOUND)
return await db_restaurant.delete()
```