📝 Add mkdocs
This commit is contained in:
parent
a3d9862c4e
commit
91baf968d7
10 changed files with 498 additions and 380 deletions
34
docs/apps.md
Normal file
34
docs/apps.md
Normal 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
30
docs/index.md
Normal 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
22
docs/migrations.md
Normal 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
102
docs/models.md
Normal 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
34
docs/projects.md
Normal 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
68
docs/routes.md
Normal 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()
|
||||
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue