<h3 dir="auto">First check</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I used the GitHub search to find a similar issue and didn't find it.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I searched the FastAPI documentation, with the integrated search.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already searched in Google "How to X in FastAPI" and didn't find any information.</li>
</ul>
<h3 dir="auto">Description</h3>
<p dir="auto">Is it possible to somehow globally filter response that is returned from a route method? What I have in mind if there is any proper way to additionally filter response based on some context, or FastAPI dependencies (<code class="notranslate">Dependency</code>, <code class="notranslate">Security</code> etc.).</p>
<p dir="auto">My use case is:</p>
<ul dir="auto">
<li>I have some authorization mechanism with different roles (let's say <code class="notranslate">admin</code> and <code class="notranslate">user</code> for simplicity).</li>
<li>Data is typical CRUD model stored in the database, and all records have boolean <code class="notranslate">active</code> column to handle "soft" delete and not remove records entirely from the database. If record was "soft" deleted the flag would be set to <code class="notranslate">false</code>.</li>
<li>As user with <code class="notranslate">admin</code> role I can access all records, even if they were "soft" deleted, and <code class="notranslate">user</code> role can only access the ones that have been not "soft" deleted.</li>
<li>Record from database is represented by pydantic model:</li>
</ul>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class MyModel(BaseModel):
    id: str
    name: str
    active: bool = True"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">MyModel</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s1">id</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">active</span>: <span class="pl-s1">bool</span> <span class="pl-c1">=</span> <span class="pl-c1">True</span></pre></div>
<ul dir="auto">
<li>I have some route defined which fetches a model from database and returns it:</li>
</ul>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="async def get_model(
    id: str = Path(...),
    current_user: User = Depends(get_current_user)
) -&gt; MyModel:
    # The `current_user` will be a model representing currently authorized user. 
    # In case of admin user inactive record might also be returned here.
    return await fetch_model_from_db(id, user.is_admin())"><pre class="notranslate"><span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">get_model</span>(
    <span class="pl-s1">id</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Path</span>(...),
    <span class="pl-s1">current_user</span>: <span class="pl-v">User</span> <span class="pl-c1">=</span> <span class="pl-v">Depends</span>(<span class="pl-s1">get_current_user</span>)
) <span class="pl-c1">-&gt;</span> <span class="pl-v">MyModel</span>:
    <span class="pl-c"># The `current_user` will be a model representing currently authorized user. </span>
    <span class="pl-c"># In case of admin user inactive record might also be returned here.</span>
    <span class="pl-k">return</span> <span class="pl-k">await</span> <span class="pl-en">fetch_model_from_db</span>(<span class="pl-s1">id</span>, <span class="pl-s1">user</span>.<span class="pl-en">is_admin</span>())</pre></div>
<ul dir="auto">
<li>The output model will have the attribute <code class="notranslate">active</code> present in any case, but I want to only preserve it for admin user and hide it from regular <code class="notranslate">user</code> role (it can access only records with <code class="notranslate">active</code> flag as <code class="notranslate">true</code> anyway). In short I would like to additionally parse response based on <code class="notranslate">current_user</code> role.</li>
</ul>
<h3 dir="auto">What I found</h3>
<p dir="auto">First thing I thought about was excluding certain attributes as per docummentation <a href="https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter" rel="nofollow">https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter</a> but this only allows excluding attributes explicitly without any additional context.</p>
<p dir="auto">Then I thought about making custom middleware, which serves the purpose of processing response object before it is returned to the client, but I couldn't find a clear way to pass down the context as <code class="notranslate">current_user</code>, so I could use that to process response and exclude unwanted attributes from response body. This however would add additional overhead as the response body is already serialized at this stage and would need to be deserialized -&gt; filtered -&gt; serialized again.</p>
<h3 dir="auto">Additional context</h3>
<p dir="auto">I could add additional function like this and call it as the last thing in every route, but it's way too verbose and I'd rather handle it more generic way:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="def filter_admin_attributes(input: Dict, is_admin: bool, attributes: Set[str]) -&gt; Dict:
   return {k: v for k, v in input.items() if not is_admin and k in attributes}

async def get_model(
    id: str = Path(...),
    current_user: User = Depends(get_current_user)
) -&gt; Dict:
    # The `current_user` will be a model representing currently authorized user. 
    # In case of admin user inactive record might also be returned here.
    result = await fetch_model_from_db(id, user.is_admin())
	return filter_admin_attributes(result.dict(), user.is_admin(), {'active',})"><pre class="notranslate"><span class="pl-k">def</span> <span class="pl-en">filter_admin_attributes</span>(<span class="pl-s1">input</span>: <span class="pl-v">Dict</span>, <span class="pl-s1">is_admin</span>: <span class="pl-s1">bool</span>, <span class="pl-s1">attributes</span>: <span class="pl-v">Set</span>[<span class="pl-s1">str</span>]) <span class="pl-c1">-&gt;</span> <span class="pl-v">Dict</span>:
   <span class="pl-k">return</span> {<span class="pl-s1">k</span>: <span class="pl-s1">v</span> <span class="pl-k">for</span> <span class="pl-s1">k</span>, <span class="pl-s1">v</span> <span class="pl-c1">in</span> <span class="pl-s1">input</span>.<span class="pl-en">items</span>() <span class="pl-k">if</span> <span class="pl-c1">not</span> <span class="pl-s1">is_admin</span> <span class="pl-c1">and</span> <span class="pl-s1">k</span> <span class="pl-c1">in</span> <span class="pl-s1">attributes</span>}

<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">get_model</span>(
    <span class="pl-s1">id</span>: <span class="pl-s1">str</span> <span class="pl-c1">=</span> <span class="pl-v">Path</span>(...),
    <span class="pl-s1">current_user</span>: <span class="pl-v">User</span> <span class="pl-c1">=</span> <span class="pl-v">Depends</span>(<span class="pl-s1">get_current_user</span>)
) <span class="pl-c1">-&gt;</span> <span class="pl-v">Dict</span>:
    <span class="pl-c"># The `current_user` will be a model representing currently authorized user. </span>
    <span class="pl-c"># In case of admin user inactive record might also be returned here.</span>
    <span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">fetch_model_from_db</span>(<span class="pl-s1">id</span>, <span class="pl-s1">user</span>.<span class="pl-en">is_admin</span>())
	<span class="pl-k">return</span> <span class="pl-en">filter_admin_attributes</span>(<span class="pl-s1">result</span>.<span class="pl-en">dict</span>(), <span class="pl-s1">user</span>.<span class="pl-en">is_admin</span>(), {<span class="pl-s">'active'</span>,})</pre></div>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="from fastapi import FastAPI, Query

app = FastAPI()

@app.get('/sprints/')
async def get_sprints(userID: int, numOfSprints: int = Query(..., ge=1, lt=100)):
    [...]
    return answer"><pre class="notranslate"><span class="pl-k">from</span> <span class="pl-s1">fastapi</span> <span class="pl-k">import</span> <span class="pl-v">FastAPI</span>, <span class="pl-v">Query</span>

<span class="pl-s1">app</span> <span class="pl-c1">=</span> <span class="pl-v">FastAPI</span>()

<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">get</span>(<span class="pl-s">'/sprints/'</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">get_sprints</span>(<span class="pl-s1">userID</span>: <span class="pl-s1">int</span>, <span class="pl-s1">numOfSprints</span>: <span class="pl-s1">int</span> <span class="pl-c1">=</span> <span class="pl-v">Query</span>(..., <span class="pl-s1">ge</span><span class="pl-c1">=</span><span class="pl-c1">1</span>, <span class="pl-s1">lt</span><span class="pl-c1">=</span><span class="pl-c1">100</span>)):
    [...]
    <span class="pl-k">return</span> <span class="pl-s1">answer</span></pre></div>
<p dir="auto">is applied, if numOfSprints is more than 99, response is: 422 Unprocessable Entity</p>
<p dir="auto">but in swagger documentation isnt clear about limit:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/20581667/113487747-1447a680-94ba-11eb-977a-e04500b512fe.png"><img src="https://user-images.githubusercontent.com/20581667/113487747-1447a680-94ba-11eb-977a-e04500b512fe.png" alt="image" style="max-width: 100%;"></a></p>
<p dir="auto">python 3.8<br>
latest fastapi</p>