From c56ea6451e38255002e563ca16b1d424765b2e79 Mon Sep 17 00:00:00 2001 From: Brian Wiborg Date: Mon, 29 Sep 2025 18:22:43 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Make=20Model.Schema=20callable?= =?UTF-8?q?=20with=20readonly=20arg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++++++------- src/ohmyapi/builtin/auth/routes.py | 4 ++-- src/ohmyapi/builtin/demo/routes.py | 18 +++++++++--------- src/ohmyapi/core/templates/app/routes.py.j2 | 8 ++++---- src/ohmyapi/db/model/model.py | 5 +++++ 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6996844..44ee001 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ Next, create your endpoints in `tournament/routes.py`: 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 @@ -137,23 +139,23 @@ from .models import Tournament tournament_router = APIRouter(prefix="/tournament", tags=['Tournament']) -@tournament_router.get("/") +@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): +async def post(tournament: Tournament.Schema(readonly=True)): queryset = Tournament.create(**payload.model_dump()) - return await Tournament.Schema.model.from_queryset(queryset) + return await Tournament.Schema().from_queryset(queryset) -@tournament_router.get("/:id") +@tournament_router.get("/:id", response_model=Tournament.Schema()) async def get(id: str): try: queryset = Tournament.get(id=id) - return await Tournament.Schema.model.from_queryset_single(tournament) + return await Tournament.Schema().from_queryset_single(tournament) except DoesNotExist: raise HTTPException(status_code=404, detail="not found") @@ -273,7 +275,7 @@ 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) + return await Tournament.Schema().from_queryset(queryset) ... @@ -315,7 +317,7 @@ 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) + return await Tournament.Schema().from_queryset(queryset) ``` ## Shell diff --git a/src/ohmyapi/builtin/auth/routes.py b/src/ohmyapi/builtin/auth/routes.py index 40a65d5..27b8193 100644 --- a/src/ohmyapi/builtin/auth/routes.py +++ b/src/ohmyapi/builtin/auth/routes.py @@ -207,7 +207,7 @@ async def introspect(token: Dict = Depends(get_token)): return token -@router.get("/me", response_model=User.Schema.model) +@router.get("/me", response_model=User.Schema()) async def me(user: User = Depends(get_current_user)): """Return the currently authenticated user.""" - return await User.Schema.model.from_tortoise_orm(user) + return await User.Schema().from_tortoise_orm(user) diff --git a/src/ohmyapi/builtin/demo/routes.py b/src/ohmyapi/builtin/demo/routes.py index 212c2be..0022039 100644 --- a/src/ohmyapi/builtin/demo/routes.py +++ b/src/ohmyapi/builtin/demo/routes.py @@ -8,29 +8,29 @@ from typing import List # 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="/tournemant") +router = APIRouter(prefix="/tournament") @router.get( - "/", tags=["tournament"], response_model=List[models.Tournament.Schema.model] + "/", tags=["tournament"], response_model=List[models.Tournament.Schema()] ) async def list(): """List all tournaments.""" - return await models.Tournament.Schema.model.from_queryset(models.Tournament.all()) + return await models.Tournament.Schema().from_queryset(models.Tournament.all()) @router.post("/", tags=["tournament"], status_code=HTTPStatus.CREATED) -async def post(tournament: models.Tournament.Schema.readonly): +async def post(tournament: models.Tournament.Schema(readonly=True)): """Create tournament.""" - return await models.Tournament.Schema.model.from_queryset( + return await models.Tournament.Schema().from_queryset( models.Tournament.create(**tournament.model_dump()) ) -@router.get("/{id}", tags=["tournament"], response_model=models.Tournament.Schema.model) +@router.get("/{id}", tags=["tournament"], response_model=models.Tournament.Schema()) async def get(id: str): """Get tournament by id.""" - return await models.Tournament.Schema.model.from_queryset( + return await models.Tournament.Schema().from_queryset( models.Tournament.get(id=id) ) @@ -43,12 +43,12 @@ async def get(id: str): ) async def put(tournament: models.Tournament.Schema.model): """Update tournament.""" - return await models.Tournament.Schema.model.from_queryset( + return await models.Tournament.Schema().from_queryset( models.Tournament.update(**tournament.model_dump()) ) -@router.delete("/{id}", tags=["tournament"]) +@router.delete("/{id}", status_code=HTTPStatus.ACCEPTED, tags=["tournament"]) async def delete(id: str): try: tournament = await models.Tournament.get(id=id) diff --git a/src/ohmyapi/core/templates/app/routes.py.j2 b/src/ohmyapi/core/templates/app/routes.py.j2 index 3edc01e..444f251 100644 --- a/src/ohmyapi/core/templates/app/routes.py.j2 +++ b/src/ohmyapi/core/templates/app/routes.py.j2 @@ -14,16 +14,16 @@ from typing import List router = APIRouter(prefix="/{{ app_name }}", tags=['{{ app_name }}']) -@router.get("/") +@router.get("/", response_model=List) async def list(): """List all ...""" return [] -@router.post("/") +@router.post("/", status_code=HTTPStatus.CREATED) async def post(): """Create ...""" - return HTTPException(status_code=HTTPStatus.CREATED) + raise HTTPException(status_code=HTTPStatus.IM_A_TEAPOT) @router.get("/{id}") @@ -38,7 +38,7 @@ async def put(id: str): return HTTPException(status_code=HTTPStatus.ACCEPTED) -@router.delete("/{id}") +@router.delete("/{id}", status_code=HTTPStatus.ACCEPTED) async def delete(id: str): return HTTPException(status_code=HTTPStatus.ACCEPTED) diff --git a/src/ohmyapi/db/model/model.py b/src/ohmyapi/db/model/model.py index fd5fad0..04e5d9c 100644 --- a/src/ohmyapi/db/model/model.py +++ b/src/ohmyapi/db/model/model.py @@ -36,6 +36,11 @@ class ModelMeta(type(TortoiseModel)): schema_opts = getattr(new_cls, "Schema", None) class BoundSchema: + def __call__(self, readonly: bool = False): + if readonly: + return self.readonly + return self.model + @property def model(self): """Return a Pydantic model class for serializing results."""