1

我也可以将 MongoDB 与 FastAPI 一起使用

  1. 使用全局client: motor.motor_asyncio.AsyncIOMotorClient对象,否则
  2. startup通过在活动期间根据此 SO 答案创建一个,该答案引用此“真实世界示例”

但是,我也想使用fastapi-users,因为它可以很好地与开箱即用的 MongoDB 配合使用。缺点是它似乎只适用于处理我的数据库客户端连接(即全局)的第一种方法。原因是为了配置 fastapi-users,我必须有一个活动的 MongoDB 客户端连接,这样我才能制作db如下所示的对象,然后我需要它db来制作MongoDBUserDatabasefastapi-users 所需的对象:

# main.py
app = FastAPI()


# Create global MongoDB connection 
DATABASE_URL = "mongodb://user:paspsword@localhost/auth_db"
client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL, uuidRepresentation="standard")
db = client["my_db"]

# Set up fastapi_users
user_db = MongoDBUserDatabase(UserDB, db["users"])

cookie_authentication = CookieAuthentication(secret='lame secret' , lifetime_seconds=3600, name='cookiemonster')

fastapi_users = FastAPIUsers(
    user_db,
    [cookie_authentication],
    User,
    UserCreate,
    UserUpdate,
    UserDB,
)

在代码中的那一点之后,我可以导入 fastapi_users 路由器。但是,如果我想将我的项目分解为我自己的 FastAPI 路由器,我会感到很沮丧,因为:

  1. 如果我将client创建移动到另一个模块以导入到我app和我的路由器中,那么我在不同的事件循环中有不同的客户端并得到类似的错误RuntimeError: Task <Task pending name='Task-4' coro=<RequestResponseCycle.run_asgi() running at /usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py:389> cb=[set.discard()]> got Future <Future pending cb=[_chain_future.<locals>._call_check_cancel() at /usr/local/lib/python3.8/asyncio/futures.py:360]> attached to a different loop(在这个 SO 问题中涉及)
  2. 如果我使用“真实世界示例”的解决方案,那么我会陷入在fastapi_users代码示例中构建对象的位置:我不能这样做,main.py因为还没有db对象。

我考虑将MongoDBUserDatabase对象作为startup事件代码的一部分(即在async def connect_to_mongo()真实世界示例中),但我也无法让它工作,因为我看不到如何让它工作。

我怎么能

  1. 以一种可以在我的主要app和几个之间共享的方式创建一个全局 MongoDB 客户端和 FastAPI-User 对象,routers而不会出现“附加到不同的循环”错误,或者
  2. 创建精美的包装类和函数以使用触发器设置 FastAPI 用户startup
4

2 回答 2

1

我认为我的解决方案不完整或不正确,但我想我会发布它以防它激发任何想法,我很难过。我遇到了确切的困境,几乎看起来像是一个设计缺陷..

我遵循了这个MongoDB 完整示例并将其命名main.py

此时我的应用程序不起作用。服务器启动,但每次尝试查询数据库时都会导致上述“附加到不同的循环”。

寻找指导,我偶然发现了同一个“现实世界”的例子

添加了启动和关闭main.py事件处理程序

# Event handlers
app.add_event_handler("startup", create_start_app_handler(app=app))
app.add_event_handler("shutdown", create_stop_app_handler(app=app))

dlw_api.db.events.py这:

import logging

from dlw_api.user import UserDB
from fastapi import FastAPI
from fastapi_users.db.mongodb import MongoDBUserDatabase
from motor.motor_asyncio import AsyncIOMotorClient


LOG = logging.getLogger(__name__)
DB_NAME = "dlwLocal"
USERS_COLLECTION = "users"
DATABASE_URI = "mongodb://dlw-mongodb:27017"  # protocol://container_name:port


_client: AsyncIOMotorClient = None
_users_db: MongoDBUserDatabase = None


def get_users_db() -> MongoDBUserDatabase:
    return _users_db


async def connect_to_db() -> None:
    global _users_db
    # logger.info("Connecting to {0}", repr(DATABASE_URL))
    client = AsyncIOMotorClient(DATABASE_URI)
    db = client[DB_NAME]
    collection = db[USERS_COLLECTION]
    _users_db = MongoDBUserDatabase(UserDB, collection)
    LOG.info(f"Connected to {DATABASE_URI}")


async def close_db_connection(app: FastAPI) -> None:
    _client.close()
    LOG.info("Connection closed")

并且dlw_api.events.py

from typing import Callable
from fastapi import FastAPI
from dlw_api.db.events import close_db_connection, connect_to_db
from dlw_api.user import configure_user_auth_routes
from fastapi_users.authentication import CookieAuthentication
from dlw_api.db.events import get_users_db


COOKIE_SECRET = "THIS_NEEDS_TO_BE_SET_CORRECTLY" # TODO: <--|
COOKIE_LIFETIME_SECONDS: int = 3_600
COOKIE_NAME = "c-is-for-cookie"

# Auth stuff:
_cookie_authentication = CookieAuthentication(
    secret=COOKIE_SECRET,
    lifetime_seconds=COOKIE_LIFETIME_SECONDS,
    name=COOKIE_NAME,
)

auth_backends = [
    _cookie_authentication,
]


def create_start_app_handler(app: FastAPI) -> Callable:
    async def start_app() -> None:
        await connect_to_db(app)
        configure_user_auth_routes(
            app=app,
            auth_backends=auth_backends,
            user_db=get_users_db(),
            secret=COOKIE_SECRET,
        )

    return start_app


def create_stop_app_handler(app: FastAPI) -> Callable:
    async def stop_app() -> None:
        await close_db_connection(app)

    return stop_app

这对我来说感觉不正确,这是否意味着所有Depends用于用户身份验证的路由都必须包含在服务器启动事件处理程序中?

于 2021-03-13T18:56:49.067 回答
0

fastapi-users 的作者 (frankie567)创建了一个 repl.it,显示了各种解决方案。 对此解决方案的讨论可能会提供更多背景信息,但该解决方案的关键部分是:

  1. 不要费心将 FastAPIstartup触发器与Depends您的 MongDB 连接管理一起使用。相反,创建一个单独的文件(即db.py)来创建您的数据库连接和客户端对象。在需要时导入此db对象,例如您的路由器,然后将其用作全局对象。
  2. 还要创建一个单独users.py的做 2 件事:
    1. 创建全局使用fastapi_users = FastAPIUsers(...)的对象以与其他路由器一起使用以处理授权。
    2. 创建一个FastAPI.APIRouter()对象并将所有 fastapi-user 路由器附加到它(router.include_router(...)
  3. 在所有其他路由器中,根据需要同时导入dbfastapi_users从上面导入
  4. 关键:将您的主要代码拆分为
    1. amain.py只进口 uvicorn 和服务app:app
    2. 一个app.py有你的主要FastAPI对象(即app),然后附加我们所有的路由器,包括一个来自users.py所有连接到的 fastapi-users 路由器的路由器。

通过按照上面的 4 拆分代码,您可以避免“附加到不同的循环”错误。

于 2021-03-21T10:50:33.753 回答