<h3 dir="auto">Is your feature request related to a problem</h3>
<p dir="auto">I wish to use pydantic's BaseModel with validators as depency injection. Classes as dependencies are handy because I can <em>fuse</em> multiple path/body/... parameters into a single object, but what would be cool is to be able to check those parameters <strong>based on other parameters</strong> via pydantic's validators even if those values are transferred in different forms (path/body/...).</p>
<h3 dir="auto">The solution you would like</h3>
<p dir="auto">So, what already works is something like this:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class User(BaseModel):
    name: str
    password: str
    age: int = Query(None)

@app.get('/users/{name}/{password}')
async def fetch(user: User = Depends()):
    return user"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">User</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">password</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">age</span>: <span class="pl-s1">int</span> <span class="pl-c1">=</span> <span class="pl-v">Query</span>(<span class="pl-c1">None</span>)

<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">get</span>(<span class="pl-s">'/users/{name}/{password}'</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">fetch</span>(<span class="pl-s1">user</span>: <span class="pl-v">User</span> <span class="pl-c1">=</span> <span class="pl-v">Depends</span>()):
    <span class="pl-k">return</span> <span class="pl-s1">user</span></pre></div>
<p dir="auto">This correctly checks types and benefits from the usual genius automatic error and documentation generation. Though, adding validators to e.g. make sure the user's name is not the same as her password leads to an internal server error when the <code class="notranslate">ValueError</code> is raised.</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class User(BaseModel):
    name: str
    password: str
    age: int = Query(None)

    @validator('password')
    def check_not_equal(cls, v, values):
        if v == values['name']:
            raise ValueError('name and password match')
        return v

@app.get('/users/{name}/{password}')
async def fetch(user: User = Depends()):
    return user"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">User</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">password</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">age</span>: <span class="pl-s1">int</span> <span class="pl-c1">=</span> <span class="pl-v">Query</span>(<span class="pl-c1">None</span>)

    <span class="pl-en">@<span class="pl-en">validator</span>(<span class="pl-s">'password'</span>)</span>
    <span class="pl-k">def</span> <span class="pl-en">check_not_equal</span>(<span class="pl-s1">cls</span>, <span class="pl-s1">v</span>, <span class="pl-s1">values</span>):
        <span class="pl-k">if</span> <span class="pl-s1">v</span> <span class="pl-c1">==</span> <span class="pl-s1">values</span>[<span class="pl-s">'name'</span>]:
            <span class="pl-k">raise</span> <span class="pl-v">ValueError</span>(<span class="pl-s">'name and password match'</span>)
        <span class="pl-k">return</span> <span class="pl-s1">v</span>

<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">get</span>(<span class="pl-s">'/users/{name}/{password}'</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">fetch</span>(<span class="pl-s1">user</span>: <span class="pl-v">User</span> <span class="pl-c1">=</span> <span class="pl-v">Depends</span>()):
    <span class="pl-k">return</span> <span class="pl-s1">user</span></pre></div>
<p dir="auto">The optimal thing would be that those validators work even when used inside a dependency. The main benefit is that this would allow me to validate values based on other values even if those values are transferred in different forms (path/body/...).</p>
<h3 dir="auto">Describe alternatives you've considered</h3>
<p dir="auto">The alternative is to not do the validation in the dependency, but afterwards.</p>
<h3 dir="auto">Additional context</h3>
<p dir="auto">Thank you for your work on this framework, FastAPI is super cool! <g-emoji class="g-emoji" alias="sunglasses" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f60e.png">😎</g-emoji></p>
<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 added a very descriptive title to this issue.</li>
<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. -  <em><strong>See additional context</strong></em></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>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already read and followed all the tutorial in the docs and didn't find an answer.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already checked if it is not related to FastAPI but to <a href="https://github.com/samuelcolvin/pydantic">Pydantic</a>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already checked if it is not related to FastAPI but to <a href="https://github.com/swagger-api/swagger-ui">Swagger UI</a>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I already checked if it is not related to FastAPI but to <a href="https://github.com/Redocly/redoc">ReDoc</a>.</li>
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> After submitting this, I commit to one of:
<ul dir="auto">
<li>Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.</li>
<li>I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.</li>
<li>Implement a Pull Request for a confirmed bug.</li>
</ul>
</li>
</ul>
<h3 dir="auto">Example</h3>
<p dir="auto">Here's a self-contained, <a href="https://stackoverflow.com/help/minimal-reproducible-example" rel="nofollow">minimal, reproducible, example</a> with my use case:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import inspect
from typing import Dict, Type

from fastapi import Depends, FastAPI, File, Form
from pydantic import BaseModel

app = FastAPI()


def as_form(cls: Type[BaseModel]):
    &quot;&quot;&quot;
    Adds an as_form class method to decorated models. The as_form class method
    can be used with FastAPI endpoints
    &quot;&quot;&quot;
    new_params = [
        inspect.Parameter(
            field.alias,
            inspect.Parameter.POSITIONAL_ONLY,
            default=(Form(field.default) if not field.required else Form(...)),
            annotation=field.outer_type_,
        )
        for field in cls.__fields__.values()
    ]

    async def _as_form(**data):
        return cls(**data)

    sig = inspect.signature(_as_form)
    sig = sig.replace(parameters=new_params)
    _as_form.__signature__ = sig
    setattr(cls, &quot;as_form&quot;, _as_form)
    return cls


@as_form
class Item(BaseModel):
    name: str
    another: str
    opts: Dict[str, int] = {}


@app.post(&quot;/test&quot;, response_model=Item)
def endpoint(item: Item = Depends(Item.as_form), data: bytes = File(...)):
    print(len(data))
    return item


if __name__ == &quot;__main__&quot;:
    import json
    import os

    from fastapi.testclient import TestClient

    tc = TestClient(app)

    item = {&quot;name&quot;: &quot;vivalldi&quot;, &quot;another&quot;: &quot;mause&quot;}

    data = bytearray(os.urandom(1))
    files = {&quot;data&quot;: (&quot;data&quot;, data, &quot;text/csv&quot;)}

    r = tc.post(&quot;/test&quot;, data=item, files=files)
    assert r.status_code == 200
    assert r.json() == {&quot;name&quot;: &quot;vivalldi&quot;, &quot;another&quot;: &quot;mause&quot;, &quot;opts&quot;: {}}

    files[&quot;opts&quot;] = (None, json.dumps({&quot;a&quot;: 2}), &quot;application/json&quot;)
    r = tc.post(&quot;/test&quot;, data=item, files=files)
    assert r.status_code == 200
    assert r.json() == {&quot;name&quot;: &quot;vivalldi&quot;, &quot;another&quot;: &quot;mause&quot;, &quot;opts&quot;: {&quot;a&quot;: 2}}"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">inspect</span>
<span class="pl-k">from</span> <span class="pl-s1">typing</span> <span class="pl-k">import</span> <span class="pl-v">Dict</span>, <span class="pl-v">Type</span>

<span class="pl-k">from</span> <span class="pl-s1">fastapi</span> <span class="pl-k">import</span> <span class="pl-v">Depends</span>, <span class="pl-v">FastAPI</span>, <span class="pl-v">File</span>, <span class="pl-v">Form</span>
<span class="pl-k">from</span> <span class="pl-s1">pydantic</span> <span class="pl-k">import</span> <span class="pl-v">BaseModel</span>

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


<span class="pl-k">def</span> <span class="pl-en">as_form</span>(<span class="pl-s1">cls</span>: <span class="pl-v">Type</span>[<span class="pl-v">BaseModel</span>]):
    <span class="pl-s">"""</span>
<span class="pl-s">    Adds an as_form class method to decorated models. The as_form class method</span>
<span class="pl-s">    can be used with FastAPI endpoints</span>
<span class="pl-s">    """</span>
    <span class="pl-s1">new_params</span> <span class="pl-c1">=</span> [
        <span class="pl-s1">inspect</span>.<span class="pl-v">Parameter</span>(
            <span class="pl-s1">field</span>.<span class="pl-s1">alias</span>,
            <span class="pl-s1">inspect</span>.<span class="pl-v">Parameter</span>.<span class="pl-v">POSITIONAL_ONLY</span>,
            <span class="pl-s1">default</span><span class="pl-c1">=</span>(<span class="pl-v">Form</span>(<span class="pl-s1">field</span>.<span class="pl-s1">default</span>) <span class="pl-k">if</span> <span class="pl-c1">not</span> <span class="pl-s1">field</span>.<span class="pl-s1">required</span> <span class="pl-k">else</span> <span class="pl-v">Form</span>(...)),
            <span class="pl-s1">annotation</span><span class="pl-c1">=</span><span class="pl-s1">field</span>.<span class="pl-s1">outer_type_</span>,
        )
        <span class="pl-k">for</span> <span class="pl-s1">field</span> <span class="pl-c1">in</span> <span class="pl-s1">cls</span>.<span class="pl-s1">__fields__</span>.<span class="pl-en">values</span>()
    ]

    <span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">_as_form</span>(<span class="pl-c1">**</span><span class="pl-s1">data</span>):
        <span class="pl-k">return</span> <span class="pl-en">cls</span>(<span class="pl-c1">**</span><span class="pl-s1">data</span>)

    <span class="pl-s1">sig</span> <span class="pl-c1">=</span> <span class="pl-s1">inspect</span>.<span class="pl-en">signature</span>(<span class="pl-s1">_as_form</span>)
    <span class="pl-s1">sig</span> <span class="pl-c1">=</span> <span class="pl-s1">sig</span>.<span class="pl-en">replace</span>(<span class="pl-s1">parameters</span><span class="pl-c1">=</span><span class="pl-s1">new_params</span>)
    <span class="pl-s1">_as_form</span>.<span class="pl-s1">__signature__</span> <span class="pl-c1">=</span> <span class="pl-s1">sig</span>
    <span class="pl-en">setattr</span>(<span class="pl-s1">cls</span>, <span class="pl-s">"as_form"</span>, <span class="pl-s1">_as_form</span>)
    <span class="pl-k">return</span> <span class="pl-s1">cls</span>


<span class="pl-en">@<span class="pl-s1">as_form</span></span>
<span class="pl-k">class</span> <span class="pl-v">Item</span>(<span class="pl-v">BaseModel</span>):
    <span class="pl-s1">name</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">another</span>: <span class="pl-s1">str</span>
    <span class="pl-s1">opts</span>: <span class="pl-v">Dict</span>[<span class="pl-s1">str</span>, <span class="pl-s1">int</span>] <span class="pl-c1">=</span> {}


<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">post</span>(<span class="pl-s">"/test"</span>, <span class="pl-s1">response_model</span><span class="pl-c1">=</span><span class="pl-v">Item</span>)</span>
<span class="pl-k">def</span> <span class="pl-en">endpoint</span>(<span class="pl-s1">item</span>: <span class="pl-v">Item</span> <span class="pl-c1">=</span> <span class="pl-v">Depends</span>(<span class="pl-v">Item</span>.<span class="pl-s1">as_form</span>), <span class="pl-s1">data</span>: <span class="pl-s1">bytes</span> <span class="pl-c1">=</span> <span class="pl-v">File</span>(...)):
    <span class="pl-en">print</span>(<span class="pl-en">len</span>(<span class="pl-s1">data</span>))
    <span class="pl-k">return</span> <span class="pl-s1">item</span>


<span class="pl-k">if</span> <span class="pl-s1">__name__</span> <span class="pl-c1">==</span> <span class="pl-s">"__main__"</span>:
    <span class="pl-k">import</span> <span class="pl-s1">json</span>
    <span class="pl-k">import</span> <span class="pl-s1">os</span>

    <span class="pl-k">from</span> <span class="pl-s1">fastapi</span>.<span class="pl-s1">testclient</span> <span class="pl-k">import</span> <span class="pl-v">TestClient</span>

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

    <span class="pl-s1">item</span> <span class="pl-c1">=</span> {<span class="pl-s">"name"</span>: <span class="pl-s">"vivalldi"</span>, <span class="pl-s">"another"</span>: <span class="pl-s">"mause"</span>}

    <span class="pl-s1">data</span> <span class="pl-c1">=</span> <span class="pl-en">bytearray</span>(<span class="pl-s1">os</span>.<span class="pl-en">urandom</span>(<span class="pl-c1">1</span>))
    <span class="pl-s1">files</span> <span class="pl-c1">=</span> {<span class="pl-s">"data"</span>: (<span class="pl-s">"data"</span>, <span class="pl-s1">data</span>, <span class="pl-s">"text/csv"</span>)}

    <span class="pl-s1">r</span> <span class="pl-c1">=</span> <span class="pl-s1">tc</span>.<span class="pl-en">post</span>(<span class="pl-s">"/test"</span>, <span class="pl-s1">data</span><span class="pl-c1">=</span><span class="pl-s1">item</span>, <span class="pl-s1">files</span><span class="pl-c1">=</span><span class="pl-s1">files</span>)
    <span class="pl-k">assert</span> <span class="pl-s1">r</span>.<span class="pl-s1">status_code</span> <span class="pl-c1">==</span> <span class="pl-c1">200</span>
    <span class="pl-k">assert</span> <span class="pl-s1">r</span>.<span class="pl-en">json</span>() <span class="pl-c1">==</span> {<span class="pl-s">"name"</span>: <span class="pl-s">"vivalldi"</span>, <span class="pl-s">"another"</span>: <span class="pl-s">"mause"</span>, <span class="pl-s">"opts"</span>: {}}

    <span class="pl-s1">files</span>[<span class="pl-s">"opts"</span>] <span class="pl-c1">=</span> (<span class="pl-c1">None</span>, <span class="pl-s1">json</span>.<span class="pl-en">dumps</span>({<span class="pl-s">"a"</span>: <span class="pl-c1">2</span>}), <span class="pl-s">"application/json"</span>)
    <span class="pl-s1">r</span> <span class="pl-c1">=</span> <span class="pl-s1">tc</span>.<span class="pl-en">post</span>(<span class="pl-s">"/test"</span>, <span class="pl-s1">data</span><span class="pl-c1">=</span><span class="pl-s1">item</span>, <span class="pl-s1">files</span><span class="pl-c1">=</span><span class="pl-s1">files</span>)
    <span class="pl-k">assert</span> <span class="pl-s1">r</span>.<span class="pl-s1">status_code</span> <span class="pl-c1">==</span> <span class="pl-c1">200</span>
    <span class="pl-k">assert</span> <span class="pl-s1">r</span>.<span class="pl-en">json</span>() <span class="pl-c1">==</span> {<span class="pl-s">"name"</span>: <span class="pl-s">"vivalldi"</span>, <span class="pl-s">"another"</span>: <span class="pl-s">"mause"</span>, <span class="pl-s">"opts"</span>: {<span class="pl-s">"a"</span>: <span class="pl-c1">2</span>}}</pre></div>
<h3 dir="auto">Description</h3>

<h4 dir="auto">tl;dr</h4>
<p dir="auto">Complex data types (objects) are not supported in <code class="notranslate">multipart/form-data</code> by FastAPI. There are workarounds for top level fields in pydantic models; nested objects are not supported. Save the above script as <code class="notranslate">nested.py</code>. Run <code class="notranslate">python ./nested.py</code> to see failing assertions. And call <code class="notranslate">uvicorn nested:app</code> to run the FastAPI app locally. Local tests can be run using httpie script <a href="https://gist.github.com/Vivalldi/629dc95979a85a83fef9a867fa19525f">in this gist</a></p>
<h4 dir="auto">full</h4>
<p dir="auto">If you look through the StackOverflow &amp; FastAPI issues you'll find plenty of examples of how to convert a model into a form. This issue is to address the shortcomings of those workarounds. The main focus of this is to determine the best way to work with multi-content multipart/form-data requests.</p>
<p dir="auto">Per the <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#special-considerations-for-multipart-content">OpenAPI Special Considerations for <code class="notranslate">multipart</code> Content</a> "boundaries MAY be used to separate sections of the content being transferred." This indicates that you can specify the content type of each individual part. An example of a combination multipart request can be found <a href="https://gist.github.com/Vivalldi/629dc95979a85a83fef9a867fa19525f">in this gist</a>. The gist was generated using httpie.</p>
<p dir="auto">Going forward I intend to investigate what can be done from a workaround standpoint. Initially, I think we can adjust the workarounds to set <code class="notranslate">object</code> types to a <code class="notranslate">File</code> and parse those files as JSON (might require some model tweaks as well). Additionally, if this issue gains traction I will look into making changes that allow FastAPI to better support multi-content multipart requests.</p>
<h3 dir="auto">Environment</h3>
<ul dir="auto">
<li>OS: macOS</li>
<li>FastAPI Version: 0.58.1</li>
<li>Python version: 3.8.6</li>
</ul>
<h3 dir="auto">Additional context</h3>

<p dir="auto">This is a spin-off of <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="744868177" data-permission-text="Title is private" data-url="https://github.com/tiangolo/fastapi/issues/2365" data-hovercard-type="issue" data-hovercard-url="/tiangolo/fastapi/issues/2365/hovercard" href="https://github.com/tiangolo/fastapi/issues/2365">#2365</a> at Mause's request</p>
<p dir="auto">Since the root cause is complex types (objects) in forms aren't supported, this may be a duplicate of <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="736067069" data-permission-text="Title is private" data-url="https://github.com/tiangolo/fastapi/issues/2295" data-hovercard-type="issue" data-hovercard-url="/tiangolo/fastapi/issues/2295/hovercard" href="https://github.com/tiangolo/fastapi/issues/2295">#2295</a></p>