FastAPI实战笔记(七)集成 NoSQL数据库

七、集成 NoSQL数据库

环境准备

安装 elastic、Redis、Mongodb略

bash 复制代码
(fastapi) root@simon:/FastAPI-Cookbook-main/Chapter07/streaming_platform# 
uvicorn app.main:app \
  --host 0.0.0.0 \
  --port 8000 \
  --reload

数据库

数据库连接

py 复制代码
# db_connection.py
import logging

from elasticsearch import (
    # Elasticsearch的异步Python客户端
    AsyncElasticsearch,
    # Elasticsearch操作时可能抛出的传输相关异常
    TransportError,
)
# 异步MongoDB客户端
from motor.motor_asyncio import AsyncIOMotorClient
# 异步Redis客户端
from redis import asyncio as aioredis

# uvicorn会自动配置日志系统 直接进行获取即可
logger = logging.getLogger("uvicorn")


# 定义各服务客户端 首先确保这些服务已经在本地运行了
mongo_client = AsyncIOMotorClient(
    "mongodb://localhost:27017"
)
es_client = AsyncElasticsearch("http://localhost:9200")
redis_client = aioredis.from_url("redis://localhost")


# 这几个ping方法将被main方法的异步上下文管理器 lifespan 调用
# mongodb 连接测试
async def ping_mongo_db_server():
    try:
        # 向admin数据库发送了个 ping 命令
        await mongo_client.admin.command("ping")
        logger.info("Connected to MongoDB")
    except Exception as e:
        logger.error(
            f"Error connecting to MongoDB: {e}"
        )
        raise e

# Elasticsearch 连接测试
async def ping_elasticsearch_server():
    try:
        # 调用info()方法获取Elasticsearch服务器信息来测试连接
        await es_client.info()
        logger.info(
            "Elasticsearch connection successful"
        )
    except TransportError as e:
        logger.error(
            f"Elasticsearch connection failed: {e}"
        )
        raise e

# Redis 连接测试
async def ping_redis_server():
    try:
        # 进行测试
        await redis_client.ping()
        logger.info("Connected to Redis")
    except Exception as e:
        logger.error(f"Error connecting to Redis: {e}")
        raise e

数据库设置

py 复制代码
# database.py(1)
from app.db_connection import es_client, mongo_client

# 定义了一个名为 beat_streaming 的数据库
# motor 库会自行检查或者创建
# Motor库重写了 __getattr__ 方法,实现了动态属性访问机制
database = mongo_client.beat_streaming

# 在main方法中被调用 返回当前的 database 对象,用于 main 中端点的依赖注入
def mongo_database():
    return database

# 关于 Elasticsearch的部分 后续继续进行补充
songs_index_mapping = {
    "mappings": {
        "properties": {
            "artist": {"type": "keyword"}, 
            "views_per_country": {
                "type": "object",
                "dynamic": True,
            },
        }
    }
}


async def create_es_index():
    await es_client.options(
        ignore_status=[400, 404]
    ).indices.create(
        index="songs_index",
        body=songs_index_mapping,
    )

CRUD操作端点

py 复制代码
# main.py

# 向集合中添加一首歌
@app.post("/song")
async def add_song(
    # Body() 用于从HTTP请求体中提取JSON数据
    song: dict = Body(
        # example 参数提供API文档示例
        example={
            "title": "My Song",
            "artist": "My Artist",
            "genre": "My Genre",
        },
    ),
    # 进行依赖注入
    mongo_db=Depends(mongo_database),
):
    # 插入单个文档
    await mongo_db.songs.insert_one(song)
    return {
        "message": "Song added successfully",
        # main方法配置了 ENCODERS_BY_TYPE[ObjectId] = str
        "id": song.get("_id"),
    }

!NOTE

为什么要写 "id": song.get("_id")

原始的 song 是一个普通的 Python dict,例如:

json 复制代码
{
"title": "My Song",
"artist": "My Artist",
"genre": "My Genre"
}

执行:

python 复制代码
await mongo_db.songs.insert_one(song)

如果文档里没有 _id 字段,PyMongo 会自动生成一个 ObjectId ;同时 PyMongo会对个_id 字段进行原地修改。 insert_one 调用完之后,内存里的 song 这个 dict 变成了类似:

python 复制代码
{
 "_id": ObjectId("65a..."),
 "title": "My Song",
 "artist": "My Artist",
 "genre": "My Genre"
}

因此通过

python 复制代码
"id": song.get("_id")

就能拿到刚生成的 _id

同时在main方法中,有

python 复制代码
ENCODERS_BY_TYPE[ObjectId] = str

告诉 FastAPI:如果响应里出现 ObjectId 类型,就用 str() 把它自动转成字符串 。所以即使 song.get("_id")ObjectId,最终返回给前端看到的是字符串形式的 id。

!IMPORTANT

NoSQL 文档库和传统 SQL 表库在设计思路上的差异

接口直接接收「任意字典」而不是固定表结构

python 复制代码
# 请求体类型是 dict,只给了一个示例结构,并没有强制字段集合或字段类型
song: dict = Body(
 example={
     "title": "My Song",
     "artist": "My Artist",
     "genre": "My Genre",
 },
)

甚至可以在请求时写入

json 复制代码
{
"title": "Song A",
"artist": "Someone",
"genre": "Rock",
"tags": ["live", "HD"],
"extra_info": {"source": "yt", "duration": 123}
}

体现的 NoSQL 特性:

  • 集合(collection)不需要预先定义严格 schema;
  • 每条文档可以有不同字段集、不同嵌套结构,新增字段不需要迁移。

对比 SQL:

  • SQL 中你必须先 CREATE TABLE songs (id INT, title VARCHAR(...), artist VARCHAR(...), ...)
  • 如果突然要加 extra_info 这种 JSON 结构,通常需要:
    • 改表(ALTER TABLE),或者
    • 设计一个单独的扩展表,或者
    • 用某些 JSON 类型字段(但依然受表结构约束)。

接口层通常会对应一个固定的 ORM / Pydantic 模型,而不是 dict


文档是 JSON 风格的一整块,而不是拆成多表

示例里的文档结构:

json 复制代码
{
  "title": "My Song",
  "artist": "My Artist",
  "genre": "My Genre"
}

在实际项目中,则可以是

json 复制代码
{
  "title": "My Song",
  "artist": "My Artist",
  "genre": "My Genre",
  "album": {
    "name": "Album X",
    "release_year": 2020
  },
  "views_per_country": {
    "US": 1000,
    "CN": 500
  }
}

所有数据在一条文档中,读一首歌就能拿到所有信息。

NoSQL 的典型特征:

  • 倾向于把「一条业务实体」相关的属性 嵌套在一个文档中
  • 为读优化,避免频繁 join。

对比 SQL:

  • 通常会拆成多张表:songsalbumssong_views 等;
  • 查询时通过 join/多次查询拼回来;

_id 自动生成、自动补回文档

插入部分:

python 复制代码
await mongo_db.songs.insert_one(song)
return {
    "message": "Song added successfully",
    "id": song.get("_id"),
}

insert_one(song) 会:

  • 如果文档里没 _id,自动生成一个 ObjectId
  • _id 原地写回你传入的 song dict

没有显式声明主键类型、序列,也不用在 Python 里管理自增 id。

NoSQL 的特点:

  • 主键 _id 是文档级的,自动生成、全局唯一;
  • 不需要在「建表」阶段定义主键类型和自增策略。

对比 SQL:

  • 必须在建表时定义主键、类型、是否自增等:

    sql 复制代码
    id SERIAL PRIMARY KEY
  • 应用代码通常通过 ORM 的模型来拿 id,模型结构和表字段紧耦合。

py 复制代码
# main.py
# 检索单首歌的端点
@app.get("/song/{song_id}")
async def get_song(
    song_id: str,
    db=Depends(mongo_database),
):
    song = await db.songs.find_one(
        {
            "_id": ObjectId(song_id)
            if ObjectId.is_valid(song_id)
            else None
        }
    )
    if not song:
        raise HTTPException(
            status_code=404, detail="Song not found"
        )
    song.pop("album", None)
    return song

# 更新歌曲端点
@app.put("/song/{song_id}")
async def update_song(
    song_id: str,
    updated_song: dict,
    db=Depends(mongo_database),
):
    result = await db.songs.update_one(
        {
            "_id": ObjectId(song_id)
            if ObjectId.is_valid(song_id)
            else None
        },
        {"$set": updated_song},
    )
    if result.modified_count == 1:
        return {"message": "Song updated successfully"}

    raise HTTPException(
        status_code=404, detail="Song not found"
    )
    
# 删除歌曲端点
@app.delete("/song/{song_id}")
async def delete_song(
    song_id: str,
    db=Depends(mongo_database),
):
    result = await db.songs.delete_one(
        {
            "_id": ObjectId(song_id)
            if ObjectId.is_valid(song_id)
            else None
        }
    )
    if result.deleted_count == 1:
        return {"message": "Song deleted successfully"}

    raise HTTPException(
        status_code=404, detail="Song not found"
    )
    
# 获取所有歌曲的端点
@app.get("/songs")
async def get_songs(
    db=Depends(mongo_database),
):
    songs = await db.songs.find().to_list(None)
    return songs

嵌入与引用

关系型数据库具有固定的表结构(schema) ,在使用前必须通过 CREATE TABLE 明确定义字段名称、数据类型、主键、外键等。表与表之间的关系通常通过外键与 SQL 的 JOIN 操作 来表达 ;例如,用户表和订单表通过 user_id 字段建立关联,查询完整信息时往往需要将多张表 JOIN 在一起。这类关系是强约束的:外键机制可以在插入或删除数据时自动执行级联操作或拒绝不合规的操作,从而保障数据的一致性。

相比之下,MongoDB 等文档型 NoSQL 数据库没有固定的 schema ,集合(collection)不要求所有文档具有相同的字段结构,使得数据模型可以灵活地随业务需求演化。数据库层面不提供 JOIN 或外键约束 ,这意味着关系不能像在 SQL 中那样通过声明式语法自动维护。取而代之的是两种常见的建模方式:嵌入(Embedding) ,即将相关数据直接内嵌到同一个文档中;或引用(Referencing) ,即在一个文档中保存另一个文档的 ID,由应用程序在需要时发起额外查询来"手动"关联数据。MongoDB主要通过嵌入和引用进行建模


嵌入就是把相关数据直接嵌套在同一个文档中,一起存、一块读。

json 复制代码
{
  "title": "歌曲标题",
  "artist": "歌手名称",
  "genre": "音乐类型",
  "album": {
    "title": "专辑标题",
    "release_year": 2017
  }
}

在 MongoDB 中,创建歌曲时可以直接在同一个 JSON 文档中嵌入完整的专辑信息,并通过 insert_one(song) 一次性写入。MongoDB 会原样保留你提供的嵌套结构,无需预先定义字段或关系。

这种模式特别适用于关系紧密且基本不变 的数据场景。例如,一首歌与其所属的专辑通常在专辑发布后就不再频繁变更,两者天然绑定;同时,这类数据往往读多写少且经常一起读取------比如在展示歌曲详情时,通常也需要一并显示专辑封面、发行年份等信息。

采用嵌入方式的主要优势在于性能与简洁性:只需一次读取操作就能获取完整的歌曲与专辑信息 ,显著提升读取性能;同时,MongoDB 保证单个文档内的更新是原子操作,避免了并发修改时的数据不一致问题。此外,嵌套文档的结构天然贴近前端使用的 JSON 模型,开发体验更加直观。

然而,嵌入设计也存在明显代价。最突出的问题是数据重复 :如果一张专辑包含十首歌,专辑信息就会被完整复制十次,造成存储冗余。当嵌入的内容体积较大或更新频繁时,问题会进一步放大;不仅文档整体变大,影响读写效率,还容易因更新遗漏导致多处副本内容不一致(例如修改了某首歌中的专辑名,却忘了同步其他歌曲)。


引用就是文档中只存 其他文档的 ID 列表,真正内容需要再查一次。

引用关系的典型用例可以是创建播放列表。一个播放列表包含多首歌曲,每首歌曲可以出现在不同的播放列表中。此外,播放列表经常被更改或更新,因此需要引用策略来管理关系。

python 复制代码
# main.py
class Playlist(BaseModel):
    name: str
    songs: list[str] = []

@app.post("/playlist")
async def create_playlist(
    playlist: Playlist = Body(
        example={
            "name": "我的播放列表",
            "songs": ["song_id"],
        }
    ),
    db=Depends(mongo_database),
):
    result = await db.playlists.insert_one(
        # 将 Pydantic 模型 转换为标准 Python 字典
        playlist.model_dump()
    )
    return {
        "message": "播放列表创建成功",
        # 和之前插入歌曲的部分进行对比 其实两种写法都是可以的
        "id": str(result.inserted_id),
    }
python 复制代码
# 检索播放列表
@app.get("/playlist/{playlist_id}")
async def get_playlist(
    playlist_id: str,
    db=Depends(mongo_database),
):
    # 先找到对应的播放列表
    playlist = await db.playlists.find_one(
        {
            "_id": ObjectId(playlist_id)
            if ObjectId.is_valid(playlist_id)
            else None
        }
    )
    if not playlist:
        raise HTTPException(
            status_code=404, detail="Playlist not found"
        )
	# 寻找歌曲
    songs = await db.songs.find(
        {
            "_id": {
                "$in": [
                    # 播放列表集合中的歌曲ID存储为字符串 所以在使用时需要将其转换成 ObjectId
                    ObjectId(song_id)
                    for song_id in playlist["songs"]
                ]
            }
        }
    ).to_list(None)

    return {"name": playlist["name"], "songs": songs}

MongoDB中的索引

json 复制代码
{
    "_id": "695f4332c83e6608f6d78c1f",
    "title": "Bohemian Rhapsody",
    "artist": "Queen",
    "genre": "classic rock",
    // 检索使用的是"album"的"release_year"字段
    "album": {
      "title": "A Night at the Opera",
      "release_year": 1975
    },
    "views_per_country": {
      "US": 10000016,
      "UK": 20000017,
      "Germany": 15000018,
      "Italy": 5000019
    }
  },
py 复制代码
# 创建特定年份歌曲索引
# 在 main.py 的上文中我们创建了一个索引 
# await db.songs.create_index(
#         {"album.release_year": -1}
#     )
# 
@app.get("/songs/year")
async def get_songs_by_released_year(
    year: int,
    db=Depends(mongo_database),
):
    query = db.songs.find({"album.release_year": year})
    # 返回查查询的执行计划 用于输出一些调试
    explained_query = await query.explain()
    logger.info(
        "Index used: %s",
        explained_query.get("queryPlanner", {})
        .get("winningPlan", {})
        .get("inputStage", {})
        .get("indexName", "No index used"),
    )

    songs = await query.to_list(None)
    return songs
py 复制代码
# main.py 上文中进行了这个索引的创建
# await db.songs.create_index({"artist": "text"})
@app.get("/songs/artist")
async def get_songs_by_artist(
    artist: str, db=Depends(mongo_database)
):
    # 使用前要先建立文本索引
    query = db.songs.find(
        # $text 操作符 用于在文本索引上执行全文搜索
        # $search 参数 指定要搜索的文本内容
        # artist 是搜索关键词
        {"$text": {"$search": artist}}
    )
    explained_query = await query.explain()
    logger.info(
        "Index used: %s",
        explained_query.get("queryPlanner", {})
        .get("winningPlan", {})
        .get("indexName", "No index used"),
    )

    songs = await query.to_list(None)
    return songs

暴露敏感数据

数据掩码是隐藏、混淆或替换敏感数据的技术,使数据在非生产环境中保持安全,同时仍可用于开发、测试等用途。通过数据库聚合安全地查看数据,以便将其暴露给API的第三方消费者。

数据填充
py 复制代码
# fill_users_in_mongo.py
# 向数据库中填充用户集合
import asyncio
from datetime import datetime

from app.db_connection import mongo_client

db = mongo_client.beat_streaming


users = [
    {
        "name": "John Doe",
        "email": "johndoe@email.com",
        "year_of_birth": 1990,
        "country": "USA",
        "actions": [
            {
                "action": "basic subscription",
                "date": datetime.fromisoformat(
                    "2021-01-01"
                ),
                "amount": 10,
            },
            {
                "action": "unscription",
                "date": datetime.fromisoformat(
                    "2021-05-01"
                ),
            },
        ],
        # 是否同意与第三方合作伙伴共享行为数据
        "consent_to_share_data": True,
    },
    {
        ....剩余内容略
    },
]

async def add_users():
    await db.users.insert_many(users)

if __name__ == "__main__":
    asyncio.run(add_users())
创建脱敏视图
py 复制代码
# streaming_platform/create_aggregation_and_user_data_view.py
# 创建一个只用于第三方访问的脱敏视图
from pymongo import MongoClient

client = MongoClient("mongodb://localhost:27017/")


pipeline_redact = {
    # $redact 常用于基于条件整体保留/丢弃文档或子文档
    "$redact": {
        "$cond": {
            "if": {
                # consent_to_share_data == true 才保留
                "$eq": ["$consent_to_share_data", True]
            },
            # 保留文档
            "then": "$$KEEP",
            # 直接丢弃
            "else": "$$PRUNE",
        }
    }
}

# 去掉 email 和 name
# $unset:在聚合管道中删除字段
pipeline_remove_email_and_name = {"$unset": ["email", "name"]}

# 日期打码
obfuscate_day_of_date = {
    "$concat": [
        {
            "$substrCP": [
                "$$action.date",
                0,
                7,
            ]
        },
        "-XX",
    ]
}

rebuild_actions_elements = {
    "input": "$actions",
    "as": "action",
    "in": {
        "$mergeObjects": [
            "$$action",
            {"date": obfuscate_day_of_date},
        ]
    },
}

# 映射新的日期字段
pipeline_set_actions = {
    "$set": {
        "actions": {"$map": rebuild_actions_elements},
    }
}


pipeline = [
    pipeline_redact,
    pipeline_remove_email_and_name,
    pipeline_set_actions,
]


# 在 beat_streaming数据库中创建 users_data_view 视图 
if __name__ == "__main__":
    client["beat_streaming"].drop_collection(
        "users_data_view"
    )

    client["beat_streaming"].create_collection(
        "users_data_view",
        viewOn="users",
        pipeline=pipeline,
    )
独立端点
py 复制代码
# third_party_endpoint.py
from fastapi import APIRouter, Depends

from app.database import mongo_database
# 在 main.py 中添加了这个路由
router = APIRouter(
    prefix="/thirdparty",
    tags=["third party"],
)


@router.get("/users/actions")
async def get_users_with_actions(
    db=Depends(mongo_database),
):
    users = [
        user
        # 查询条件 {} 不设置任何过滤条件 相当于 SQL 中的 SELECT * FROM table
        # 返回集合中的所有记录
        # 投影操作 {"_id": 0} 不返回 _id 字段
        # {"name": 1, "email": 1}  则是只返回 name 和 email 字段
        async for user in db.users_data_view.find(
            {}, {"_id": 0}
        )
    ]

    return users

集成 Elasticsearch 与 Redis

db_connection.py 中已经完成了 Elasticsearch 异步客户端的定义以及与 Elasticsearch的连接检查函数;同时完成了 Redis 客户端的定义以及与 Redis的连接检查函数。

数据填充

py 复制代码
from app.db_connection import es_client
from songs_info import songs_info

mapping = {
    "mappings": {
        "properties": {
            "artist": {"type": "keyword"},
            "views_per_country": {
                "type": "object",
                "dynamic": True,
            },
        }
    }
}


async def create_index():
    await es_client.options(
        ignore_status=[400, 404]
    ).indices.create(
        index="songs_index",
        body=mapping,
    )
    await es_client.close()


async def fill_elastichsearch():
    for song in songs_info:
        await es_client.index(
            index="songs_index", body=song
        )
    await es_client.close()


async def delete_all_indexes():
    await es_client.options(
        ignore_status=[400, 404]
    ).indices.delete(index="*")
    await es_client.close()


async def main():
    await delete_all_indexes()
    await create_index()
    await fill_elastichsearch()


if __name__ == "__main__":
    import asyncio

    asyncio.run(main())

构建查询

py 复制代码
# es_queries.py
def top_ten_artists_query(country) -> dict:
    views_field = f"views_per_country.{country}"

    query = {
        "bool": {
            "filter": [
                {"exists": {"field": views_field}}
            ],
        }
    }

    aggs = {
        "top_ten_artists": {
            "terms": {
                "field": "artist",
                "size": 10,
                "order": {"views": "desc"},
            },
            "aggs": {
                "views": {
                    "sum": {
                        "field": views_field,
                        "missing": 0,
                    }
                }
            },
        }
    }

    return {
        "index": "songs_index",
        "size": 0,
        "query": query,
        "aggs": aggs,
    }


def top_ten_songs_query(country) -> dict:
    views_field = f"views_per_country.{country}"
    query = {
        "bool": {
            "must": {"match_all": {}},
            "filter": [
                {"exists": {"field": views_field}}
            ],
        }
    }
    sort = {views_field: {"order": "desc"}}
    source = [
        "title",
        views_field,
        "album.title",
        "artist",
    ]
    return {
        "index": "songs_index",
        "query": query,
        "size": 10,
        "sort": sort,
        "source": source,
    }

建立端点

py 复制代码
import json
import logging

from elasticsearch import BadRequestError
from fastapi import APIRouter, Depends, HTTPException
from fastapi_cache.decorator import cache

from app.db_connection import es_client, redis_client
from app.es_queries import (
    top_ten_artists_query,
    top_ten_songs_query,
)

logger = logging.getLogger("uvicorn")


router = APIRouter(
    prefix="/search",
    tags=["search"],
)


def get_elasticsearch_client():
    return es_client

def get_redis_client():
    return redis_client


@router.get("/top/ten/artists/{country}")
async def top_ten_artist_by_country(
    country: str,
    es_client=Depends(get_elasticsearch_client),
    redis_client=Depends(get_redis_client),
):
    cache_key = f"top_ten_artists_{country}"

    cached_data = await redis_client.get(cache_key)
    if cached_data:
        logger.info(
            f"Returning cached data for {country}"
        )
        return json.loads(cached_data)

    logger.info(
        f"Getting top ten artists for {country}"
    )
    try:
        response = await es_client.search(
            **top_ten_artists_query(country)
        )
    except BadRequestError as e:
        logger.error(e)

        raise HTTPException(
            status_code=400,
            detail="Invalid country",
        )

    artists = [
        {
            "artist": record.get("key"),
            "views": record.get("views", {}).get(
                "value"
            ),
        }
        for record in response["aggregations"][
            "top_ten_artists"
        ]["buckets"]
    ]

    await redis_client.set(
        cache_key, json.dumps(artists), ex=3600
    )

    return artists


@router.get("/top/ten/songs/{country}")
@cache(expire=60)
async def get_top_ten_by_country(
    country: str,
    es_client=Depends(get_elasticsearch_client),
):
    try:
        response = await es_client.search(
            **top_ten_songs_query(country)
        )
    except BadRequestError as e:
        logger.error(e)

        raise HTTPException(
            status_code=400,
            detail="Invalid country",
        )

    songs = []
    for record in response["hits"]["hits"]:
        song = {
            "title": record["_source"]["title"],
            "artist": record["_source"]["artist"],
            "album": record["_source"]["album"][
                "title"
            ],
            "views": record["_source"]
            .get("views_per_country", {})
            .get(country),
        }
        songs.append(song)

    return songs

main方法

py 复制代码
import logging
from asyncio import gather
from contextlib import asynccontextmanager

from bson import ObjectId
from fastapi import (
    Body,
    Depends,
    FastAPI,
    HTTPException,
)
from fastapi.encoders import ENCODERS_BY_TYPE
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from pydantic import BaseModel

from app import main_search, third_party_endpoint
from app.database import mongo_database
from app.db_connection import (
    ping_elasticsearch_server,
    ping_mongo_db_server,
    ping_redis_server,
    redis_client,
)

logger = logging.getLogger("uvicorn")

# 全局配置 将MongoDB的ObjectId转换为字符串
ENCODERS_BY_TYPE[ObjectId] = str


# 建立一个异步上下文管理器 在启动和关闭时执行特定代码
@asynccontextmanager
# 当前并没有添加异常处理 也就是说只要有一个出现了问题 启动就会失败
async def lifespan(app: FastAPI):
    #  gather 并发执行多个异步任务,并等待所有任务都完成后才返回
    await gather(
        ping_mongo_db_server(),
        ping_elasticsearch_server(),
        ping_redis_server(),
    )

    db = mongo_database()
    # 删除现有的索引
    await db.songs.drop_indexes()
    # 创建特定年份歌曲索引
    await db.songs.create_index(
        {"album.release_year": -1}
    )
    # 创建艺术家文本索引
    await db.songs.create_index({"artist": "text"})

    FastAPICache.init(
        RedisBackend(redis_client),
        prefix="fastapi-cache",
    )

    yield
 

# 将声明周期上下文管理器作为参数传递给FastAPI对象
app = FastAPI(lifespan=lifespan)
# 添加第三方路由
app.include_router(third_party_endpoint.router)

try:
    app.include_router(main_search.router)
except Exception:
    pass
相关推荐
Dontla1 天前
IndexedDB(浏览器原生NoSQL非关系型数据库)浏览器数据库、chrome数据库、idb工具库
数据库·chrome·nosql
Psycho_MrZhang1 天前
Django/Flask/FastAPI简要对比分析
django·flask·fastapi
曲幽2 天前
FastAPI + SQLite:从基础CRUD到安全并发的实战指南
python·sqlite·fastapi·web·jwt·form·sqlalchemy·oauth2
Psycho_MrZhang2 天前
FastAPI 设计思想总结
fastapi
七夜zippoe2 天前
依赖注入:构建可测试的Python应用架构
开发语言·python·架构·fastapi·依赖注入·反转
山沐与山2 天前
【Python】深入理解Python Web框架:从Flask到FastAPI的并发之路
python·flask·fastapi
西西弗Sisyphus3 天前
Python FastAPI 和 Uvicorn 同步 (Synchronous) vs 异步 (Asynchronous)
python·fastapi·uvicorn
钱彬 (Qian Bin)3 天前
项目实践14—全球证件智能识别系统(切换回SQLite数据库并基于Docker实现离线部署和日常管理)
运维·docker·容器·fastapi·证件识别
闲人编程3 天前
电商平台用户系统API设计
数据库·后端·消息队列·fastapi·监控·容器化·codecapsule