Fastapi - oceanbei333/leetcode GitHub Wiki
Fastapi 简介
openapi
2.0与3.0
Python类型标注
类型标注,主要是给函数、类、变量等添加类型注解(约束必须是某种类型),或者其他约束(是否可选,是否有某种属性或者方法)
常用的注解类型
python内置的类型和标准库的类型,一般都有相对应的注解类型
并且这些类型在3.9以后将被弃用
建议现在大多数的场景下的python版本都不会高于3.9,建议还是typing的类型,而不是python自带的类型
- typing.Tuple -> tuple
二元组 Tuple[str, int]
-
typing.List -> list
-
typing.Dict -> dict
-
typing.Set -> set
-
typing.FrozenSet -> frozenset
-
typing.Iterable
-
typing. Callable
可调用类型; Callable[[int], str] 是一个函数,接受一个 int 参数,返回一个 str 。
-
typing.Type -> type
注解类,而不是类的实例
Type(str), 必须是类型str或者str的子类,也就是必须是类
class User: ...
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...
# Accepts User, BasicUser, ProUser, TeamUser, ...
def make_new_user(user_instance: Type[User]) -> User:
# ...
return user_class()
Type 合法的参数仅有类、 Any 、类型变量 以及上述类型的联合类型。
-
typing.Literal
Enum
功能上其实类似于使用枚举类型进行标注,而在pydantic也是使用枚举的
-
typing.ClassVar
标记类变量
enterprise_d = Starship(3000)
enterprise_d.stats = {} # Error, setting class variable on instance
Starship.stats = {} # This is OK
创建新的注解类型
-
typing.NamedTuple
-
typing.NewType(name, tp)
和原来的类型一样,只是标识,他们是不同的类而已
UserId = NewType('UserId', int)
first_user = UserId(1)
- typing.TypedDict
class Point2D(TypedDict):
x: int
y: int
label: str
a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
b: Point2D = {'z': 3, 'label': 'bad'}
其他
- typing.Union
Union[str, int],标注可以是str或者int类型
- typing.Optional
Optional[str] 等价于 Union[str, None]
这与可选参数并非相同的概念。可选参数是一个具有默认值的参数
def foo(arg: int = 0) -> None:
此时arg参数不可以使用Optional[int], 因为Optional[int]的意思是Union[int, None], 也就是要么是整型类型,要么是None
除非可选参数的arg的默认值是None
def foo(arg: Optional[int] = None) -> None:
- typing.Final
不能被再次赋值或者在子类中被重载
MAX_SIZE: Final = 9000
MAX_SIZE += 1 # Error reported by type checker
class Connection:
TIMEOUT: Final[int] = 10
class FastConnector(Connection):
TIMEOUT = 1 # Error reported by type checker
- typing.overload
类似于Java的重载
@overload
def process(response: None) -> None:
...
@overload
def process(response: int) -> tuple[int, str]:
...
@overload
def process(response: bytes) -> str:
...
def process(response):
<actual implementation>
-
typing.final
不能被子类重写
-
typing.AnyStr
AnyStr = TypeVar('AnyStr', str, bytes)
-
typing.Protocol
类似于abc.ABC, 只不过ABC是通过在父类中定义虚拟方法,约束子类必须实现某种方法
而Protocol,通过注解,约束某个对象必须有某种方法,才可以被使用
比如定义function的时候,对传入的参数进行约束
如果需要使用ABC实现,可以约束对象必须某种虚拟类的子类
class Proto(Protocol):
def meth(self) -> int:
class C:
def meth(self) -> int:
return 0
def func1(x: Proto) -> int:
return x.meth()
class D(ABC):
@abstractmethod
def meth(self) -> int:
return 0
def func2(x: Proto) -> int:
if isinstance(x, D):
return x.meth()
def func3(x: Proto) -> int:
if hasattr(x, 'meth'):
return x.meth()
func(C()) # Passes static type chec
- typing.runtime_checkable
泛型
They are building blocks for creating generic types.
-
typing.Generic
使用:实例化Generic,并把注解类型作为变量传入,需要注解的类,需要继承这个实例
适用于,暂时不能确定注解类型,延迟到使用时或者子类中确定
比如对于一个类似dict的类
class Mapping(Generic[KT, VT]):
def __getitem__(self, key: KT) -> VT:
...
# Etc.
def lookup_name(mapping: Mapping[str, str], key: str, default: str) -> str:
try:
return mapping[key]
except KeyError:
return default
class IntMapping(Maping[int: int]):
-
typing.TypeVar
变量,其值是注解类型
T = TypeVar('T') # Can be anything A = TypeVar('A', str, bytes) # Must be str or bytes
主要是和Generic配合使用,因为Generic实例的时候,需要将注解类型作为变量传入,所以这时候就需要Typevar
pydantic
Data validation and settings management
pydantic enforces type hints at runtime, and provides user friendly errors when data is invalid.
让类型注解不仅仅是静态检查的提示而已
相比于typing,添加了一些额外的类型,比如ip、email、uuid、date、datetime等
也可以添加自对应的约束
也可以字段mapping,值的mapping,数据结构转换
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel
from enum import Enum
class User(BaseModel):
id: int
name = 'John Doe'
signup_ts: Optional[datetime] = Field(None)
friends: List[int] = []
external_data = {
'id': '123',
'signup_ts': '2019-06-01 12:22',
'friends': [1, 2, '3'],
}
user = User(**external_data)
print(user.id)
#> 123
print(repr(user.signup_ts))
#> datetime.datetime(2019, 6, 1, 12, 22)
print(user.friends)
#> [1, 2, 3]
print(user.dict())
"""
{
'id': 123,
'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
'friends': [1, 2, 3],
'name': 'John Doe',
}
"""
Settings management
from typing import Set
from pydantic import (
BaseModel,
BaseSettings,
PyObject,
RedisDsn,
PostgresDsn,
Field,
)
class SubModel(BaseModel):
foo = 'bar'
apple = 1
class Settings(BaseSettings):
auth_key: str
api_key: str = Field(..., env='my_api_key')
redis_dsn: RedisDsn = 'redis://user:pass@localhost:6379/1'
pg_dsn: PostgresDsn = 'postgres://user:pass@localhost:5432/foobar'
special_function: PyObject = 'math.cos'
# to override domains:
# export my_prefix_domains='["foo.com", "bar.com"]'
domains: Set[str] = set()
# to override more_settings:
# export my_prefix_more_settings='{"foo": "x", "apple": 1}'
more_settings: SubModel = SubModel()
class Config:
env_prefix = 'my_prefix_' # defaults to no prefix, i.e. ""
fields = {
'auth_key': {
'env': 'my_auth_key',
},
'redis_dsn': {
'env': ['service_redis_dsn', 'redis_url']
}
}
print(Settings().dict())
"""
{
'auth_key': 'xxx',
'api_key': 'xxx',
'redis_dsn': RedisDsn('redis://user:pass@localhost:6379/1',
scheme='redis', user='user', password='pass', host='localhost',
host_type='int_domain', port='6379', path='/1'),
'pg_dsn': PostgresDsn('postgres://user:pass@localhost:5432/foobar',
scheme='postgres', user='user', password='pass', host='localhost',
host_type='int_domain', port='5432', path='/foobar'),
'special_function': <built-in function cos>,
'domains': set(),
'more_settings': {'foo': 'bar', 'apple': 1},
}
"""
项目启动
- 下载项目代码
-
环境变量
默认已经有一个.env文件
-
项目启动
docker-compose up -d
- 容器
- 数据库容器
- 后端容器
- celery容器
Makefile
make shell 后需要手动建立数据连接
后端服务
预入口文件
python /app/app/backend_pre_start.py
# make migrations
alembic revision --autogenerate
# Run migrations
alembic upgrade head
# Create initial data in DB
python /app/app/initial_data.py
目录结构
项目根路径
backend/
对于单模块的项目,默认app模块
对比django的目录结构
api/
对应于django的views.py和urls.py文件
api/api.py
可以使用api.py文件来组织路由
api/接口文件.py
views.p
专注于处理请求,业务逻辑,
具体业务逻辑的实现需要委托给crud的类的实例
不直接面对数据库,数据库操作由crud实现
models/
对应于django的 modes.py
专注于数据库建表,没有任何业务逻辑,目前使用sqlalchemy,可以换成其他ORM框架,或者直接使用sql
不同领域的表在不同文件中组织,然后统一在__init__.py暴露
db/
封装了数据库连接
通过依赖注入,在接口中使用数据库连接
schemas/
对应于django的serializers.py
主要是构造接口返回的数据与对数据校验
但是不同于django的同一张表一般只会对应于一个serializers类,fastapi对于一张表可能会对应多个BaseModel,是因为不同的场景需要使用不同的BaseModel,比如post接口的request body、更新的request body、以及序列化与反序列化
所有类依然通过__init__.py暴露,但是因为这里的类比较多,建议直接 import *
crud/
这是一个django中缺失的目录
这里主要是处理数据的增改删除
这里的类都是一个具有能力的类,也是实践面向对象思想最佳场所
基类CRUDBase中封装了对数据库的常规操作
子类可以专注于业务逻辑的实现
CRUDBase详解
泛型(Generic)
类中的某些类型注解推迟确定,类似于函数的传参,方便对父类进行类型注解
ModelType = TypeVar("ModelType", bound=Base)