From 1c84c7b96afc73df3e67377eef69cfbcbfc76281 Mon Sep 17 00:00:00 2001 From: Yurii Karabas <1998uriyyo@gmail.com> Date: Sat, 23 Oct 2021 22:21:04 +0300 Subject: [PATCH] Fix issue with double model parsing --- fastapi/dependencies/utils.py | 28 +++++++++++++++++----------- tests/test_dependency_cache.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 35ba44aab..6d3190262 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -467,7 +467,8 @@ async def solve_dependencies( media_type=None, # type: ignore # in Starlette background=None, # type: ignore # in Starlette ) - dependency_cache = dependency_cache or {} + if dependency_cache is None: + dependency_cache = {} sub_dependant: Dependant for sub_dependant in dependant.dependencies: sub_dependant.call = cast(Callable[..., Any], sub_dependant.call) @@ -493,7 +494,21 @@ async def solve_dependencies( ) use_sub_dependant.security_scopes = sub_dependant.security_scopes - solved_result = await solve_dependencies( + if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache: + solved = dependency_cache[sub_dependant.cache_key] + + if sub_dependant.name is not None: + values[sub_dependant.name] = solved + + continue + + ( + sub_values, + sub_errors, + background_tasks, + _, # the subdependency returns the same response we have + sub_dependency_cache, + ) = await solve_dependencies( request=request, dependant=use_sub_dependant, body=body, @@ -502,19 +517,10 @@ async def solve_dependencies( dependency_overrides_provider=dependency_overrides_provider, dependency_cache=dependency_cache, ) - ( - sub_values, - sub_errors, - background_tasks, - _, # the subdependency returns the same response we have - sub_dependency_cache, - ) = solved_result dependency_cache.update(sub_dependency_cache) if sub_errors: errors.extend(sub_errors) continue - if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache: - solved = dependency_cache[sub_dependant.cache_key] elif is_gen_callable(call) or is_async_gen_callable(call): stack = request.scope.get("fastapi_astack") assert isinstance(stack, AsyncExitStack) diff --git a/tests/test_dependency_cache.py b/tests/test_dependency_cache.py index 65ed7f946..71e888618 100644 --- a/tests/test_dependency_cache.py +++ b/tests/test_dependency_cache.py @@ -1,9 +1,21 @@ from fastapi import Depends, FastAPI from fastapi.testclient import TestClient +from pydantic import BaseModel, root_validator app = FastAPI() -counter_holder = {"counter": 0} +counter_holder = {"counter": 0, "parsing_counter": 0} + + +class Model(BaseModel): + @root_validator + def __validate__(cls, _): + counter_holder["parsing_counter"] += 1 + return {} + + +async def model_dep(model: Model) -> Model: + return model async def dep_counter(): @@ -35,6 +47,15 @@ async def get_sub_counter_no_cache( return {"counter": count, "subcounter": subcount} +@app.post("/sub-model-parsing/") +async def get_double_model_parsing( + a: Model = Depends(model_dep), + b: Model = Depends(model_dep), +): + assert a is b + return {"parsing_counter": counter_holder["parsing_counter"]} + + client = TestClient(app) @@ -66,3 +87,10 @@ def test_sub_counter_no_cache(): response = client.get("/sub-counter-no-cache/") assert response.status_code == 200, response.text assert response.json() == {"counter": 4, "subcounter": 3} + + +def test_sub_model_parsing_no_repeatable_parsing(): + counter_holder["parsing_counter"] = 0 + response = client.post("/sub-model-parsing/", json={}) + assert response.status_code == 200, response.text + assert response.json() == {"parsing_counter": 1}