mirror of
https://github.com/fastapi-practices/fastapi_best_architecture.git
synced 2025-08-26 04:33:09 +08:00
update to python3.10 (#29)
This commit is contained in:
@ -18,10 +18,10 @@ repos:
|
|||||||
rev: 23.3.0
|
rev: 23.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3.8
|
language_version: python3.10
|
||||||
args:
|
args:
|
||||||
- '--skip-string-normalization'
|
- '--skip-string-normalization'
|
||||||
- '--line-length'
|
- '--line-length'
|
||||||
- '120'
|
- '120'
|
||||||
- '--target-version'
|
- '--target-version'
|
||||||
- 'py38'
|
- 'py310'
|
||||||
|
12
.ruff.toml
12
.ruff.toml
@ -9,11 +9,14 @@ select = [
|
|||||||
"PGH004",
|
"PGH004",
|
||||||
"PLE1142",
|
"PLE1142",
|
||||||
"RUF100",
|
"RUF100",
|
||||||
|
"I002",
|
||||||
|
"F404",
|
||||||
|
"TCH",
|
||||||
|
"UP007"
|
||||||
]
|
]
|
||||||
ignore = ["F401"]
|
|
||||||
line-length = 120
|
line-length = 120
|
||||||
format = "grouped"
|
format = "grouped"
|
||||||
target-version = "py38"
|
target-version = "py310"
|
||||||
cache-dir = "./.ruff_cache"
|
cache-dir = "./.ruff_cache"
|
||||||
|
|
||||||
[flake8-pytest-style]
|
[flake8-pytest-style]
|
||||||
@ -34,3 +37,8 @@ ignore-variadic-names = true
|
|||||||
[isort]
|
[isort]
|
||||||
lines-between-types = 1
|
lines-between-types = 1
|
||||||
order-by-type = true
|
order-by-type = true
|
||||||
|
|
||||||
|
[per-file-ignores]
|
||||||
|
"backend/app/api/v1/*.py" = ["TCH"]
|
||||||
|
"backend/app/models/*.py" = ["TCH003"]
|
||||||
|
"backend/app/**/__init__.py" = ["F401"]
|
||||||
|
@ -5,6 +5,8 @@ This is a base project of the FastAPI framework.
|
|||||||
It‘s purpose is to allow you to develop your project directly with it
|
It‘s purpose is to allow you to develop your project directly with it
|
||||||
as your base project
|
as your base project
|
||||||
|
|
||||||
|
Support python3.10 and above
|
||||||
|
|
||||||
## Skill
|
## Skill
|
||||||
|
|
||||||
- [x] FastAPI
|
- [x] FastAPI
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Union
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
@ -42,7 +42,7 @@ def password_verify(plain_password: str, hashed_password: str) -> bool:
|
|||||||
return pwd_context.verify(plain_password, hashed_password)
|
return pwd_context.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
|
||||||
def create_access_token(data: Union[int, Any], expires_delta: Union[timedelta, None] = None) -> str:
|
def create_access_token(data: int | Any, expires_delta: timedelta | None = None) -> str:
|
||||||
"""
|
"""
|
||||||
Generate encryption token
|
Generate encryption token
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from typing import TypeVar, Generic, Sequence, Dict, Union
|
from typing import TypeVar, Generic, Sequence, Dict
|
||||||
|
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
from fastapi_pagination.bases import AbstractPage, AbstractParams, RawParams
|
from fastapi_pagination.bases import AbstractPage, AbstractParams, RawParams
|
||||||
@ -35,7 +35,7 @@ class Page(AbstractPage[T], Generic[T]):
|
|||||||
page: int # 第n页
|
page: int # 第n页
|
||||||
size: int # 每页数量
|
size: int # 每页数量
|
||||||
total_pages: int # 总页数
|
total_pages: int # 总页数
|
||||||
links: Dict[str, Union[str, None]] # 跳转链接
|
links: Dict[str, str | None] # 跳转链接
|
||||||
|
|
||||||
__params_type__ = Params # 使用自定义的Params
|
__params_type__ = Params # 使用自定义的Params
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Any, Union, Set, Dict
|
from typing import Any, Union, Set, Dict
|
||||||
|
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from pydantic import validate_arguments, BaseModel
|
from pydantic import validate_arguments, BaseModel
|
||||||
|
|
||||||
_JsonEncoder = Union[Set[Union[int, str]], Dict[Union[int, str], Any]]
|
_JsonEncoder = Union[Set[int | str], Dict[int | str, Any]]
|
||||||
|
|
||||||
__all__ = ['ResponseModel', 'response_base']
|
__all__ = ['ResponseModel', 'response_base']
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ class ResponseModel(BaseModel):
|
|||||||
|
|
||||||
code: int = 200
|
code: int = 200
|
||||||
msg: str = 'Success'
|
msg: str = 'Success'
|
||||||
data: Optional[Any] = None
|
data: Any | None = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
json_encoders = {datetime: lambda x: x.strftime('%Y-%m-%d %H:%M:%S')}
|
json_encoders = {datetime: lambda x: x.strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
@ -31,9 +31,7 @@ class ResponseBase:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@validate_arguments
|
@validate_arguments
|
||||||
def success(
|
def success(*, code: int = 200, msg: str = 'Success', data: Any | None = None, exclude: _JsonEncoder | None = None):
|
||||||
*, code: int = 200, msg: str = 'Success', data: Optional[Any] = None, exclude: Optional[_JsonEncoder] = None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
请求成功返回通用方法
|
请求成功返回通用方法
|
||||||
|
|
||||||
@ -48,13 +46,13 @@ class ResponseBase:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@validate_arguments
|
@validate_arguments
|
||||||
def fail(*, code: int = 400, msg: str = 'Bad Request', data: Any = None, exclude: Optional[_JsonEncoder] = None):
|
def fail(*, code: int = 400, msg: str = 'Bad Request', data: Any = None, exclude: _JsonEncoder | None = None):
|
||||||
data = data if data is None else ResponseBase.__encode_json(data)
|
data = data if data is None else ResponseBase.__encode_json(data)
|
||||||
return ResponseModel(code=code, msg=msg, data=data).dict(exclude={'data': exclude})
|
return ResponseModel(code=code, msg=msg, data=data).dict(exclude={'data': exclude})
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@validate_arguments
|
@validate_arguments
|
||||||
def response_200(*, msg: str = 'Success', data: Optional[Any] = None, exclude: Optional[_JsonEncoder] = None):
|
def response_200(*, msg: str = 'Success', data: Any | None = None, exclude: _JsonEncoder | None = None):
|
||||||
data = data if data is None else ResponseBase.__encode_json(data)
|
data = data if data is None else ResponseBase.__encode_json(data)
|
||||||
return ResponseModel(code=200, msg=msg, data=data).dict(exclude={'data': exclude})
|
return ResponseModel(code=200, msg=msg, data=data).dict(exclude={'data': exclude})
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseSettings, root_validator
|
from pydantic import BaseSettings, root_validator
|
||||||
|
|
||||||
@ -35,9 +34,9 @@ class Settings(BaseSettings):
|
|||||||
TITLE: str = 'FastAPI'
|
TITLE: str = 'FastAPI'
|
||||||
VERSION: str = '0.0.1'
|
VERSION: str = '0.0.1'
|
||||||
DESCRIPTION: str = 'FastAPI Best Architecture'
|
DESCRIPTION: str = 'FastAPI Best Architecture'
|
||||||
DOCS_URL: Optional[str] = '/v1/docs'
|
DOCS_URL: str | None = '/v1/docs'
|
||||||
REDOCS_URL: Optional[str] = '/v1/redocs'
|
REDOCS_URL: str | None = '/v1/redocs'
|
||||||
OPENAPI_URL: Optional[str] = '/v1/openapi'
|
OPENAPI_URL: str | None = '/v1/openapi'
|
||||||
|
|
||||||
@root_validator
|
@root_validator
|
||||||
def validator_api_url(cls, values):
|
def validator_api_url(cls, values):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from typing import Any, Dict, Generic, Type, TypeVar, Union, Optional, NoReturn
|
from typing import Any, Dict, Generic, Type, TypeVar, NoReturn
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlalchemy import select, update, delete
|
from sqlalchemy import select, update, delete
|
||||||
@ -17,7 +17,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
|||||||
def __init__(self, model: Type[ModelType]):
|
def __init__(self, model: Type[ModelType]):
|
||||||
self.model = model
|
self.model = model
|
||||||
|
|
||||||
async def get(self, db: AsyncSession, pk: int) -> Optional[ModelType]:
|
async def get(self, db: AsyncSession, pk: int) -> ModelType | None:
|
||||||
"""
|
"""
|
||||||
通过主键 id 获取一条数据
|
通过主键 id 获取一条数据
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
|||||||
model = await db.execute(select(self.model).where(self.model.id == pk))
|
model = await db.execute(select(self.model).where(self.model.id == pk))
|
||||||
return model.scalars().first()
|
return model.scalars().first()
|
||||||
|
|
||||||
async def create(self, db: AsyncSession, obj_in: CreateSchemaType, user_id: Optional[int] = None) -> NoReturn:
|
async def create(self, db: AsyncSession, obj_in: CreateSchemaType, user_id: int | None = None) -> NoReturn:
|
||||||
"""
|
"""
|
||||||
新增一条数据
|
新增一条数据
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
|||||||
db.add(db_obj)
|
db.add(db_obj)
|
||||||
|
|
||||||
async def update(
|
async def update(
|
||||||
self, db: AsyncSession, pk: int, obj_in: Union[UpdateSchemaType, Dict[str, Any]], user_id: Optional[int] = None
|
self, db: AsyncSession, pk: int, obj_in: UpdateSchemaType | Dict[str, Any], user_id: int | None = None
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
通过主键 id 更新一条数据
|
通过主键 id 更新一条数据
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from typing import Optional, NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
from sqlalchemy import func, select, update, desc
|
from sqlalchemy import func, select, update, desc
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -13,10 +13,10 @@ from backend.app.schemas.user import CreateUser, UpdateUser, Avatar
|
|||||||
|
|
||||||
|
|
||||||
class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]):
|
class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]):
|
||||||
async def get_user_by_id(self, db: AsyncSession, user_id: int) -> Optional[User]:
|
async def get_user_by_id(self, db: AsyncSession, user_id: int) -> User | None:
|
||||||
return await self.get(db, user_id)
|
return await self.get(db, user_id)
|
||||||
|
|
||||||
async def get_user_by_username(self, db: AsyncSession, username: str) -> Optional[User]:
|
async def get_user_by_username(self, db: AsyncSession, username: str) -> User | None:
|
||||||
user = await db.execute(select(self.model).where(self.model.username == username))
|
user = await db.execute(select(self.model).where(self.model.username == username))
|
||||||
return user.scalars().first()
|
return user.scalars().first()
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr, MappedAsDataclass
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr, MappedAsDataclass
|
||||||
@ -24,9 +23,9 @@ class _BaseMixin(MappedAsDataclass):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
create_user: Mapped[int] = mapped_column(sort_order=9999, comment='创建者')
|
create_user: Mapped[int] = mapped_column(sort_order=9999, comment='创建者')
|
||||||
update_user: Mapped[Optional[int]] = mapped_column(init=False, default=None, sort_order=9999, comment='修改者')
|
update_user: Mapped[int | None] = mapped_column(init=False, default=None, sort_order=9999, comment='修改者')
|
||||||
created_time: Mapped[datetime] = mapped_column(init=False, default=func.now(), sort_order=9999, comment='创建时间')
|
created_time: Mapped[datetime] = mapped_column(init=False, default=func.now(), sort_order=9999, comment='创建时间')
|
||||||
updated_time: Mapped[Optional[datetime]] = mapped_column(
|
updated_time: Mapped[datetime | None] = mapped_column(
|
||||||
init=False, onupdate=func.now(), sort_order=9999, comment='更新时间'
|
init=False, onupdate=func.now(), sort_order=9999, comment='更新时间'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from sqlalchemy import func, String
|
from sqlalchemy import func, String
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
@ -21,7 +20,7 @@ class User(DataClassBase):
|
|||||||
email: Mapped[str] = mapped_column(String(50), unique=True, index=True, comment='邮箱')
|
email: Mapped[str] = mapped_column(String(50), unique=True, index=True, comment='邮箱')
|
||||||
is_superuser: Mapped[bool] = mapped_column(default=False, comment='超级权限')
|
is_superuser: Mapped[bool] = mapped_column(default=False, comment='超级权限')
|
||||||
is_active: Mapped[bool] = mapped_column(default=True, comment='用户账号状态')
|
is_active: Mapped[bool] = mapped_column(default=True, comment='用户账号状态')
|
||||||
avatar: Mapped[Optional[str]] = mapped_column(String(255), default=None, comment='头像')
|
avatar: Mapped[str | None] = mapped_column(String(255), default=None, comment='头像')
|
||||||
mobile_number: Mapped[Optional[str]] = mapped_column(String(11), default=None, comment='手机号')
|
mobile_number: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号')
|
||||||
time_joined: Mapped[datetime] = mapped_column(init=False, default=func.now(), comment='注册时间')
|
time_joined: Mapped[datetime] = mapped_column(init=False, default=func.now(), comment='注册时间')
|
||||||
last_login: Mapped[Optional[datetime]] = mapped_column(init=False, onupdate=func.now(), comment='上次登录')
|
last_login: Mapped[datetime | None] = mapped_column(init=False, onupdate=func.now(), comment='上次登录')
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@ -10,4 +9,4 @@ class Token(BaseModel):
|
|||||||
msg: str = 'Success'
|
msg: str = 'Success'
|
||||||
access_token: str
|
access_token: str
|
||||||
token_type: str = 'Bearer'
|
token_type: str = 'Bearer'
|
||||||
is_superuser: Optional[bool] = None
|
is_superuser: bool | None = None
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import datetime
|
import datetime
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, HttpUrl
|
from pydantic import BaseModel, Field, HttpUrl
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ class CreateUser(Auth):
|
|||||||
class UpdateUser(BaseModel):
|
class UpdateUser(BaseModel):
|
||||||
username: str
|
username: str
|
||||||
email: str
|
email: str
|
||||||
mobile_number: Optional[str] = None
|
mobile_number: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class Avatar(BaseModel):
|
class Avatar(BaseModel):
|
||||||
@ -28,9 +27,9 @@ class Avatar(BaseModel):
|
|||||||
class GetUserInfo(UpdateUser):
|
class GetUserInfo(UpdateUser):
|
||||||
id: int
|
id: int
|
||||||
uid: str
|
uid: str
|
||||||
avatar: Optional[str] = None
|
avatar: str | None = None
|
||||||
time_joined: datetime.datetime = None
|
time_joined: datetime.datetime = None
|
||||||
last_login: Optional[datetime.datetime] = None
|
last_login: datetime.datetime | None = None
|
||||||
is_superuser: bool
|
is_superuser: bool
|
||||||
is_active: bool
|
is_active: bool
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseSettings
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
@ -11,9 +10,9 @@ class Settings(BaseSettings):
|
|||||||
TITLE: str = 'FastAPI'
|
TITLE: str = 'FastAPI'
|
||||||
VERSION: str = 'v0.0.1'
|
VERSION: str = 'v0.0.1'
|
||||||
DESCRIPTION: str = 'FastAPI Best Architecture'
|
DESCRIPTION: str = 'FastAPI Best Architecture'
|
||||||
DOCS_URL: Optional[str] = '/v1/docs'
|
DOCS_URL: str | None = '/v1/docs'
|
||||||
REDOCS_URL: Optional[str] = None
|
REDOCS_URL: str | None = None
|
||||||
OPENAPI_URL: Optional[str] = '/v1/openapi'
|
OPENAPI_URL: str | None = '/v1/openapi'
|
||||||
|
|
||||||
# Static Server
|
# Static Server
|
||||||
STATIC_FILES: bool = False
|
STATIC_FILES: bool = False
|
||||||
|
Reference in New Issue
Block a user