<p dir="auto"><strong>Description</strong></p>
<p dir="auto">Is there a way to use pydantic models for GET requests? I would like to have a similar interface for both query params and for the body. So for instance, an example could look like this:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="class PingArgs(BaseModel):
    &quot;&quot;&quot;Model input for PingArgs.&quot;&quot;&quot;

    dt: datetime.datetime = ...
    to_sum: List[int] = ...

    @validator(&quot;dt&quot;, pre=False, always=True, whole=True)
    def validate_dt(cls, v, values):
        &quot;&quot;&quot;Validate dt.&quot;&quot;&quot;
        parsed_dt = v.replace(tzinfo=None)
        return parsed_dt

@router.get(&quot;/ping&quot;, tags=[&quot;basic&quot;])
def ping(args: PingArgs, request: Request):
    &quot;&quot;&quot;Example.&quot;&quot;&quot;
    return JSONResponse(
        status_code=starlette.status.HTTP_200_OK,
        content={&quot;detail&quot;: &quot;pong&quot;, &quot;dt&quot;: args.dt.isoformat() &quot;summed&quot;: sum(x for x in args.to_sum)},
    )"><pre class="notranslate"><code class="notranslate">class PingArgs(BaseModel):
    """Model input for PingArgs."""

    dt: datetime.datetime = ...
    to_sum: List[int] = ...

    @validator("dt", pre=False, always=True, whole=True)
    def validate_dt(cls, v, values):
        """Validate dt."""
        parsed_dt = v.replace(tzinfo=None)
        return parsed_dt

@router.get("/ping", tags=["basic"])
def ping(args: PingArgs, request: Request):
    """Example."""
    return JSONResponse(
        status_code=starlette.status.HTTP_200_OK,
        content={"detail": "pong", "dt": args.dt.isoformat() "summed": sum(x for x in args.to_sum)},
    )
</code></pre></div>
<p dir="auto">Where as, right now I think you would have to do something like this:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="@router.get(&quot;/ping&quot;, tags=[&quot;basic&quot;])
def ping(dt: datetime.datetime = Query(None), to_sum: List[int] = Query(None), request: Request):
    &quot;&quot;&quot;Example.&quot;&quot;&quot;
    parsed_dt = dt.replace(tzinfo=None)
    
    return JSONResponse(
        status_code=starlette.status.HTTP_200_OK,
        content={&quot;detail&quot;: &quot;pong&quot;, &quot;dt&quot;: dt.isoformat() &quot;summed&quot;: sum(x for x in to_sum)},
    )"><pre class="notranslate"><code class="notranslate">@router.get("/ping", tags=["basic"])
def ping(dt: datetime.datetime = Query(None), to_sum: List[int] = Query(None), request: Request):
    """Example."""
    parsed_dt = dt.replace(tzinfo=None)
    
    return JSONResponse(
        status_code=starlette.status.HTTP_200_OK,
        content={"detail": "pong", "dt": dt.isoformat() "summed": sum(x for x in to_sum)},
    )
</code></pre></div>
<p dir="auto">Hope this can be clarified.</p>
<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.</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">Description</h3>
<p dir="auto">Websocket routes appear to only work on the main FastAPI object, not on APIRouter objects (instead it raises WebSocketDisconnect).</p>
<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="from fastapi import FastAPI
from fastapi.testclient import TestClient
from fastapi.websockets import WebSocket
from fastapi import APIRouter 

app = FastAPI()
router = APIRouter(prefix=&quot;/test&quot;)
app.include_router(router)


@app.websocket_route(&quot;/ws&quot;)
async def websocket(websocket: WebSocket):
    await websocket.accept()
    await websocket.send_json({&quot;msg&quot;: &quot;Hello WebSocket&quot;})
    await websocket.close()


@router.websocket_route(&quot;/ws&quot;)
async def websocket(websocket: WebSocket):
    await websocket.accept()
    await websocket.send_json({&quot;msg&quot;: &quot;Hello WebSocket 2&quot;})
    await websocket.close()


def test_websocket():
    client = TestClient(app)
    with client.websocket_connect(&quot;/ws&quot;) as websocket:
        data = websocket.receive_json()
        assert data == {&quot;msg&quot;: &quot;Hello WebSocket&quot;}


def test_websocket2():
    client = TestClient(app)
    with client.websocket_connect(&quot;/test/ws&quot;) as websocket:
        data = websocket.receive_json()
        assert data == {&quot;msg&quot;: &quot;Hello WebSocket 2&quot;}"><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-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-k">from</span> <span class="pl-s1">fastapi</span>.<span class="pl-s1">websockets</span> <span class="pl-k">import</span> <span class="pl-v">WebSocket</span>
<span class="pl-k">from</span> <span class="pl-s1">fastapi</span> <span class="pl-k">import</span> <span class="pl-v">APIRouter</span> 

<span class="pl-s1">app</span> <span class="pl-c1">=</span> <span class="pl-v">FastAPI</span>()
<span class="pl-s1">router</span> <span class="pl-c1">=</span> <span class="pl-v">APIRouter</span>(<span class="pl-s1">prefix</span><span class="pl-c1">=</span><span class="pl-s">"/test"</span>)
<span class="pl-s1">app</span>.<span class="pl-en">include_router</span>(<span class="pl-s1">router</span>)


<span class="pl-en">@<span class="pl-s1">app</span>.<span class="pl-en">websocket_route</span>(<span class="pl-s">"/ws"</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">websocket</span>(<span class="pl-s1">websocket</span>: <span class="pl-v">WebSocket</span>):
    <span class="pl-k">await</span> <span class="pl-s1">websocket</span>.<span class="pl-en">accept</span>()
    <span class="pl-k">await</span> <span class="pl-s1">websocket</span>.<span class="pl-en">send_json</span>({<span class="pl-s">"msg"</span>: <span class="pl-s">"Hello WebSocket"</span>})
    <span class="pl-k">await</span> <span class="pl-s1">websocket</span>.<span class="pl-en">close</span>()


<span class="pl-en">@<span class="pl-s1">router</span>.<span class="pl-en">websocket_route</span>(<span class="pl-s">"/ws"</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">websocket</span>(<span class="pl-s1">websocket</span>: <span class="pl-v">WebSocket</span>):
    <span class="pl-k">await</span> <span class="pl-s1">websocket</span>.<span class="pl-en">accept</span>()
    <span class="pl-k">await</span> <span class="pl-s1">websocket</span>.<span class="pl-en">send_json</span>({<span class="pl-s">"msg"</span>: <span class="pl-s">"Hello WebSocket 2"</span>})
    <span class="pl-k">await</span> <span class="pl-s1">websocket</span>.<span class="pl-en">close</span>()


<span class="pl-k">def</span> <span class="pl-en">test_websocket</span>():
    <span class="pl-s1">client</span> <span class="pl-c1">=</span> <span class="pl-v">TestClient</span>(<span class="pl-s1">app</span>)
    <span class="pl-k">with</span> <span class="pl-s1">client</span>.<span class="pl-en">websocket_connect</span>(<span class="pl-s">"/ws"</span>) <span class="pl-k">as</span> <span class="pl-s1">websocket</span>:
        <span class="pl-s1">data</span> <span class="pl-c1">=</span> <span class="pl-s1">websocket</span>.<span class="pl-en">receive_json</span>()
        <span class="pl-k">assert</span> <span class="pl-s1">data</span> <span class="pl-c1">==</span> {<span class="pl-s">"msg"</span>: <span class="pl-s">"Hello WebSocket"</span>}


<span class="pl-k">def</span> <span class="pl-en">test_websocket2</span>():
    <span class="pl-s1">client</span> <span class="pl-c1">=</span> <span class="pl-v">TestClient</span>(<span class="pl-s1">app</span>)
    <span class="pl-k">with</span> <span class="pl-s1">client</span>.<span class="pl-en">websocket_connect</span>(<span class="pl-s">"/test/ws"</span>) <span class="pl-k">as</span> <span class="pl-s1">websocket</span>:
        <span class="pl-s1">data</span> <span class="pl-c1">=</span> <span class="pl-s1">websocket</span>.<span class="pl-en">receive_json</span>()
        <span class="pl-k">assert</span> <span class="pl-s1">data</span> <span class="pl-c1">==</span> {<span class="pl-s">"msg"</span>: <span class="pl-s">"Hello WebSocket 2"</span>}</pre></div>
<h3 dir="auto">Further info</h3>
<ul dir="auto">
<li>Only the second websocket fails in the example above.</li>
<li>Error log:</li>
</ul>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="def test_websocket2():
        client = TestClient(app)
&gt;      with client.websocket_connect(&quot;/test/ws&quot;) as websocket:

/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/starlette/testclient.py:444: in websocket_connect
super().request(&quot;GET&quot;, url, **kwargs)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/requests/sessions.py:542: in request
resp = self.send(prep, **send_kwargs)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/requests/sessions.py:655: in send
r = adapter.send(request, **kwargs)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/starlette/testclient.py:145: in send
session = WebSocketTestSession(self.app, scope)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/starlette/testclient.py:278: in __init__
self._raise_on_close(message)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = &lt;starlette.testclient.WebSocketTestSession object at 0x7f7cbc689c50&gt;
message = {'code': 1000, 'type': 'websocket.close'}
def _raise_on_close(self, message: Message) -&gt; None:
        if message[&quot;type&quot;] == &quot;websocket.close&quot;:
&gt;           raise WebSocketDisconnect(message.get(&quot;code&quot;, 1000))
E           starlette.websockets.WebSocketDisconnect: 1000
"><pre class="notranslate"><code class="notranslate">def test_websocket2():
        client = TestClient(app)
&gt;      with client.websocket_connect("/test/ws") as websocket:

/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/starlette/testclient.py:444: in websocket_connect
super().request("GET", url, **kwargs)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/requests/sessions.py:542: in request
resp = self.send(prep, **send_kwargs)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/requests/sessions.py:655: in send
r = adapter.send(request, **kwargs)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/starlette/testclient.py:145: in send
session = WebSocketTestSession(self.app, scope)
/opt/conda/envs/build_env/envs/run_env/lib/python3.6/site-packages/starlette/testclient.py:278: in __init__
self._raise_on_close(message)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = &lt;starlette.testclient.WebSocketTestSession object at 0x7f7cbc689c50&gt;
message = {'code': 1000, 'type': 'websocket.close'}
def _raise_on_close(self, message: Message) -&gt; None:
        if message["type"] == "websocket.close":
&gt;           raise WebSocketDisconnect(message.get("code", 1000))
E           starlette.websockets.WebSocketDisconnect: 1000

</code></pre></div>
<h3 dir="auto">Environment</h3>
<ul dir="auto">
<li>OS: Amazon Linux Workspace</li>
<li>FastAPI Version: 0.63.0</li>
<li>Python version: 3.6.8</li>
</ul>