📝 Add mkdocs
This commit is contained in:
parent
a3d9862c4e
commit
91baf968d7
10 changed files with 498 additions and 380 deletions
391
README.md
391
README.md
|
|
@ -1,401 +1,40 @@
|
|||
# OhMyAPI
|
||||
|
||||
> Think: Django RestFramework, but less clunky and 100% async.
|
||||
OhMyAPI is a web-application scaffolding framework and management layer built around `FastAPI`, `TortoiseORM` and `Aerich` migrations.
|
||||
|
||||
OhMyAPI is a Django-flavored web-application scaffolding framework and management layer,
|
||||
built around FastAPI and TortoiseORM and is thus 100% async.
|
||||
> *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
|
||||
- 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
|
||||
- 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
|
||||
- AVOID adding any abstractions on top, unless they make things extremely convenient
|
||||
|
||||
---
|
||||
|
||||
## Getting started
|
||||
|
||||
**Creating a Project**
|
||||
## Installation
|
||||
|
||||
```
|
||||
pipx install ohmyapi
|
||||
ohmyapi startproject myproject
|
||||
cd myproject
|
||||
```
|
||||
|
||||
This will create the following directory structure:
|
||||
|
||||
```
|
||||
myproject/
|
||||
- pyproject.toml
|
||||
- README.md
|
||||
- settings.py
|
||||
```
|
||||
## Docs
|
||||
|
||||
Run your project with:
|
||||
|
||||
```
|
||||
ohmyapi serve
|
||||
```
|
||||
|
||||
In your browser go to:
|
||||
- http://localhost:8000/docs
|
||||
|
||||
**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`:
|
||||
|
||||
```python
|
||||
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`:
|
||||
|
||||
```python
|
||||
from ohmyapi.router import APIRouter, HTTPException, HTTPStatus
|
||||
from ohmyapi.db.exceptions import DoesNotExist
|
||||
|
||||
from typing import List
|
||||
|
||||
from .models import Tournament
|
||||
|
||||
# OhMyAPI will automatically pick up all instances of `fastapi.APIRouter` and
|
||||
# add their routes to the main project router.
|
||||
#
|
||||
# Note:
|
||||
# Use prefixes wisely to avoid cross-app namespace-collisions!
|
||||
# Tags improve the UX of the OpenAPI docs at /docs.
|
||||
#
|
||||
tournament_router = APIRouter(prefix="/tournament", tags=['Tournament'])
|
||||
|
||||
|
||||
@tournament_router.get("/", response_model=List[Tournament.Schema()])
|
||||
async def list():
|
||||
queryset = Tournament.all()
|
||||
return await Tournament.Schema.model.from_queryset(queryset)
|
||||
|
||||
|
||||
@tournament_router.post("/", status_code=HTTPStatus.CREATED)
|
||||
async def post(tournament: Tournament.Schema(readonly=True)):
|
||||
queryset = Tournament.create(**payload.model_dump())
|
||||
return await Tournament.Schema().from_queryset(queryset)
|
||||
|
||||
|
||||
@tournament_router.get("/:id", response_model=Tournament.Schema())
|
||||
async def get(id: str):
|
||||
try:
|
||||
queryset = Tournament.get(id=id)
|
||||
return await Tournament.Schema().from_queryset_single(tournament)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=404, detail="not found")
|
||||
|
||||
|
||||
@tournament_router.delete("/:id")
|
||||
async def delete(id: str):
|
||||
try:
|
||||
tournament = await Tournament.get(id=id)
|
||||
return await Tournament.Schema.model.from_queryset(tournament.delete())
|
||||
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:
|
||||
|
||||
```python
|
||||
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`:
|
||||
|
||||
```python
|
||||
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().from_queryset(queryset)
|
||||
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### Model-Level Permissions
|
||||
|
||||
Use Tortoise's `Manager` to implement model-level permissions.
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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().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`
|
||||
```
|
||||
|
||||
```python
|
||||
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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}}
|
||||
```
|
||||
See: `docs/`
|
||||
|
||||
- [Projects](docs/projects.md)
|
||||
- [Apps](docs/apps.md)
|
||||
- [Models](docs/models.md)
|
||||
- [Migrations](docs/migrations.md)
|
||||
- [Routes](docs/routes.md)
|
||||
|
|
|
|||
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()
|
||||
|
||||
```
|
||||
9
mkdocs.yml
Normal file
9
mkdocs.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
site_name: OhMyAPI Docs
|
||||
theme: readthedocs
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Projects: projects.md
|
||||
- Apps: apps.md
|
||||
- Models: models.md
|
||||
- Migrations: migrations.md
|
||||
- Routes: routes.md
|
||||
187
poetry.lock
generated
187
poetry.lock
generated
|
|
@ -519,6 +519,24 @@ all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>
|
|||
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||
standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
version = "2.1.0"
|
||||
description = "Copy your docs directly to the gh-pages branch."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
|
||||
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.8.1"
|
||||
|
||||
[package.extras]
|
||||
dev = ["flake8", "markdown", "twine", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
|
|
@ -649,7 +667,7 @@ version = "3.1.6"
|
|||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
|
|
@ -661,6 +679,22 @@ MarkupSafe = ">=2.0"
|
|||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.9"
|
||||
description = "Python implementation of John Gruber's Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"},
|
||||
{file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
|
||||
testing = ["coverage", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.0.0"
|
||||
|
|
@ -691,7 +725,7 @@ version = "3.0.2"
|
|||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
|
||||
|
|
@ -783,6 +817,66 @@ files = [
|
|||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mergedeep"
|
||||
version = "1.3.4"
|
||||
description = "A deep merge function for 🐍."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
|
||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.6.1"
|
||||
description = "Project documentation with Markdown."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"},
|
||||
{file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
|
||||
ghp-import = ">=1.0"
|
||||
jinja2 = ">=2.11.1"
|
||||
markdown = ">=3.3.6"
|
||||
markupsafe = ">=2.0.1"
|
||||
mergedeep = ">=1.3.4"
|
||||
mkdocs-get-deps = ">=0.2.0"
|
||||
packaging = ">=20.5"
|
||||
pathspec = ">=0.11.1"
|
||||
pyyaml = ">=5.1"
|
||||
pyyaml-env-tag = ">=0.1"
|
||||
watchdog = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["babel (>=2.9.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-get-deps"
|
||||
version = "0.2.0"
|
||||
description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"},
|
||||
{file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mergedeep = ">=1.3.4"
|
||||
platformdirs = ">=2.2.0"
|
||||
pyyaml = ">=5.1"
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.1.0"
|
||||
|
|
@ -1138,6 +1232,21 @@ files = [
|
|||
{file = "pypika_tortoise-0.6.2.tar.gz", hash = "sha256:f95ab59d9b6454db2e8daa0934728458350a1f3d56e81d9d1debc8eebeff26b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
|
||||
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
|
|
@ -1183,7 +1292,7 @@ version = "6.0.3"
|
|||
description = "YAML parser and emitter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"},
|
||||
{file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"},
|
||||
|
|
@ -1253,6 +1362,21 @@ files = [
|
|||
{file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml-env-tag"
|
||||
version = "1.1"
|
||||
description = "A custom YAML tag for referencing environment variables in YAML files."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"},
|
||||
{file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyyaml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
|
|
@ -1318,6 +1442,18 @@ files = [
|
|||
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
|
|
@ -1494,6 +1630,49 @@ h11 = ">=0.8"
|
|||
[package.extras]
|
||||
standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "6.0.0"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"},
|
||||
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"},
|
||||
{file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"},
|
||||
{file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"},
|
||||
{file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"},
|
||||
{file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"},
|
||||
{file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"},
|
||||
{file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"},
|
||||
{file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"},
|
||||
{file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"},
|
||||
{file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"},
|
||||
{file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"},
|
||||
{file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"},
|
||||
{file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"},
|
||||
{file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"},
|
||||
{file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"},
|
||||
{file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"},
|
||||
{file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"},
|
||||
{file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"},
|
||||
{file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"},
|
||||
{file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"},
|
||||
{file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"},
|
||||
{file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
watchmedo = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.14"
|
||||
|
|
@ -1512,4 +1691,4 @@ auth = ["argon2-cffi", "crypto", "passlib", "pyjwt", "python-multipart"]
|
|||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.11"
|
||||
content-hash = "c765d9f42a4d8bee26474bda7e19f0b6fdd43833688d0db781611090e9ee3b99"
|
||||
content-hash = "3d301460081dada359d425d69feefc63c1e5135aa64b6f000f554bfc1231febd"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ dependencies = [
|
|||
ipython = ">=9.5.0,<10.0.0"
|
||||
black = "^25.9.0"
|
||||
isort = "^6.0.1"
|
||||
mkdocs = "^1.6.1"
|
||||
|
||||
[project.optional-dependencies]
|
||||
auth = ["passlib", "pyjwt", "crypto", "argon2-cffi", "python-multipart"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue