fba
35
总安装量
35
周安装量
#5881
全站排名
安装命令
npx skills add https://github.com/fastapi-practices/skills --skill fba
Agent 安装分布
claude-code
28
gemini-cli
26
opencode
25
codex
23
github-copilot
21
cursor
19
Skill 文档
FastAPI Best Architecture Skills
宿¹ææ¡£: https://fastapi-practices.github.io/fastapi_best_architecture_docs/
项ç®ç»æ
backend/
âââ main.py # åºç¨å
¥å£
âââ run.py # å¯å¨èæ¬ï¼IDE è°è¯ï¼
âââ cli.py # CLI å½ä»¤è¡å·¥å
·
âââ app/ # æ ¸å¿ä¸å¡æ¨¡å
â âââ router.py # ä¸»è·¯ç±æ±æ»
â âââ admin/ # 管çåå°åºç¨
â â âââ api/v1/ # API è·¯ç±
â â â âââ auth/ # è®¤è¯æ¨¡å
â â â âââ sys/ # ç³»ç»æ¨¡å
â â â âââ log/ # æ¥å¿æ¨¡å
â â â âââ monitor/ # çæ§æ¨¡å
â â âââ crud/ # æ°æ®è®¿é®å±
â â âââ model/ # æ°æ®åºæ¨¡å
â â âââ schema/ # Pydantic 模å
â â âââ service/ # ä¸å¡é»è¾å±
â â âââ tests/ # åå
æµè¯
â âââ task/ # 弿¥ä»»å¡ï¼Celeryï¼
âââ common/ # éç¨æ¨¡å
â âââ exception/ # å¼å¸¸å¤ç
â âââ response/ # ååºæ¨¡å
â âââ security/ # å®å
¨æ¨¡åï¼JWTãRBACï¼
â âââ model.py # 模ååºç±»
â âââ schema.py # Schema åºç±»
âââ core/ # æ ¸å¿é
ç½®
â âââ conf.py # å
¨å±é
ç½®
â âââ registrar.py # åºç¨æ³¨å
âââ database/ # æ°æ®åºé
ç½®
â âââ db.py # SQLAlchemy
â âââ redis.py # Redis
âââ middleware/ # ä¸é´ä»¶
âââ plugin/ # æä»¶ç³»ç»
âââ utils/ # å·¥å
·å½æ°
âââ locale/ # å½é
åè¯è¨å
âââ alembic/ # æ°æ®åºè¿ç§»
âââ sql/ # SQL åå§åèæ¬
æ ¸å¿æ¶æ
项ç®éç¨ä¼ªä¸å±æ¶æï¼
API â Service â CRUD â Model
â
Schemaï¼æ°æ®ä¼ è¾ï¼
| å±çº§ | èè´£ |
|---|---|
| API | è·¯ç±å¤çãåæ°éªè¯ãååºè¿å |
| Schema | æ°æ®ä¼ è¾å¯¹è±¡ï¼è¯·æ±/ååºæ°æ®ç»æå®ä¹ |
| Service | ä¸å¡é»è¾ãæ°æ®å¤çãå¼å¸¸å¤çï¼éææ¹æ³ + * 强å¶å
³é®ååæ°ï¼ |
| CRUD | æ°æ®åºæä½ï¼ç»§æ¿ CRUDPlusï¼ |
| Model | ORM 模åï¼ç»§æ¿ Baseï¼ |
å¼åæµç¨
- å®ä¹æ°æ®åºæ¨¡åï¼modelï¼
- å®ä¹æ°æ®éªè¯æ¨¡åï¼schemaï¼
- å®ä¹è·¯ç±ï¼routerï¼
- ç¼åä¸å¡é»è¾ï¼serviceï¼
- ç¼åæ°æ®åºæä½ï¼crudï¼
è¯¦ç»æå
| ææ¡£ | å 容 |
|---|---|
| references/naming.md | CRUD/Schema å½åè§è |
| references/model.md | æ°æ®åºæ¨¡åãåæ®µç±»åãè¿ç§» |
| references/schema.md | Schema å®ä¹è§è |
| references/api.md | è·¯ç±ç»ç»ãååºè§èãè®¤è¯æé |
| references/plugin.md | æä»¶å¼å宿´æå |
| references/coding-style.md | ç¼ç 飿 ¼ãææ¡£ã注éè§è |
| references/config.md | å ¨å±é 置项说æ |
å¿«éåè
äºå¡å¤ç
# åªè¯»æ¥è¯¢
@router.get('/users')
async def get_users(db: CurrentSession): ...
# å¢å æ¹ï¼èªå¨äºå¡ï¼
@router.post('/users')
async def create_user(db: CurrentSessionTransaction, obj: CreateUserParam): ...
ååºæ ¼å¼
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
return response_base.success() # æ æ°æ®
return response_base.success(data=user) # å¸¦æ°æ®
å¼å¸¸å¤ç
from backend.common.exception import errors
raise errors.NotFoundError(msg='ç¨æ·ä¸åå¨') # 404
raise errors.RequestError(msg='åæ°é误') # 400
raise errors.ForbiddenError(msg='æ æè®¿é®') # 403
raise errors.ConflictError(msg='ç¨æ·åå·²åå¨') # 409
raise errors.ServerError(msg='æå¡å¼å¸¸') # 500
JWT 认è¯
from backend.common.security.jwt import DependsJwtAuth
@router.get('/users', dependencies=[DependsJwtAuth])
async def get_users(): ...
RBAC æé
from backend.common.security.permission import RequestPermission
from backend.common.security.rbac import DependsRBAC
@router.post('/users', dependencies=[Depends(RequestPermission('sys:user:add')), DependsRBAC])
async def create_user(): ...
ä»£ç æ¨¡æ¿
Schema 屿¨¡æ¿
from pydantic import Field
from backend.common.schema import SchemaBase
class ItemSchemaBase(SchemaBase):
"""åºç¡æ¨¡å"""
name: str = Field(description='åç§°')
status: int = Field(default=1, description='ç¶æ')
class CreateItemParam(ItemSchemaBase):
"""åå»ºåæ°"""
pass
class UpdateItemParam(SchemaBase):
"""æ´æ°åæ°"""
name: str | None = Field(None, description='åç§°')
status: int | None = Field(None, description='ç¶æ')
class GetItemDetail(ItemSchemaBase):
"""Xxx详æ
"""
id: int = Field(description='ID')
CRUD 屿¨¡æ¿
from sqlalchemy import Select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy_crud_plus import CRUDPlus
class CRUDItem(CRUDPlus[Item]):
"""Xxx æ°æ®åºæä½ç±»"""
async def get(self, db: AsyncSession, pk: int) -> Item | None:
"""
è·å Xxx
:param db: æ°æ®åºä¼è¯
:param pk: Xxx ID
:return:
"""
return await self.select_model(db, pk)
async def get_select(self, name: str | None, status: int | None) -> Select:
"""
è·å Xxx å表æ¥è¯¢è¡¨è¾¾å¼
:param name: åç§°
:param status: ç¶æ
:return:
"""
filters = {}
if name is not None:
filters['name__like'] = f'%{name}%'
if status is not None:
filters['status'] = status
return await self.select_order('id', 'desc', **filters)
async def create(self, db: AsyncSession, obj: CreateItemParam) -> None:
"""
å建 Xxx
:param db: æ°æ®åºä¼è¯
:param obj: å建 Xxx åæ°
:return:
"""
await self.create_model(db, obj)
async def update(self, db: AsyncSession, pk: int, obj: UpdateItemParam) -> int:
"""
æ´æ° Xxx
:param db: æ°æ®åºä¼è¯
:param pk: Xxx ID
:param obj: æ´æ° Xxx åæ°
:return:
"""
return await self.update_model(db, pk, obj)
async def delete(self, db: AsyncSession, pk: int) -> int:
"""
å é¤ Xxx
:param db: æ°æ®åºä¼è¯
:param pk: Xxx ID
:return:
"""
return await self.delete_model(db, pk)
item_dao: CRUDItem = CRUDItem(Item)
Service 屿¨¡æ¿
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from backend.common.exception import errors
from backend.common.pagination import paging_data
class ItemService:
"""Xxx æå¡ç±»"""
@staticmethod
async def get(*, db: AsyncSession, pk: int) -> Item:
"""
è·å Xxx
:param db: æ°æ®åºä¼è¯
:param pk: Xxx ID
:return:
"""
item = await item_dao.get(db, pk)
if not item:
raise errors.NotFoundError(msg='è®°å½ä¸åå¨')
return item
@staticmethod
async def get_list(*, db: AsyncSession, name: str | None, status: int | None) -> dict[str, Any]:
"""
è·å Xxx å表
:param db: æ°æ®åºä¼è¯
:param name: åç§°
:param status: ç¶æ
:return:
"""
select = await item_dao.get_select(name, status)
return await paging_data(db, select)
@staticmethod
async def create(*, db: AsyncSession, obj: CreateItemParam) -> None:
"""
å建 Xxx
:param db: æ°æ®åºä¼è¯
:param obj: å建 Xxx åæ°
:return:
"""
await item_dao.create(db, obj)
@staticmethod
async def update(*, db: AsyncSession, pk: int, obj: UpdateItemParam) -> int:
"""
æ´æ° Xxx
:param db: æ°æ®åºä¼è¯
:param pk: Xxx ID
:param obj: æ´æ° Xxx åæ°
:return:
"""
item = await item_dao.get(db, pk)
if not item:
raise errors.NotFoundError(msg='è®°å½ä¸åå¨')
count = await item_dao.update(db, pk, obj)
return count
@staticmethod
async def delete(*, db: AsyncSession, pk: int) -> int:
"""
å é¤ Xxx
:param db: æ°æ®åºä¼è¯
:param pk: Xxx ID
:return:
"""
item = await item_dao.get(db, pk)
if not item:
raise errors.NotFoundError(msg='è®°å½ä¸åå¨')
count = await item_dao.delete(db, pk)
return count
item_service: ItemService = ItemService()
API 屿¨¡æ¿
from typing import Annotated
from fastapi import APIRouter, Path, Query
from backend.common.pagination import DependsPagination, PageData
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
from backend.common.security.jwt import DependsJwtAuth
from backend.database.db import CurrentSession, CurrentSessionTransaction
router = APIRouter()
@router.get(
'',
summary='å页è·åå表',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_items_paginated(
db: CurrentSession,
name: Annotated[str | None, Query(description='åç§°')] = None,
status: Annotated[int | None, Query(description='ç¶æ')] = None,
) -> ResponseSchemaModel[PageData[GetItemDetail]]:
page_data = await item_service.get_list(db=db, name=name, status=status)
return response_base.success(data=page_data)
@router.get('/{pk}', summary='è·å详æ
', dependencies=[DependsJwtAuth])
async def get_item(
db: CurrentSession,
pk: Annotated[int, Path(description='ID')],
) -> ResponseSchemaModel[GetItemDetail]:
item = await item_service.get(db=db, pk=pk)
return response_base.success(data=item)
@router.post('', summary='å建', dependencies=[DependsJwtAuth])
async def create_item(
db: CurrentSessionTransaction,
obj: CreateItemParam,
) -> ResponseModel:
await item_service.create(db=db, obj=obj)
return response_base.success()
@router.put('/{pk}', summary='æ´æ°', dependencies=[DependsJwtAuth])
async def update_item(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='ID')],
obj: UpdateItemParam,
) -> ResponseModel:
count = await item_service.update(db=db, pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()
@router.delete('/{pk}', summary='å é¤', dependencies=[DependsJwtAuth])
async def delete_item(
db: CurrentSessionTransaction,
pk: Annotated[int, Path(description='ID')],
) -> ResponseModel:
count = await item_service.delete(db=db, pk=pk)
if count > 0:
return response_base.success()
return response_base.fail()
CLI å½ä»¤
# å¯å¨æå¡
fba run
# æ°æ®åºè¿ç§»
fba alembic revision --autogenerate -m "æè¿°"
fba alembic upgrade head
# 代ç è´¨é
prek run --all-files