FastAPI 依賴注入詳解:處理依賴樹

    async def app(request: Request) -> Response:

        ......

        solved_result = await solve_dependencies(
            request=request,
            dependant=dependant,
            body=body,
            dependency_overrides_provider=dependency_overrides_provider,
        )

        values, errors, background_tasks, sub_response, _ = solved_result

        if errors:
            raise RequestValidationError(errors, body=body)
        else:
            raw_response = await run_endpoint_function(
                dependant=dependant, values=values, is_coroutine=is_coroutine
            )

        ......

這里是endpoint的前一步虎眨,request首先要經(jīng)過solve_dependencies()來與endpoint的依賴樹進(jìn)行匹配您单,這相當(dāng)于API的門衛(wèi)一般的存在梯投。
匹配的結(jié)果包含在solve過程中的結(jié)果稻薇,錯(cuò)誤,以及其他若干信息葵萎。有了詳細(xì)的信息导犹,我們便能精確的定位到問題的所在。

async def solve_dependencies(
        *,
        request: Union[Request, WebSocket],
        dependant: Dependant,
        body: Optional[Union[Dict[str, Any], FormData]] = None,
        background_tasks: Optional[BackgroundTasks] = None,
        response: Optional[Response] = None,
        dependency_overrides_provider: Optional[Any] = None,
        dependency_cache: Optional[Dict[Tuple[Callable, Tuple[str]], Any]] = None,
) -> Tuple[
    Dict[str, Any],
    List[ErrorWrapper],
    Optional[BackgroundTasks],
    Response,
    Dict[Tuple[Callable, Tuple[str]], Any],
]:
    """

    :param request: 請(qǐng)求報(bào)文
    :param dependant: endpoint對(duì)應(yīng)的依賴樹
    :param body: 請(qǐng)求體
    :param background_tasks: 后臺(tái)任務(wù)
    :param response: 子依賴
    :param dependency_overrides_provider: app中設(shè)置的依賴替代項(xiàng)
    :param dependency_cache: 已完成的依賴
    :return:
    """
    values: Dict[str, Any] = {}
    errors: List[ErrorWrapper] = []
    response = response or Response(
        content=None,
        status_code=None,  # type: ignore
        headers=None,
        media_type=None,
        background=None,
    )
    dependency_cache = dependency_cache or {}

開始解依賴樹

    for sub_dependant in dependant.dependencies:
        sub_dependant.call = cast(Callable, sub_dependant.call)
        sub_dependant.cache_key = cast(
            Tuple[Callable, Tuple[str]], sub_dependant.cache_key
        )
        # cast的作用是標(biāo)注類型羡忘,方便提示

        call = sub_dependant.call
        use_sub_dependant = sub_dependant
        # 分別拿到依賴內(nèi)容和依賴項(xiàng)

        if (
                dependency_overrides_provider
                and dependency_overrides_provider.dependency_overrides
        ):
            # 依賴重寫時(shí)
            original_call = sub_dependant.call
            call = getattr(
                dependency_overrides_provider, "dependency_overrides", {}
            ).get(original_call, original_call)
            # 找到對(duì)應(yīng)的重寫
            use_path: str = sub_dependant.path  # type: ignore
            use_sub_dependant = get_dependant(
                path=use_path,
                call=call,
                name=sub_dependant.name,
                security_scopes=sub_dependant.security_scopes,
            )
            # 重新生成依賴
            use_sub_dependant.security_scopes = sub_dependant.security_scopes

        solved_result = await solve_dependencies(
            request=request,
            dependant=use_sub_dependant,
            body=body,
            background_tasks=background_tasks,
            response=response,
            dependency_overrides_provider=dependency_overrides_provider,
            dependency_cache=dependency_cache,
        )
        # 獲得依賴項(xiàng)的子依賴的結(jié)果集

        (
            sub_values,
            sub_errors,
            background_tasks,
            _,  # 子依賴項(xiàng)返回與我們相同的響應(yīng)
            sub_dependency_cache,
        ) = solved_result
        # 拿到結(jié)果和錯(cuò)誤

上面這部分負(fù)責(zé)解決子依賴谎痢,拿到最后的結(jié)果集,然后用子依賴的結(jié)果集來解決自身

        dependency_cache.update(sub_dependency_cache)
        # 將子依賴已解決的內(nèi)容注冊(cè)
        if sub_errors:
            errors.extend(sub_errors)
            continue

        if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
            # 如果設(shè)置了使用緩存壳坪,且該依賴內(nèi)容已被子依賴解決過
            solved = dependency_cache[sub_dependant.cache_key]
            # 直接抄答案

        # 否則照常執(zhí)行
        elif is_gen_callable(call) or is_async_gen_callable(call):
            stack = request.scope.get("fastapi_astack")
            if stack is None:
                raise RuntimeError(
                    async_contextmanager_dependencies_error
                )  # pragma: no cover
            solved = await solve_generator(
                call=call, stack=stack, sub_values=sub_values
            )
            # 用子依賴得到的結(jié)果集舶得,作為參數(shù),傳入到依賴內(nèi)容中爽蝴。

        elif is_coroutine_callable(call):
            solved = await call(**sub_values)
        else:
            solved = await run_in_threadpool(call, **sub_values)

        # 對(duì)于同步異步不同的執(zhí)行方式

        if sub_dependant.name is not None:
            values[sub_dependant.name] = solved
            # 收集結(jié)果
        if sub_dependant.cache_key not in dependency_cache:
            dependency_cache[sub_dependant.cache_key] = solved
            # 將結(jié)果添加到結(jié)果集

解決該節(jié)點(diǎn)的解決每一個(gè)依賴項(xiàng)

    # 依賴項(xiàng)不再有子依賴時(shí)沐批,會(huì)直接跳過上面的for循環(huán)
    path_values, path_errors = request_params_to_args(
        dependant.path_params, request.path_params
    )
    query_values, query_errors = request_params_to_args(
        dependant.query_params, request.query_params
    )
    header_values, header_errors = request_params_to_args(
        dependant.header_params, request.headers
    )
    cookie_values, cookie_errors = request_params_to_args(
        dependant.cookie_params, request.cookies
    )
    # 生成依賴樹時(shí),我們將需要的參數(shù)蝎亚,保存到了path_params九孩,query_params等地方。
    # 現(xiàn)在就是從Request中提取它們的好時(shí)機(jī)
    # 當(dāng)然這個(gè)過程中也會(huì)產(chǎn)生錯(cuò)誤发框,我們會(huì)收集這些錯(cuò)誤
    values.update(path_values)
    values.update(query_values)
    values.update(header_values)
    values.update(cookie_values)
    errors += path_errors + query_errors + header_errors + cookie_errors
    # 合并現(xiàn)有錯(cuò)誤

解決參數(shù)依賴躺彬,分別遍歷依賴的個(gè)參數(shù)需求列表,與request所能提供的做匹配梅惯。
我們來看一下匹配的函數(shù)

def request_params_to_args(
        required_params: Sequence[ModelField],
        received_params: Union[Mapping[str, Any], QueryParams, Headers],
) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
    values = {}
    errors = []
    for field in required_params:
        if is_scalar_sequence_field(field) and isinstance(
                received_params, (QueryParams, Headers)
        ):
            value = received_params.getlist(field.alias) or field.default
        else:
            value = received_params.get(field.alias)
        # 分別對(duì)標(biāo)準(zhǔn)序列參數(shù)和其他參數(shù)的情況進(jìn)行處理宪拥,拿到value
        # 這里未處理默認(rèn)值的情況下

        field_info = field.field_info
        assert isinstance(
            field_info, params.Param
        ), "Params must be subclasses of Param"
        if value is None:
            if field.required:
                errors.append(
                    ErrorWrapper(
                        MissingError(), loc=(field_info.in_.value, field.alias)
                    )
                )
                # 必須則引發(fā)錯(cuò)誤
            else:
                values[field.name] = deepcopy(field.default)
                # 否則使用默認(rèn)值
            continue
        v_, errors_ = field.validate(
            value, values, loc=(field_info.in_.value, field.alias)
        )
        if isinstance(errors_, ErrorWrapper):
            errors.append(errors_)
        elif isinstance(errors_, list):
            errors.extend(errors_)
        else:
            values[field.name] = v_
    return values, errors
回到solve_dependencies
    if dependant.body_params:
        # 如果有user_info(user: User)這樣的參數(shù),User為Model铣减。
        # 其會(huì)保存到body_params中她君,現(xiàn)在是處理它們的時(shí)候
        (
            body_values,
            body_errors,
        ) = await request_body_to_args(  # body_params checked above
            required_params=dependant.body_params, received_body=body
        )
        # body傳入進(jìn)去,進(jìn)行匹配葫哗,得到結(jié)果
        values.update(body_values)
        errors.extend(body_errors)
        # 整合結(jié)果
    if dependant.http_connection_param_name:
        values[dependant.http_connection_param_name] = request
    if dependant.request_param_name and isinstance(request, Request):
        values[dependant.request_param_name] = request
    elif dependant.websocket_param_name and isinstance(request, WebSocket):
        values[dependant.websocket_param_name] = request
    # 這三者是解決需要特定參數(shù)的時(shí)候缔刹,主要是指Request或WebSocket這樣的參數(shù)

    if dependant.background_tasks_param_name:
        if background_tasks is None:
            background_tasks = BackgroundTasks()
        values[dependant.background_tasks_param_name] = background_tasks
    # 后臺(tái)任務(wù)
    if dependant.response_param_name:
        values[dependant.response_param_name] = response
    # 如果需要操作Response報(bào)文球涛,這里會(huì)提供sub response,其內(nèi)容最后會(huì)整合到response中

    if dependant.security_scopes_param_name:
        values[dependant.security_scopes_param_name] = SecurityScopes(
            scopes=dependant.security_scopes
        )
    # 拿到安全域
    return values, errors, background_tasks, response, dependency_cache

solve_dependencies本身也是遞歸函數(shù)校镐,這和get_dependant是相輔相成的亿扁。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸟廓,隨后出現(xiàn)的幾起案子从祝,更是在濱河造成了極大的恐慌,老刑警劉巖肝箱,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哄褒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡煌张,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門退客,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骏融,“玉大人,你說我怎么就攤上這事萌狂〉挡#” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵茫藏,是天一觀的道長(zhǎng)误趴。 經(jīng)常有香客問我,道長(zhǎng)务傲,這世上最難降的妖魔是什么凉当? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮售葡,結(jié)果婚禮上看杭,老公的妹妹穿的比我還像新娘。我一直安慰自己挟伙,他們只是感情好楼雹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尖阔,像睡著了一般贮缅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上介却,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天谴供,我揣著相機(jī)與錄音,去河邊找鬼筷笨。 笑死憔鬼,一個(gè)胖子當(dāng)著我的面吹牛龟劲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轴或,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昌跌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了照雁?” 一聲冷哼從身側(cè)響起蚕愤,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饺蚊,沒想到半個(gè)月后萍诱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赖钞,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桐智,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年座舍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锁摔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姻政。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粮揉,死狀恐怖屠缭,靈堂內(nèi)的尸體忽然破棺而出黄琼,到底是詐尸還是另有隱情苗缩,我是刑警寧澤饵蒂,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站酱讶,受9級(jí)特大地震影響退盯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泻肯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一渊迁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧软免,春花似錦宫纬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至榛泛,卻和暖如春蝌蹂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曹锨。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工孤个, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沛简。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓齐鲤,卻偏偏與公主長(zhǎng)得像斥废,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子给郊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354