<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.</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>
</ul>
<h3 dir="auto">Commit to Help</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I commit to help with one of those options 👆</li>
</ul>
<h3 dir="auto">Example Code</h3>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="from fastapi import FastAPI"><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></pre></div>
<h3 dir="auto">Description</h3>
<p dir="auto">We have a test that does something like this:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="now = utc_datetime()
url = client.app.url_path_for(&quot;balances&quot;)
start_date = now - timedelta(minutes=1)
start_date_param = urlencode({&quot;start_date&quot;: start_date.isoformat()})
end_date_param = urlencode({&quot;end_date&quot;: now.isoformat()})
response = client.get(url + f&quot;?{start_date_param}&amp;{end_date_param}&quot;)"><pre class="notranslate"><span class="pl-s1">now</span> <span class="pl-c1">=</span> <span class="pl-en">utc_datetime</span>()
<span class="pl-s1">url</span> <span class="pl-c1">=</span> <span class="pl-s1">client</span>.<span class="pl-s1">app</span>.<span class="pl-en">url_path_for</span>(<span class="pl-s">"balances"</span>)
<span class="pl-s1">start_date</span> <span class="pl-c1">=</span> <span class="pl-s1">now</span> <span class="pl-c1">-</span> <span class="pl-en">timedelta</span>(<span class="pl-s1">minutes</span><span class="pl-c1">=</span><span class="pl-c1">1</span>)
<span class="pl-s1">start_date_param</span> <span class="pl-c1">=</span> <span class="pl-en">urlencode</span>({<span class="pl-s">"start_date"</span>: <span class="pl-s1">start_date</span>.<span class="pl-en">isoformat</span>()})
<span class="pl-s1">end_date_param</span> <span class="pl-c1">=</span> <span class="pl-en">urlencode</span>({<span class="pl-s">"end_date"</span>: <span class="pl-s1">now</span>.<span class="pl-en">isoformat</span>()})
<span class="pl-s1">response</span> <span class="pl-c1">=</span> <span class="pl-s1">client</span>.<span class="pl-en">get</span>(<span class="pl-s1">url</span> <span class="pl-c1">+</span> <span class="pl-s">f"?<span class="pl-s1"><span class="pl-kos">{</span><span class="pl-s1">start_date_param</span><span class="pl-kos">}</span></span>&amp;<span class="pl-s1"><span class="pl-kos">{</span><span class="pl-s1">end_date_param</span><span class="pl-kos">}</span></span>"</span>)</pre></div>
<p dir="auto">With FastAPI 0.86 everything works. Starting from 0.87 (which uses <code class="notranslate">starlette</code> 0.21.0) our endpoint errors with:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{'error': {'code': 'validation_error', 'message': 'Validation Error', 'errors': [{'attribute': 'query -&gt; start_date', 'code': 'value_error.datetime', 'message': 'invalid datetime format', 'ctx': None}, {'attribute': 'query -&gt; end_date', 'code': 'value_error.datetime', 'message': 'invalid datetime format', 'ctx': None}]}}"><pre class="notranslate">{<span class="pl-s">'error'</span>: {<span class="pl-s">'code'</span>: <span class="pl-s">'validation_error'</span>, <span class="pl-s">'message'</span>: <span class="pl-s">'Validation Error'</span>, <span class="pl-s">'errors'</span>: [{<span class="pl-s">'attribute'</span>: <span class="pl-s">'query -&gt; start_date'</span>, <span class="pl-s">'code'</span>: <span class="pl-s">'value_error.datetime'</span>, <span class="pl-s">'message'</span>: <span class="pl-s">'invalid datetime format'</span>, <span class="pl-s">'ctx'</span>: <span class="pl-c1">None</span>}, {<span class="pl-s">'attribute'</span>: <span class="pl-s">'query -&gt; end_date'</span>, <span class="pl-s">'code'</span>: <span class="pl-s">'value_error.datetime'</span>, <span class="pl-s">'message'</span>: <span class="pl-s">'invalid datetime format'</span>, <span class="pl-s">'ctx'</span>: <span class="pl-c1">None</span>}]}}</pre></div>
<p dir="auto">If I force the installation of <code class="notranslate">starlette</code> 0.22.0 and rerun the test, everything works. This is a known bug of <code class="notranslate">starlette</code> 0.21.0 and it has been fixed in 0.22.0:</p>
<ul dir="auto">
<li><a href="https://github.com/encode/starlette/pull/1953/files">https://github.com/encode/starlette/pull/1953/files</a></li>
<li><a href="https://github.com/encode/starlette/releases/tag/0.22.0">https://github.com/encode/starlette/releases/tag/0.22.0</a></li>
</ul>
<h2 dir="auto">Solution</h2>
<p dir="auto">Bump <code class="notranslate">starlette</code> version in FastAPI to 0.22.0</p>
<h2 dir="auto">Note</h2>
<p dir="auto">I can make this small PR with the update as soon as you confirm/acknowledge this issue.</p>
<h3 dir="auto">Operating System</h3>
<p dir="auto">macOS</p>
<h3 dir="auto">Operating System Details</h3>
<p dir="auto"><em>No response</em></p>
<h3 dir="auto">FastAPI Version</h3>
<p dir="auto">0.87.0</p>
<h3 dir="auto">Python Version</h3>
<p dir="auto">3.10.0</p>
<h3 dir="auto">Additional Context</h3>
<p dir="auto"><em>No response</em></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.</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>
</ul>
<h3 dir="auto">Commit to Help</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> I commit to help with one of those options <g-emoji class="g-emoji" alias="point_up_2" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f446.png">👆</g-emoji></li>
</ul>
<h3 dir="auto">Example Code</h3>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import logging

from fastapi import FastAPI

app = FastAPI()


@app.get(&quot;/example&quot;)
async def _show_encoding_error(look_for: str):
    return {&quot;found&quot;: look_for}


if __name__ == '__main__':
    from fastapi.testclient import TestClient

    with TestClient(app) as client:
        params = {&quot;look_for&quot;: &quot;plain text&quot;}
        resp = client.get(&quot;/example&quot;, params=params).json()
        logging.warning(resp)
        assert resp[&quot;found&quot;] == &quot;plain text&quot;

        params = {&quot;look_for&quot;: &quot;España&quot;}
        resp = client.get(&quot;/example&quot;, params=params).json()
        logging.warning(resp)
        assert resp[&quot;found&quot;] == &quot;España&quot;, resp[&quot;found&quot;]"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">logging</span>

<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-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">"/example"</span>)</span>
<span class="pl-k">async</span> <span class="pl-k">def</span> <span class="pl-en">_show_encoding_error</span>(<span class="pl-s1">look_for</span>: <span class="pl-s1">str</span>):
    <span class="pl-k">return</span> {<span class="pl-s">"found"</span>: <span class="pl-s1">look_for</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">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">with</span> <span class="pl-v">TestClient</span>(<span class="pl-s1">app</span>) <span class="pl-k">as</span> <span class="pl-s1">client</span>:
        <span class="pl-s1">params</span> <span class="pl-c1">=</span> {<span class="pl-s">"look_for"</span>: <span class="pl-s">"plain text"</span>}
        <span class="pl-s1">resp</span> <span class="pl-c1">=</span> <span class="pl-s1">client</span>.<span class="pl-en">get</span>(<span class="pl-s">"/example"</span>, <span class="pl-s1">params</span><span class="pl-c1">=</span><span class="pl-s1">params</span>).<span class="pl-en">json</span>()
        <span class="pl-s1">logging</span>.<span class="pl-en">warning</span>(<span class="pl-s1">resp</span>)
        <span class="pl-k">assert</span> <span class="pl-s1">resp</span>[<span class="pl-s">"found"</span>] <span class="pl-c1">==</span> <span class="pl-s">"plain text"</span>

        <span class="pl-s1">params</span> <span class="pl-c1">=</span> {<span class="pl-s">"look_for"</span>: <span class="pl-s">"España"</span>}
        <span class="pl-s1">resp</span> <span class="pl-c1">=</span> <span class="pl-s1">client</span>.<span class="pl-en">get</span>(<span class="pl-s">"/example"</span>, <span class="pl-s1">params</span><span class="pl-c1">=</span><span class="pl-s1">params</span>).<span class="pl-en">json</span>()
        <span class="pl-s1">logging</span>.<span class="pl-en">warning</span>(<span class="pl-s1">resp</span>)
        <span class="pl-k">assert</span> <span class="pl-s1">resp</span>[<span class="pl-s">"found"</span>] <span class="pl-c1">==</span> <span class="pl-s">"España"</span>, <span class="pl-s1">resp</span>[<span class="pl-s">"found"</span>]</pre></div>
<h3 dir="auto">Description</h3>
<p dir="auto">After the change to <code class="notranslate">httpx</code> for the <code class="notranslate">TestClient</code> in <strong>v0.87.0</strong>, the query parameters are not properly encoded? when sending requests with it, and strings are corrupted when received in the endpoints.</p>
<p dir="auto">The example app works as expected if called from the SwaggerUI or from another python process using a <em>plain</em> <code class="notranslate">httpx.Client</code>, so it appears something broke with the new wrapping for <code class="notranslate">TestClient</code> <g-emoji class="g-emoji" alias="smiling_face_with_tear" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f972.png">🥲</g-emoji></p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import httpx

params = {&quot;look_for&quot;: &quot;España&quot;}
with httpx.Client(base_url=&quot;http://localhost:8000/&quot;) as client:
    resp = client.get(&quot;/example&quot;, params=params).json()
    assert resp[&quot;found&quot;] == &quot;España&quot;"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">httpx</span>

<span class="pl-s1">params</span> <span class="pl-c1">=</span> {<span class="pl-s">"look_for"</span>: <span class="pl-s">"España"</span>}
<span class="pl-k">with</span> <span class="pl-s1">httpx</span>.<span class="pl-v">Client</span>(<span class="pl-s1">base_url</span><span class="pl-c1">=</span><span class="pl-s">"http://localhost:8000/"</span>) <span class="pl-k">as</span> <span class="pl-s1">client</span>:
    <span class="pl-s1">resp</span> <span class="pl-c1">=</span> <span class="pl-s1">client</span>.<span class="pl-en">get</span>(<span class="pl-s">"/example"</span>, <span class="pl-s1">params</span><span class="pl-c1">=</span><span class="pl-s1">params</span>).<span class="pl-en">json</span>()
    <span class="pl-k">assert</span> <span class="pl-s1">resp</span>[<span class="pl-s">"found"</span>] <span class="pl-c1">==</span> <span class="pl-s">"España"</span></pre></div>
<h3 dir="auto">Operating System</h3>
<p dir="auto">macOS</p>
<h3 dir="auto">Operating System Details</h3>
<p dir="auto">M1, running arm64 arch</p>
<h3 dir="auto">FastAPI Version</h3>
<p dir="auto">0.87.0</p>
<h3 dir="auto">Python Version</h3>
<p dir="auto">Python 3.10.5</p>
<h3 dir="auto">Additional Context</h3>
<p dir="auto">starlette-0.21<br>
httpx-0.23.0</p>
<p dir="auto">Discovered when trying to migrate the test suite for a ~big project previously using fastapi-0.85.1 + starlette-0.20.4.</p>
<p dir="auto">All minor syntax changes from old <code class="notranslate">requests</code> to new <code class="notranslate">httpx</code> were under control, but in one unit test, <strong>a string with an accent</strong> was making some search to fail without results (test is sending "Formalización" but endpoint is receiving <strong>"FormalizaciÃ³n"</strong> <g-emoji class="g-emoji" alias="scream" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f631.png">😱</g-emoji>), and I was getting crazy <g-emoji class="g-emoji" alias="sweat_smile" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f605.png">😅</g-emoji></p>