diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 47ef7c07f..000000000
--- a/.flake8
+++ /dev/null
@@ -1,5 +0,0 @@
-[flake8]
-max-line-length = 88
-select = C,E,F,W,B,B9
-ignore = E203, E501, W503
-exclude = __init__.py
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index fe4c5ee86..ab27d4b85 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -18,6 +18,8 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: "3.7"
+ cache: "pip"
+ cache-dependency-path: pyproject.toml
- uses: actions/cache@v3
id: cache
with:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7f87be700..ddc43c942 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -21,11 +21,13 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ cache: "pip"
+ cache-dependency-path: pyproject.toml
- uses: actions/cache@v3
id: cache
with:
path: ${{ env.pythonLocation }}
- key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v02
+ key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v03
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: pip install -e .[all,dev,doc,test]
@@ -52,6 +54,8 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: '3.8'
+ cache: "pip"
+ cache-dependency-path: pyproject.toml
- name: Get coverage files
uses: actions/download-artifact@v3
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bd5b8641a..96f097caa 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,25 +12,18 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
- rev: v3.1.0
+ rev: v3.2.2
hooks:
- id: pyupgrade
args:
- --py3-plus
- --keep-runtime-typing
-- repo: https://github.com/PyCQA/autoflake
- rev: v1.7.7
+- repo: https://github.com/charliermarsh/ruff-pre-commit
+ rev: v0.0.138
hooks:
- - id: autoflake
+ - id: ruff
args:
- - --recursive
- - --in-place
- - --remove-all-unused-imports
- - --remove-unused-variables
- - --expand-star-imports
- - --exclude
- - __init__.py
- - --remove-duplicate-keys
+ - --fix
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
diff --git a/README.md b/README.md
index fe0ad49de..7c4a6c4b4 100644
--- a/README.md
+++ b/README.md
@@ -427,7 +427,7 @@ For a more complete example including more features, see the Strawberry and other libraries.
* Many extra features (thanks to Starlette) as:
* **WebSockets**
- * extremely easy tests based on `requests` and `pytest`
+ * extremely easy tests based on HTTPX and `pytest`
* **CORS**
* **Cookie Sessions**
* ...and more.
@@ -447,7 +447,7 @@ Used by Pydantic:
Used by Starlette:
-* requests
- Required if you want to use the `TestClient`.
+* httpx
- Required if you want to use the `TestClient`.
* jinja2
- Required if you want to use the default template configuration.
* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
* itsdangerous
- Required for `SessionMiddleware` support.
diff --git a/docs/az/docs/index.md b/docs/az/docs/index.md
index 3129f9dc6..282c15032 100644
--- a/docs/az/docs/index.md
+++ b/docs/az/docs/index.md
@@ -446,7 +446,7 @@ Used by Pydantic:
Used by Starlette:
-* requests
- Required if you want to use the `TestClient`.
+* httpx
- Required if you want to use the `TestClient`.
* jinja2
- Required if you want to use the default template configuration.
* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
* itsdangerous
- Required for `SessionMiddleware` support.
diff --git a/docs/de/docs/features.md b/docs/de/docs/features.md
index f825472a9..f281afd1e 100644
--- a/docs/de/docs/features.md
+++ b/docs/de/docs/features.md
@@ -169,7 +169,7 @@ Mit **FastAPI** bekommen Sie viele von **Starlette**'s Funktionen (da FastAPI nu
* **WebSocket**-Unterstützung.
* Hintergrundaufgaben im selben Prozess.
* Ereignisse für das Starten und Herunterfahren.
-* Testclient basierend auf `requests`.
+* Testclient basierend auf HTTPX.
* **CORS**, GZip, statische Dateien, Antwortfluss.
* **Sitzungs und Cookie** Unterstützung.
* 100% Testabdeckung.
diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md
index 07f51b1be..68fc8b753 100644
--- a/docs/de/docs/index.md
+++ b/docs/de/docs/index.md
@@ -445,7 +445,7 @@ Used by Pydantic:
Used by Starlette:
-* requests
- Required if you want to use the `TestClient`.
+* httpx
- Required if you want to use the `TestClient`.
* jinja2
- Required if you want to use the default template configuration.
* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
* itsdangerous
- Required for `SessionMiddleware` support.
diff --git a/docs/en/docs/advanced/async-tests.md b/docs/en/docs/advanced/async-tests.md
index e34f48f17..9b39d70fc 100644
--- a/docs/en/docs/advanced/async-tests.md
+++ b/docs/en/docs/advanced/async-tests.md
@@ -1,6 +1,6 @@
# Async Tests
-You have already seen how to test your **FastAPI** applications using the provided `TestClient`, but with it, you can't test or run any other `async` function in your (synchronous) pytest functions.
+You have already seen how to test your **FastAPI** applications using the provided `TestClient`. Up to now, you have only seen how to write synchronous tests, without using `async` functions.
Being able to use asynchronous functions in your tests could be useful, for example, when you're querying your database asynchronously. Imagine you want to test sending requests to your FastAPI application and then verify that your backend successfully wrote the correct data in the database, while using an async database library.
@@ -8,7 +8,7 @@ Let's look at how we can make that work.
## pytest.mark.anyio
-If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. Anyio provides a neat plugin for this, that allows us to specify that some test functions are to be called asynchronously.
+If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. AnyIO provides a neat plugin for this, that allows us to specify that some test functions are to be called asynchronously.
## HTTPX
@@ -16,13 +16,7 @@ Even if your **FastAPI** application uses normal `def` functions instead of `asy
The `TestClient` does some magic inside to call the asynchronous FastAPI application in your normal `def` test functions, using standard pytest. But that magic doesn't work anymore when we're using it inside asynchronous functions. By running our tests asynchronously, we can no longer use the `TestClient` inside our test functions.
-Luckily there's a nice alternative, called HTTPX.
-
-HTTPX is an HTTP client for Python 3 that allows us to query our FastAPI application similarly to how we did it with the `TestClient`.
-
-If you're familiar with the Requests library, you'll find that the API of HTTPX is almost identical.
-
-The important difference for us is that with HTTPX we are not limited to synchronous, but can also make asynchronous requests.
+The `TestClient` is based on HTTPX, and luckily, we can use it directly to test the API.
## Example
@@ -85,7 +79,7 @@ This is the equivalent to:
response = client.get('/')
```
-that we used to make our requests with the `TestClient`.
+...that we used to make our requests with the `TestClient`.
!!! tip
Note that we're using async/await with the new `AsyncClient` - the request is asynchronous.
diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md
index ed90f29be..3bf49e392 100644
--- a/docs/en/docs/advanced/middleware.md
+++ b/docs/en/docs/advanced/middleware.md
@@ -68,7 +68,7 @@ Enforces that all incoming requests have a correctly set `Host` header, in order
The following arguments are supported:
-* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard domains such as `*.example.com` are supported for matching subdomains to allow any hostname either use `allowed_hosts=["*"]` or omit the middleware.
+* `allowed_hosts` - A list of domain names that should be allowed as hostnames. Wildcard domains such as `*.example.com` are supported for matching subdomains. To allow any hostname either use `allowed_hosts=["*"]` or omit the middleware.
If an incoming request does not validate correctly then a `400` response will be sent.
diff --git a/docs/en/docs/advanced/openapi-callbacks.md b/docs/en/docs/advanced/openapi-callbacks.md
index 656ddbb3f..71924ce8b 100644
--- a/docs/en/docs/advanced/openapi-callbacks.md
+++ b/docs/en/docs/advanced/openapi-callbacks.md
@@ -50,7 +50,7 @@ It could be just one or two lines of code, like:
```Python
callback_url = "https://example.com/api/v1/invoices/events/"
-requests.post(callback_url, json={"description": "Invoice paid", "paid": True})
+httpx.post(callback_url, json={"description": "Invoice paid", "paid": True})
```
But possibly the most important part of the callback is making sure that your API user (the external developer) implements the *external API* correctly, according to the data that *your API* is going to send in the request body of the callback, etc.
@@ -64,7 +64,7 @@ This example doesn't implement the callback itself (that could be just a line of
!!! tip
The actual callback is just an HTTP request.
- When implementing the callback yourself, you could use something like HTTPX or Requests.
+ When implementing the callback yourself, you could use something like HTTPX or Requests.
## Write the callback documentation code
diff --git a/docs/en/docs/advanced/websockets.md b/docs/en/docs/advanced/websockets.md
index 0e9bc5b06..3cf840819 100644
--- a/docs/en/docs/advanced/websockets.md
+++ b/docs/en/docs/advanced/websockets.md
@@ -112,17 +112,15 @@ In WebSocket endpoints you can import from `fastapi` and use:
They work the same way as for other FastAPI endpoints/*path operations*:
-```Python hl_lines="58-65 68-83"
+```Python hl_lines="66-77 76-91"
{!../../../docs_src/websockets/tutorial002.py!}
```
!!! info
- In a WebSocket it doesn't really make sense to raise an `HTTPException`. So it's better to close the WebSocket connection directly.
+ As this is a WebSocket it doesn't really make sense to raise an `HTTPException`, instead we raise a `WebSocketException`.
You can use a closing code from the valid codes defined in the specification.
- In the future, there will be a `WebSocketException` that you will be able to `raise` from anywhere, and add exception handlers for it. It depends on the PR #527 in Starlette.
-
### Try the WebSockets with dependencies
If your file is named `main.py`, run your application with:
diff --git a/docs/en/docs/alternatives.md b/docs/en/docs/alternatives.md
index bcd406bf9..0f074ccf3 100644
--- a/docs/en/docs/alternatives.md
+++ b/docs/en/docs/alternatives.md
@@ -367,7 +367,7 @@ It has:
* WebSocket support.
* In-process background tasks.
* Startup and shutdown events.
-* Test client built on requests.
+* Test client built on HTTPX.
* CORS, GZip, Static Files, Streaming responses.
* Session and Cookie support.
* 100% test coverage.
diff --git a/docs/en/docs/features.md b/docs/en/docs/features.md
index 02bb3ac1f..387ff86c9 100644
--- a/docs/en/docs/features.md
+++ b/docs/en/docs/features.md
@@ -166,7 +166,7 @@ With **FastAPI** you get all of **Starlette**'s features (as FastAPI is just Sta
* **WebSocket** support.
* In-process background tasks.
* Startup and shutdown events.
-* Test client built on `requests`.
+* Test client built on HTTPX.
* **CORS**, GZip, Static Files, Streaming responses.
* **Session and Cookie** support.
* 100% test coverage.
diff --git a/docs/en/docs/help-fastapi.md b/docs/en/docs/help-fastapi.md
index 8d8d708ed..a7ac9415f 100644
--- a/docs/en/docs/help-fastapi.md
+++ b/docs/en/docs/help-fastapi.md
@@ -47,7 +47,7 @@ You can:
* Follow me on **GitHub**.
* See other Open Source projects I have created that could help you.
* Follow me to see when I create a new Open Source project.
-* Follow me on **Twitter**.
+* Follow me on **Twitter** or Mastodon.
* Tell me how you use FastAPI (I love to hear that).
* Hear when I make announcements or release new tools.
* You can also follow @fastapi on Twitter (a separate account).
@@ -67,13 +67,54 @@ I love to hear about how **FastAPI** is being used, what you have liked in it, i
* Vote for **FastAPI** in Slant.
* Vote for **FastAPI** in AlternativeTo.
+* Say you use **FastAPI** on StackShare.
## Help others with issues in GitHub
-You can see existing issues and try and help others, most of the times they are questions that you might already know the answer for. 🤓
+You can see existing issues and try and help others, most of the times those issues are questions that you might already know the answer for. 🤓
If you are helping a lot of people with issues, you might become an official [FastAPI Expert](fastapi-people.md#experts){.internal-link target=_blank}. 🎉
+Just remember, the most important point is: try to be kind. People come with their frustrations and in many cases don't ask in the best way, but try as best as you can to be kind. 🤗
+
+The idea is for the **FastAPI** community to be kind and welcoming. At the same time, don't accept bullying or disrespectful behavior towards others. We have to take care of each other.
+
+---
+
+Here's how to help others with issues:
+
+### Understand the question
+
+* Check if you can understand what is the **purpose** and use case of the person asking.
+
+* Then check if the question (the vast majority are questions) is **clear**.
+
+* In many cases the question asked is about an imaginary solution from the user, but there might be a **better** one. If you can understand the problem and use case better, you might be able to suggest a better **alternative solution**.
+
+* If you can't understand the question, ask for more **details**.
+
+### Reproduce the problem
+
+For most of the cases and most of the questions there's something related to the person's **original code**.
+
+In many cases they will only copy a fragment of the code, but that's not enough to **reproduce the problem**.
+
+* You can ask them to provide a minimal, reproducible, example, that you can **copy-paste** and run locally to see the same error or behavior they are seeing, or to understand their use case better.
+
+* If you are feeling too generous, you can try to **create an example** like that yourself, just based on the description of the problem. Just have in mind that this might take a lot of time and it might be better to ask them to clarify the problem first.
+
+### Suggest solutions
+
+* After being able to understand the question, you can give them a possible **answer**.
+
+* In many cases, it's better to understand their **underlying problem or use case**, because there might be a better way to solve it than what they are trying to do.
+
+### Ask to close
+
+If they reply, there's a high chance you would have solved their problem, congrats, **you're a hero**! 🦸
+
+* Now you can ask them, if that solved their problem, to **close the issue**.
+
## Watch the GitHub repository
You can "watch" FastAPI in GitHub (clicking the "watch" button at the top right): https://github.com/tiangolo/fastapi. 👀
@@ -91,6 +132,57 @@ You can and other libraries.
* Many extra features (thanks to Starlette) as:
* **WebSockets**
- * extremely easy tests based on `requests` and `pytest`
+ * extremely easy tests based on HTTPX and `pytest`
* **CORS**
* **Cookie Sessions**
* ...and more.
@@ -444,7 +444,7 @@ Used by Pydantic:
Used by Starlette:
-* requests
- Required if you want to use the `TestClient`.
+* httpx
- Required if you want to use the `TestClient`.
* jinja2
- Required if you want to use the default template configuration.
* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
* itsdangerous
- Required for `SessionMiddleware` support.
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 95e466ea9..4252d68c5 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -2,6 +2,68 @@
## Latest Changes
+* 🔧 Update sponsors, disable course bundle. PR [#5713](https://github.com/tiangolo/fastapi/pull/5713) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Update typer[all] requirement from <0.7.0,>=0.6.1 to >=0.6.1,<0.8.0. PR [#5639](https://github.com/tiangolo/fastapi/pull/5639) by [@dependabot[bot]](https://github.com/apps/dependabot).
+
+## 0.88.0
+
+### Upgrades
+
+* ⬆ Bump Starlette to version `0.22.0` to fix bad encoding for query parameters in new `TestClient`. PR [#5659](https://github.com/tiangolo/fastapi/pull/5659) by [@azogue](https://github.com/azogue).
+
+### Docs
+
+* ✏️ Fix typo in docs for `docs/en/docs/advanced/middleware.md`. PR [#5376](https://github.com/tiangolo/fastapi/pull/5376) by [@rifatrakib](https://github.com/rifatrakib).
+
+### Translations
+
+* 🌐 Add Portuguese translation for `docs/pt/docs/deployment/docker.md`. PR [#5663](https://github.com/tiangolo/fastapi/pull/5663) by [@ayr-ton](https://github.com/ayr-ton).
+
+### Internal
+
+* 👷 Tweak build-docs to improve CI performance. PR [#5699](https://github.com/tiangolo/fastapi/pull/5699) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#5566](https://github.com/tiangolo/fastapi/pull/5566) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
+* ⬆️ Upgrade Ruff. PR [#5698](https://github.com/tiangolo/fastapi/pull/5698) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Remove pip cache for Smokeshow as it depends on a requirements.txt. PR [#5700](https://github.com/tiangolo/fastapi/pull/5700) by [@tiangolo](https://github.com/tiangolo).
+* 💚 Fix pip cache for Smokeshow. PR [#5697](https://github.com/tiangolo/fastapi/pull/5697) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Fix and tweak CI cache handling. PR [#5696](https://github.com/tiangolo/fastapi/pull/5696) by [@tiangolo](https://github.com/tiangolo).
+* 👷 Update `setup-python` action in tests to use new caching feature. PR [#5680](https://github.com/tiangolo/fastapi/pull/5680) by [@madkinsz](https://github.com/madkinsz).
+* ⬆ Bump black from 22.8.0 to 22.10.0. PR [#5569](https://github.com/tiangolo/fastapi/pull/5569) by [@dependabot[bot]](https://github.com/apps/dependabot).
+
+## 0.87.0
+
+Highlights of this release:
+
+* [Upgraded Starlette](https://github.com/encode/starlette/releases/tag/0.21.0)
+ * Now the `TestClient` is based on HTTPX instead of Requests. 🚀
+ * There are some possible **breaking changes** in the `TestClient` usage, but [@Kludex](https://github.com/Kludex) built [bump-testclient](https://github.com/Kludex/bump-testclient) to help you automatize migrating your tests. Make sure you are using Git and that you can undo any unnecessary changes (false positive changes, etc) before using `bump-testclient`.
+* New [WebSocketException (and docs)](https://fastapi.tiangolo.com/advanced/websockets/#using-depends-and-others), re-exported from Starlette.
+* Upgraded and relaxed dependencies for package extras `all` (including new Uvicorn version), when you install `"fastapi[all]"`.
+* New docs about how to [**Help Maintain FastAPI**](https://fastapi.tiangolo.com/help-fastapi/#help-maintain-fastapi).
+
+### Features
+
+* ⬆️ Upgrade and relax dependencies for extras "all". PR [#5634](https://github.com/tiangolo/fastapi/pull/5634) by [@tiangolo](https://github.com/tiangolo).
+* ✨ Re-export Starlette's `WebSocketException` and add it to docs. PR [#5629](https://github.com/tiangolo/fastapi/pull/5629) by [@tiangolo](https://github.com/tiangolo).
+* 📝 Update references to Requests for tests to HTTPX, and add HTTPX to extras. PR [#5628](https://github.com/tiangolo/fastapi/pull/5628) by [@tiangolo](https://github.com/tiangolo).
+* ⬆ Upgrade Starlette to `0.21.0`, including the new [`TestClient` based on HTTPX](https://github.com/encode/starlette/releases/tag/0.21.0). PR [#5471](https://github.com/tiangolo/fastapi/pull/5471) by [@pawelrubin](https://github.com/pawelrubin).
+
+### Docs
+
+* ✏️ Tweak Help FastAPI from PR review after merging. PR [#5633](https://github.com/tiangolo/fastapi/pull/5633) by [@tiangolo](https://github.com/tiangolo).
+* ✏️ Clarify docs on CORS. PR [#5627](https://github.com/tiangolo/fastapi/pull/5627) by [@paxcodes](https://github.com/paxcodes).
+* 📝 Update Help FastAPI: Help Maintain FastAPI. PR [#5632](https://github.com/tiangolo/fastapi/pull/5632) by [@tiangolo](https://github.com/tiangolo).
+
+### Translations
+
+* 🌐 Fix highlight lines for Japanese translation for `docs/tutorial/query-params.md`. PR [#2969](https://github.com/tiangolo/fastapi/pull/2969) by [@ftnext](https://github.com/ftnext).
+* 🌐 Add French translation for `docs/fr/docs/advanced/additional-status-code.md`. PR [#5477](https://github.com/tiangolo/fastapi/pull/5477) by [@axel584](https://github.com/axel584).
+* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/request-forms-and-files.md`. PR [#5579](https://github.com/tiangolo/fastapi/pull/5579) by [@batlopes](https://github.com/batlopes).
+* 🌐 Add Japanese translation for `docs/ja/docs/advanced/websockets.md`. PR [#4983](https://github.com/tiangolo/fastapi/pull/4983) by [@xryuseix](https://github.com/xryuseix).
+
+### Internal
+
+* ✨ Use Ruff for linting. PR [#5630](https://github.com/tiangolo/fastapi/pull/5630) by [@tiangolo](https://github.com/tiangolo).
* 🛠 Add Arabic issue number to Notify Translations GitHub Action. PR [#5610](https://github.com/tiangolo/fastapi/pull/5610) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump dawidd6/action-download-artifact from 2.24.1 to 2.24.2. PR [#5609](https://github.com/tiangolo/fastapi/pull/5609) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump dawidd6/action-download-artifact from 2.24.0 to 2.24.1. PR [#5603](https://github.com/tiangolo/fastapi/pull/5603) by [@dependabot[bot]](https://github.com/apps/dependabot).
diff --git a/docs/en/docs/tutorial/cors.md b/docs/en/docs/tutorial/cors.md
index 4ab3da3b4..33b11983b 100644
--- a/docs/en/docs/tutorial/cors.md
+++ b/docs/en/docs/tutorial/cors.md
@@ -57,7 +57,7 @@ The following arguments are supported:
* `allow_origins` - A list of origins that should be permitted to make cross-origin requests. E.g. `['https://example.org', 'https://www.example.org']`. You can use `['*']` to allow any origin.
* `allow_origin_regex` - A regex string to match against origins that should be permitted to make cross-origin requests. e.g. `'https://.*\.example\.org'`.
* `allow_methods` - A list of HTTP methods that should be allowed for cross-origin requests. Defaults to `['GET']`. You can use `['*']` to allow all standard methods.
-* `allow_headers` - A list of HTTP request headers that should be supported for cross-origin requests. Defaults to `[]`. You can use `['*']` to allow all headers. The `Accept`, `Accept-Language`, `Content-Language` and `Content-Type` headers are always allowed for CORS requests.
+* `allow_headers` - A list of HTTP request headers that should be supported for cross-origin requests. Defaults to `[]`. You can use `['*']` to allow all headers. The `Accept`, `Accept-Language`, `Content-Language` and `Content-Type` headers are always allowed for simple CORS requests.
* `allow_credentials` - Indicate that cookies should be supported for cross-origin requests. Defaults to `False`. Also, `allow_origins` cannot be set to `['*']` for credentials to be allowed, origins must be specified.
* `expose_headers` - Indicate any response headers that should be made accessible to the browser. Defaults to `[]`.
* `max_age` - Sets a maximum time in seconds for browsers to cache CORS responses. Defaults to `600`.
diff --git a/docs/en/docs/tutorial/testing.md b/docs/en/docs/tutorial/testing.md
index d2ccd7dc7..be07aab37 100644
--- a/docs/en/docs/tutorial/testing.md
+++ b/docs/en/docs/tutorial/testing.md
@@ -2,16 +2,16 @@
Thanks to Starlette, testing **FastAPI** applications is easy and enjoyable.
-It is based on Requests, so it's very familiar and intuitive.
+It is based on HTTPX, which in turn is designed based on Requests, so it's very familiar and intuitive.
With it, you can use pytest directly with **FastAPI**.
## Using `TestClient`
!!! info
- To use `TestClient`, first install `requests`.
+ To use `TestClient`, first install `httpx`.
- E.g. `pip install requests`.
+ E.g. `pip install httpx`.
Import `TestClient`.
@@ -19,7 +19,7 @@ Create a `TestClient` by passing your **FastAPI** application to it.
Create functions with a name that starts with `test_` (this is standard `pytest` conventions).
-Use the `TestClient` object the same way as you do with `requests`.
+Use the `TestClient` object the same way as you do with `httpx`.
Write simple `assert` statements with the standard Python expressions that you need to check (again, standard `pytest`).
@@ -130,7 +130,7 @@ You could then update `test_main.py` with the extended tests:
{!> ../../../docs_src/app_testing/app_b/test_main.py!}
```
-Whenever you need the client to pass information in the request and you don't know how to, you can search (Google) how to do it in `requests`.
+Whenever you need the client to pass information in the request and you don't know how to, you can search (Google) how to do it in `httpx`, or even how to do it with `requests`, as HTTPX's design is based on Requests' design.
Then you just do the same in your tests.
@@ -142,7 +142,7 @@ E.g.:
* To pass *headers*, use a `dict` in the `headers` parameter.
* For *cookies*, a `dict` in the `cookies` parameter.
-For more information about how to pass data to the backend (using `requests` or the `TestClient`) check the Requests documentation.
+For more information about how to pass data to the backend (using `httpx` or the `TestClient`) check the HTTPX documentation.
!!! info
Note that the `TestClient` receives data that can be converted to JSON, not Pydantic models.
diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html
index c5eb94870..e9b9f60eb 100644
--- a/docs/en/overrides/main.html
+++ b/docs/en/overrides/main.html
@@ -22,12 +22,6 @@
requests
- Requerido si quieres usar el `TestClient`.
+* httpx
- Requerido si quieres usar el `TestClient`.
* jinja2
- Requerido si quieres usar la configuración por defecto de templates.
* python-multipart
- Requerido si quieres dar soporte a "parsing" de formularios, con `request.form()`.
* itsdangerous
- Requerido para dar soporte a `SessionMiddleware`.
diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md
index 0f7cd569a..dfc4d24e3 100644
--- a/docs/fa/docs/index.md
+++ b/docs/fa/docs/index.md
@@ -421,7 +421,7 @@ item: Item
* قابلیتهای اضافی دیگر (بر اساس Starlette) شامل:
* **وبسوکت**
* **GraphQL**
- * تستهای خودکار آسان مبتنی بر `requests` و `pytest`
+ * تستهای خودکار آسان مبتنی بر HTTPX و `pytest`
* **CORS**
* **Cookie Sessions**
* و موارد بیشمار دیگر.
@@ -441,7 +441,7 @@ item: Item
استفاده شده توسط Starlette:
-* requests
- در صورتی که میخواهید از `TestClient` استفاده کنید.
+* HTTPX
- در صورتی که میخواهید از `TestClient` استفاده کنید.
* aiofiles
- در صورتی که میخواهید از `FileResponse` و `StaticFiles` استفاده کنید.
* jinja2
- در صورتی که بخواهید از پیکربندی پیشفرض برای قالبها استفاده کنید.
* python-multipart
- در صورتی که بخواهید با استفاده از `request.form()` از قابلیت "تجزیه (parse)" فرم استفاده کنید.
diff --git a/docs/fr/docs/advanced/additional-responses.md b/docs/fr/docs/advanced/additional-responses.md
new file mode 100644
index 000000000..35b57594d
--- /dev/null
+++ b/docs/fr/docs/advanced/additional-responses.md
@@ -0,0 +1,240 @@
+# Réponses supplémentaires dans OpenAPI
+
+!!! Attention
+ Ceci concerne un sujet plutôt avancé.
+
+ Si vous débutez avec **FastAPI**, vous n'en aurez peut-être pas besoin.
+
+Vous pouvez déclarer des réponses supplémentaires, avec des codes HTTP, des types de médias, des descriptions, etc.
+
+Ces réponses supplémentaires seront incluses dans le schéma OpenAPI, elles apparaîtront donc également dans la documentation de l'API.
+
+Mais pour ces réponses supplémentaires, vous devez vous assurer de renvoyer directement une `Response` comme `JSONResponse`, avec votre code HTTP et votre contenu.
+
+## Réponse supplémentaire avec `model`
+
+Vous pouvez ajouter à votre décorateur de *paramètre de chemin* un paramètre `responses`.
+
+Il prend comme valeur un `dict` dont les clés sont des codes HTTP pour chaque réponse, comme `200`, et la valeur de ces clés sont d'autres `dict` avec des informations pour chacun d'eux.
+
+Chacun de ces `dict` de réponse peut avoir une clé `model`, contenant un modèle Pydantic, tout comme `response_model`.
+
+**FastAPI** prendra ce modèle, générera son schéma JSON et l'inclura au bon endroit dans OpenAPI.
+
+Par exemple, pour déclarer une autre réponse avec un code HTTP `404` et un modèle Pydantic `Message`, vous pouvez écrire :
+
+```Python hl_lines="18 22"
+{!../../../docs_src/additional_responses/tutorial001.py!}
+```
+
+!!! Remarque
+ Gardez à l'esprit que vous devez renvoyer directement `JSONResponse`.
+
+!!! Info
+ La clé `model` ne fait pas partie d'OpenAPI.
+
+ **FastAPI** prendra le modèle Pydantic à partir de là, générera le `JSON Schema` et le placera au bon endroit.
+
+ Le bon endroit est :
+
+ * Dans la clé `content`, qui a pour valeur un autre objet JSON (`dict`) qui contient :
+ * Une clé avec le type de support, par ex. `application/json`, qui contient comme valeur un autre objet JSON, qui contient :
+ * Une clé `schema`, qui a pour valeur le schéma JSON du modèle, voici le bon endroit.
+ * **FastAPI** ajoute ici une référence aux schémas JSON globaux à un autre endroit de votre OpenAPI au lieu de l'inclure directement. De cette façon, d'autres applications et clients peuvent utiliser ces schémas JSON directement, fournir de meilleurs outils de génération de code, etc.
+
+Les réponses générées au format OpenAPI pour cette *opération de chemin* seront :
+
+```JSON hl_lines="3-12"
+{
+ "responses": {
+ "404": {
+ "description": "Additional Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Message"
+ }
+ }
+ }
+ },
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Item"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+Les schémas sont référencés à un autre endroit du modèle OpenAPI :
+
+```JSON hl_lines="4-16"
+{
+ "components": {
+ "schemas": {
+ "Message": {
+ "title": "Message",
+ "required": [
+ "message"
+ ],
+ "type": "object",
+ "properties": {
+ "message": {
+ "title": "Message",
+ "type": "string"
+ }
+ }
+ },
+ "Item": {
+ "title": "Item",
+ "required": [
+ "id",
+ "value"
+ ],
+ "type": "object",
+ "properties": {
+ "id": {
+ "title": "Id",
+ "type": "string"
+ },
+ "value": {
+ "title": "Value",
+ "type": "string"
+ }
+ }
+ },
+ "ValidationError": {
+ "title": "ValidationError",
+ "required": [
+ "loc",
+ "msg",
+ "type"
+ ],
+ "type": "object",
+ "properties": {
+ "loc": {
+ "title": "Location",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string"
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string"
+ }
+ }
+ },
+ "HTTPValidationError": {
+ "title": "HTTPValidationError",
+ "type": "object",
+ "properties": {
+ "detail": {
+ "title": "Detail",
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+## Types de médias supplémentaires pour la réponse principale
+
+Vous pouvez utiliser ce même paramètre `responses` pour ajouter différents types de médias pour la même réponse principale.
+
+Par exemple, vous pouvez ajouter un type de média supplémentaire `image/png`, en déclarant que votre *opération de chemin* peut renvoyer un objet JSON (avec le type de média `application/json`) ou une image PNG :
+
+```Python hl_lines="19-24 28"
+{!../../../docs_src/additional_responses/tutorial002.py!}
+```
+
+!!! Remarque
+ Notez que vous devez retourner l'image en utilisant directement un `FileResponse`.
+
+!!! Info
+ À moins que vous ne spécifiiez explicitement un type de média différent dans votre paramètre `responses`, FastAPI supposera que la réponse a le même type de média que la classe de réponse principale (par défaut `application/json`).
+
+ Mais si vous avez spécifié une classe de réponse personnalisée avec `None` comme type de média, FastAPI utilisera `application/json` pour toute réponse supplémentaire associée à un modèle.
+
+## Combinaison d'informations
+
+Vous pouvez également combiner des informations de réponse provenant de plusieurs endroits, y compris les paramètres `response_model`, `status_code` et `responses`.
+
+Vous pouvez déclarer un `response_model`, en utilisant le code HTTP par défaut `200` (ou un code personnalisé si vous en avez besoin), puis déclarer des informations supplémentaires pour cette même réponse dans `responses`, directement dans le schéma OpenAPI.
+
+**FastAPI** conservera les informations supplémentaires des `responses` et les combinera avec le schéma JSON de votre modèle.
+
+Par exemple, vous pouvez déclarer une réponse avec un code HTTP `404` qui utilise un modèle Pydantic et a une `description` personnalisée.
+
+Et une réponse avec un code HTTP `200` qui utilise votre `response_model`, mais inclut un `example` personnalisé :
+
+```Python hl_lines="20-31"
+{!../../../docs_src/additional_responses/tutorial003.py!}
+```
+
+Tout sera combiné et inclus dans votre OpenAPI, et affiché dans la documentation de l'API :
+
+requests
- Required if you want to use the `TestClient`.
+* HTTPX
- Required if you want to use the `TestClient`.
* jinja2
- Required if you want to use the default template configuration.
* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
* itsdangerous
- Required for `SessionMiddleware` support.
diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml
index 1c4f45682..7dce4b127 100644
--- a/docs/fr/mkdocs.yml
+++ b/docs/fr/mkdocs.yml
@@ -67,6 +67,9 @@ nav:
- tutorial/query-params.md
- tutorial/body.md
- tutorial/background-tasks.md
+- Guide utilisateur avancé:
+ - advanced/additional-status-codes.md
+ - advanced/additional-responses.md
- async.md
- Déploiement:
- deployment/index.md
diff --git a/docs/he/docs/index.md b/docs/he/docs/index.md
index fa63d8cb7..19f2f2041 100644
--- a/docs/he/docs/index.md
+++ b/docs/he/docs/index.md
@@ -445,7 +445,7 @@ item: Item
בשימוש Starlette:
-- requests
- דרוש אם ברצונכם להשתמש ב - `TestClient`.
+- httpx
- דרוש אם ברצונכם להשתמש ב - `TestClient`.
- jinja2
- דרוש אם ברצונכם להשתמש בברירת המחדל של תצורת הטמפלייטים.
- python-multipart
- דרוש אם ברצונכם לתמוך ב "פרסור" טפסים, באצמעות request.form()
.
- itsdangerous
- דרוש אם ברצונכם להשתמש ב - `SessionMiddleware`.
diff --git a/docs/id/docs/index.md b/docs/id/docs/index.md
index 3129f9dc6..66fc2859e 100644
--- a/docs/id/docs/index.md
+++ b/docs/id/docs/index.md
@@ -426,7 +426,7 @@ For a more complete example including more features, see the requests
- Required if you want to use the `TestClient`.
+* httpx
- Required if you want to use the `TestClient`.
* jinja2
- Required if you want to use the default template configuration.
* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
* itsdangerous
- Required for `SessionMiddleware` support.
diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md
index 852a5e56e..9d95dd6d7 100644
--- a/docs/it/docs/index.md
+++ b/docs/it/docs/index.md
@@ -423,7 +423,7 @@ For a more complete example including more features, see the requests
- Required if you want to use the `TestClient`.
+* httpx
- Required if you want to use the `TestClient`.
* jinja2
- Required if you want to use the default template configuration.
* python-multipart
- Required if you want to support form "parsing", with `request.form()`.
* itsdangerous
- Required for `SessionMiddleware` support.
diff --git a/docs/ja/docs/advanced/websockets.md b/docs/ja/docs/advanced/websockets.md
new file mode 100644
index 000000000..65e4112a6
--- /dev/null
+++ b/docs/ja/docs/advanced/websockets.md
@@ -0,0 +1,186 @@
+# WebSocket
+
+**FastAPI**でWebSocketが使用できます。
+
+## `WebSockets`のインストール
+
+まず `WebSockets`のインストールが必要です。
+
+requests
- 使用 `TestClient` 时安装。
+* httpx
- 使用 `TestClient` 时安装。
* jinja2
- 使用默认模板配置时安装。
* python-multipart
- 需要通过 `request.form()` 对表单进行「解析」时安装。
* itsdangerous
- 需要 `SessionMiddleware` 支持时安装。
diff --git a/docs_src/security/tutorial005.py b/docs_src/security/tutorial005.py
index ab3af9a6a..bd0a33581 100644
--- a/docs_src/security/tutorial005.py
+++ b/docs_src/security/tutorial005.py
@@ -107,7 +107,7 @@ async def get_current_user(
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
- authenticate_value = f"Bearer"
+ authenticate_value = "Bearer"
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py
index c6a095d2c..ba756ef4f 100644
--- a/docs_src/security/tutorial005_py310.py
+++ b/docs_src/security/tutorial005_py310.py
@@ -106,7 +106,7 @@ async def get_current_user(
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
- authenticate_value = f"Bearer"
+ authenticate_value = "Bearer"
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py
index 38391308a..9e4dbcffb 100644
--- a/docs_src/security/tutorial005_py39.py
+++ b/docs_src/security/tutorial005_py39.py
@@ -107,7 +107,7 @@ async def get_current_user(
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
- authenticate_value = f"Bearer"
+ authenticate_value = "Bearer"
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
diff --git a/docs_src/websockets/tutorial002.py b/docs_src/websockets/tutorial002.py
index cf5c7e805..cab749e4d 100644
--- a/docs_src/websockets/tutorial002.py
+++ b/docs_src/websockets/tutorial002.py
@@ -1,6 +1,14 @@
from typing import Union
-from fastapi import Cookie, Depends, FastAPI, Query, WebSocket, status
+from fastapi import (
+ Cookie,
+ Depends,
+ FastAPI,
+ Query,
+ WebSocket,
+ WebSocketException,
+ status,
+)
from fastapi.responses import HTMLResponse
app = FastAPI()
@@ -61,7 +69,7 @@ async def get_cookie_or_token(
token: Union[str, None] = Query(default=None),
):
if session is None and token is None:
- await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
+ raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
return session or token
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index a5c7aeb17..037d9804b 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.86.0"
+__version__ = "0.88.0"
from starlette import status as status
@@ -8,6 +8,7 @@ from .applications import FastAPI as FastAPI
from .background import BackgroundTasks as BackgroundTasks
from .datastructures import UploadFile as UploadFile
from .exceptions import HTTPException as HTTPException
+from .exceptions import WebSocketException as WebSocketException
from .param_functions import Body as Body
from .param_functions import Cookie as Cookie
from .param_functions import Depends as Depends
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 64a6c1276..4c817d5d0 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -105,10 +105,10 @@ def check_file_field(field: ModelField) -> None:
assert parse_options_header
except ImportError:
logger.error(multipart_incorrect_install_error)
- raise RuntimeError(multipart_incorrect_install_error)
+ raise RuntimeError(multipart_incorrect_install_error) from None
except ImportError:
logger.error(multipart_not_installed_error)
- raise RuntimeError(multipart_not_installed_error)
+ raise RuntimeError(multipart_not_installed_error) from None
def get_param_sub_dependant(
@@ -426,21 +426,21 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool:
return inspect.iscoroutinefunction(call)
if inspect.isclass(call):
return False
- dunder_call = getattr(call, "__call__", None)
+ dunder_call = getattr(call, "__call__", None) # noqa: B004
return inspect.iscoroutinefunction(dunder_call)
def is_async_gen_callable(call: Callable[..., Any]) -> bool:
if inspect.isasyncgenfunction(call):
return True
- dunder_call = getattr(call, "__call__", None)
+ dunder_call = getattr(call, "__call__", None) # noqa: B004
return inspect.isasyncgenfunction(dunder_call)
def is_gen_callable(call: Callable[..., Any]) -> bool:
if inspect.isgeneratorfunction(call):
return True
- dunder_call = getattr(call, "__call__", None)
+ dunder_call = getattr(call, "__call__", None) # noqa: B004
return inspect.isgeneratorfunction(dunder_call)
@@ -724,14 +724,14 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
# in case a sub-dependency is evaluated with a single unique body field
# That is combined (embedded) with other body fields
for param in flat_dependant.body_params:
- setattr(param.field_info, "embed", True)
+ setattr(param.field_info, "embed", True) # noqa: B010
model_name = "Body_" + name
BodyModel: Type[BaseModel] = create_model(model_name)
for f in flat_dependant.body_params:
BodyModel.__fields__[f.name] = f
required = any(True for f in flat_dependant.body_params if f.required)
- BodyFieldInfo_kwargs: Dict[str, Any] = dict(default=None)
+ BodyFieldInfo_kwargs: Dict[str, Any] = {"default": None}
if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params):
BodyFieldInfo: Type[params.Body] = params.File
elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params):
@@ -740,7 +740,7 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
BodyFieldInfo = params.Body
body_param_media_types = [
- getattr(f.field_info, "media_type")
+ f.field_info.media_type
for f in flat_dependant.body_params
if isinstance(f.field_info, params.Body)
]
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index 6bde9f4ab..2f95bcbf6 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -157,7 +157,7 @@ def jsonable_encoder(
data = vars(obj)
except Exception as e:
errors.append(e)
- raise ValueError(errors)
+ raise ValueError(errors) from e
return jsonable_encoder(
data,
include=include,
diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py
index 0f50acc6c..ca097b1ce 100644
--- a/fastapi/exceptions.py
+++ b/fastapi/exceptions.py
@@ -3,6 +3,7 @@ from typing import Any, Dict, Optional, Sequence, Type
from pydantic import BaseModel, ValidationError, create_model
from pydantic.error_wrappers import ErrorList
from starlette.exceptions import HTTPException as StarletteHTTPException
+from starlette.exceptions import WebSocketException as WebSocketException # noqa: F401
class HTTPException(StarletteHTTPException):
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 8c0bec5e6..9a7d88efc 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -701,7 +701,7 @@ class APIRouter(routing.Router):
), "A path prefix must not end with '/', as the routes will start with '/'"
else:
for r in router.routes:
- path = getattr(r, "path")
+ path = getattr(r, "path") # noqa: B009
name = getattr(r, "name", "unknown")
if path is not None and not path:
raise Exception(
diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py
index bca5c721a..24ddbf482 100644
--- a/fastapi/security/api_key.py
+++ b/fastapi/security/api_key.py
@@ -54,7 +54,7 @@ class APIKeyHeader(APIKeyBase):
self.auto_error = auto_error
async def __call__(self, request: Request) -> Optional[str]:
- api_key: str = request.headers.get(self.model.name)
+ api_key = request.headers.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(
diff --git a/fastapi/security/http.py b/fastapi/security/http.py
index 1b473c69e..8b677299d 100644
--- a/fastapi/security/http.py
+++ b/fastapi/security/http.py
@@ -38,7 +38,7 @@ class HTTPBase(SecurityBase):
async def __call__(
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials):
if self.auto_error:
@@ -67,7 +67,7 @@ class HTTPBasic(HTTPBase):
async def __call__( # type: ignore
self, request: Request
) -> Optional[HTTPBasicCredentials]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if self.realm:
unauthorized_headers = {"WWW-Authenticate": f'Basic realm="{self.realm}"'}
@@ -113,7 +113,7 @@ class HTTPBearer(HTTPBase):
async def __call__(
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials):
if self.auto_error:
@@ -148,7 +148,7 @@ class HTTPDigest(HTTPBase):
async def __call__(
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials):
if self.auto_error:
diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py
index 653c3010e..eb6b4277c 100644
--- a/fastapi/security/oauth2.py
+++ b/fastapi/security/oauth2.py
@@ -126,7 +126,7 @@ class OAuth2(SecurityBase):
self.auto_error = auto_error
async def __call__(self, request: Request) -> Optional[str]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
if not authorization:
if self.auto_error:
raise HTTPException(
@@ -157,7 +157,7 @@ class OAuth2PasswordBearer(OAuth2):
)
async def __call__(self, request: Request) -> Optional[str]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
@@ -200,7 +200,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
)
async def __call__(self, request: Request) -> Optional[str]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
diff --git a/fastapi/security/open_id_connect_url.py b/fastapi/security/open_id_connect_url.py
index dfe9f7b25..393614f7c 100644
--- a/fastapi/security/open_id_connect_url.py
+++ b/fastapi/security/open_id_connect_url.py
@@ -23,7 +23,7 @@ class OpenIdConnect(SecurityBase):
self.auto_error = auto_error
async def __call__(self, request: Request) -> Optional[str]:
- authorization: str = request.headers.get("Authorization")
+ authorization = request.headers.get("Authorization")
if not authorization:
if self.auto_error:
raise HTTPException(
diff --git a/fastapi/security/utils.py b/fastapi/security/utils.py
index 2da0dd20f..fa7a450b7 100644
--- a/fastapi/security/utils.py
+++ b/fastapi/security/utils.py
@@ -1,7 +1,9 @@
-from typing import Tuple
+from typing import Optional, Tuple
-def get_authorization_scheme_param(authorization_header_value: str) -> Tuple[str, str]:
+def get_authorization_scheme_param(
+ authorization_header_value: Optional[str],
+) -> Tuple[str, str]:
if not authorization_header_value:
return "", ""
scheme, _, param = authorization_header_value.partition(" ")
diff --git a/fastapi/utils.py b/fastapi/utils.py
index b94dacecc..b15f6a2cf 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -89,7 +89,7 @@ def create_response_field(
except RuntimeError:
raise fastapi.exceptions.FastAPIError(
f"Invalid args for response field! Hint: check that {type_} is a valid pydantic field type"
- )
+ ) from None
def create_cloned_field(
diff --git a/pyproject.toml b/pyproject.toml
index a23289fb1..be3080ae8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,7 +39,7 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
- "starlette==0.20.4",
+ "starlette==0.22.0",
"pydantic >=1.6.2,!=1.7,!=1.7.1,!=1.7.2,!=1.7.3,!=1.8,!=1.8.1,<2.0.0",
]
dynamic = ["version"]
@@ -53,10 +53,9 @@ test = [
"pytest >=7.1.3,<8.0.0",
"coverage[toml] >= 6.5.0,<7.0",
"mypy ==0.982",
- "flake8 >=3.8.3,<6.0.0",
- "black == 22.8.0",
+ "ruff ==0.0.138",
+ "black == 22.10.0",
"isort >=5.0.6,<6.0.0",
- "requests >=2.24.0,<3.0.0",
"httpx >=0.23.0,<0.24.0",
"email_validator >=1.1.1,<2.0.0",
# TODO: once removing databases from tutorial, upgrade SQLAlchemy
@@ -84,25 +83,24 @@ doc = [
"mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0",
# TODO: upgrade and enable typer-cli once it supports Click 8.x.x
# "typer-cli >=0.0.12,<0.0.13",
- "typer[all] >=0.6.1,<0.7.0",
+ "typer[all] >=0.6.1,<0.8.0",
"pyyaml >=5.3.1,<7.0.0",
]
dev = [
- "autoflake >=1.4.0,<2.0.0",
- "flake8 >=3.8.3,<6.0.0",
+ "ruff ==0.0.138",
"uvicorn[standard] >=0.12.0,<0.19.0",
"pre-commit >=2.17.0,<3.0.0",
]
all = [
- "requests >=2.24.0,<3.0.0",
- "jinja2 >=2.11.2,<4.0.0",
- "python-multipart >=0.0.5,<0.0.6",
- "itsdangerous >=1.1.0,<3.0.0",
- "pyyaml >=5.3.1,<7.0.0",
- "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0",
- "orjson >=3.2.1,<4.0.0",
- "email_validator >=1.1.1,<2.0.0",
- "uvicorn[standard] >=0.12.0,<0.19.0",
+ "httpx >=0.23.0",
+ "jinja2 >=2.11.2",
+ "python-multipart >=0.0.5",
+ "itsdangerous >=1.1.0",
+ "pyyaml >=5.3.1",
+ "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0",
+ "orjson >=3.2.1",
+ "email_validator >=1.1.1",
+ "uvicorn[standard] >=0.12.0",
]
[tool.hatch.version]
@@ -157,3 +155,32 @@ source = [
"fastapi"
]
context = '${CONTEXT}'
+
+[tool.ruff]
+select = [
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # pyflakes
+ # "I", # isort
+ "C", # flake8-comprehensions
+ "B", # flake8-bugbear
+]
+ignore = [
+ "E501", # line too long, handled by black
+ "B008", # do not perform function calls in argument defaults
+ "C901", # too complex
+]
+
+[tool.ruff.per-file-ignores]
+"__init__.py" = ["F401"]
+"docs_src/dependencies/tutorial007.py" = ["F821"]
+"docs_src/dependencies/tutorial008.py" = ["F821"]
+"docs_src/dependencies/tutorial009.py" = ["F821"]
+"docs_src/dependencies/tutorial010.py" = ["F821"]
+"docs_src/custom_response/tutorial007.py" = ["B007"]
+"docs_src/dataclasses/tutorial003.py" = ["I001"]
+"docs_src/path_operation_advanced_configuration/tutorial007.py" = ["B904"]
+"docs_src/custom_request_and_route/tutorial002.py" = ["B904"]
+
+[tool.ruff.isort]
+known-third-party = ["fastapi", "pydantic", "starlette"]
diff --git a/scripts/docs.py b/scripts/docs.py
index d5fbacf59..e0953b8ed 100644
--- a/scripts/docs.py
+++ b/scripts/docs.py
@@ -284,7 +284,9 @@ def build_all():
continue
langs.append(lang.name)
cpu_count = os.cpu_count() or 1
- with Pool(cpu_count * 2) as p:
+ process_pool_size = cpu_count * 4
+ typer.echo(f"Using process pool size: {process_pool_size}")
+ with Pool(process_pool_size) as p:
p.map(build_lang, langs)
@@ -332,7 +334,7 @@ def serve():
os.chdir("site")
server_address = ("", 8008)
server = HTTPServer(server_address, SimpleHTTPRequestHandler)
- typer.echo(f"Serving at: http://127.0.0.1:8008")
+ typer.echo("Serving at: http://127.0.0.1:8008")
server.serve_forever()
@@ -420,7 +422,7 @@ def get_file_to_nav_map(nav: list) -> Dict[str, Tuple[str, ...]]:
file_to_nav = {}
for item in nav:
if type(item) is str:
- file_to_nav[item] = tuple()
+ file_to_nav[item] = ()
elif type(item) is dict:
item_key = list(item.keys())[0]
sub_nav = item[item_key]
diff --git a/scripts/format.sh b/scripts/format.sh
index ee4fbf1a5..3ac1fead8 100755
--- a/scripts/format.sh
+++ b/scripts/format.sh
@@ -1,6 +1,6 @@
#!/bin/sh -e
set -x
-autoflake --remove-all-unused-imports --recursive --remove-unused-variables --in-place docs_src fastapi tests scripts --exclude=__init__.py
+ruff fastapi tests docs_src scripts --fix
black fastapi tests docs_src scripts
isort fastapi tests docs_src scripts
diff --git a/scripts/lint.sh b/scripts/lint.sh
index 2e2072cf1..0feb973a8 100755
--- a/scripts/lint.sh
+++ b/scripts/lint.sh
@@ -4,6 +4,6 @@ set -e
set -x
mypy fastapi
-flake8 fastapi tests
+ruff fastapi tests docs_src scripts
black fastapi tests --check
isort fastapi tests docs_src scripts --check-only
diff --git a/tests/test_custom_route_class.py b/tests/test_custom_route_class.py
index 1a9ea7199..2e8d9c6de 100644
--- a/tests/test_custom_route_class.py
+++ b/tests/test_custom_route_class.py
@@ -110,6 +110,6 @@ def test_route_classes():
for r in app.router.routes:
assert isinstance(r, Route)
routes[r.path] = r
- assert getattr(routes["/a/"], "x_type") == "A"
- assert getattr(routes["/a/b/"], "x_type") == "B"
- assert getattr(routes["/a/b/c/"], "x_type") == "C"
+ assert getattr(routes["/a/"], "x_type") == "A" # noqa: B009
+ assert getattr(routes["/a/b/"], "x_type") == "B" # noqa: B009
+ assert getattr(routes["/a/b/c/"], "x_type") == "C" # noqa: B009
diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py
index ba8c7353f..bf05aa585 100644
--- a/tests/test_enforce_once_required_parameter.py
+++ b/tests/test_enforce_once_required_parameter.py
@@ -101,7 +101,7 @@ def test_schema():
def test_get_invalid():
- response = client.get("/foo", params={"client_id": None})
+ response = client.get("/foo")
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py
index 491ba61c6..e979628a5 100644
--- a/tests/test_extra_routes.py
+++ b/tests/test_extra_routes.py
@@ -333,7 +333,7 @@ def test_get_api_route_not_decorated():
def test_delete():
- response = client.delete("/items/foo", json={"name": "Foo"})
+ response = client.request("DELETE", "/items/foo", json={"name": "Foo"})
assert response.status_code == 200, response.text
assert response.json() == {"item_id": "foo", "item": {"name": "Foo", "price": None}}
diff --git a/tests/test_get_request_body.py b/tests/test_get_request_body.py
index 88b9d839f..52a052faa 100644
--- a/tests/test_get_request_body.py
+++ b/tests/test_get_request_body.py
@@ -104,5 +104,5 @@ def test_openapi_schema():
def test_get_with_body():
body = {"name": "Foo", "description": "Some description", "price": 5.5}
- response = client.get("/product", json=body)
+ response = client.request("GET", "/product", json=body)
assert response.json() == body
diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py
index 214f039b6..cb182a1cd 100644
--- a/tests/test_param_include_in_schema.py
+++ b/tests/test_param_include_in_schema.py
@@ -33,8 +33,6 @@ async def hidden_query(
return {"hidden_query": hidden_query}
-client = TestClient(app)
-
openapi_shema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
@@ -161,6 +159,7 @@ openapi_shema = {
def test_openapi_schema():
+ client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_shema
@@ -184,7 +183,8 @@ def test_openapi_schema():
],
)
def test_hidden_cookie(path, cookies, expected_status, expected_response):
- response = client.get(path, cookies=cookies)
+ client = TestClient(app, cookies=cookies)
+ response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
@@ -207,12 +207,14 @@ def test_hidden_cookie(path, cookies, expected_status, expected_response):
],
)
def test_hidden_header(path, headers, expected_status, expected_response):
+ client = TestClient(app)
response = client.get(path, headers=headers)
assert response.status_code == expected_status
assert response.json() == expected_response
def test_hidden_path():
+ client = TestClient(app)
response = client.get("/hidden_path/hidden_path")
assert response.status_code == 200
assert response.json() == {"hidden_path": "hidden_path"}
@@ -234,6 +236,7 @@ def test_hidden_path():
],
)
def test_hidden_query(path, expected_status, expected_response):
+ client = TestClient(app)
response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
diff --git a/tests/test_security_api_key_cookie.py b/tests/test_security_api_key_cookie.py
index a5b2e44f0..0bf4e9bb3 100644
--- a/tests/test_security_api_key_cookie.py
+++ b/tests/test_security_api_key_cookie.py
@@ -22,8 +22,6 @@ def read_current_user(current_user: User = Depends(get_current_user)):
return current_user
-client = TestClient(app)
-
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
@@ -51,18 +49,21 @@ openapi_schema = {
def test_openapi_schema():
+ client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_api_key():
- response = client.get("/users/me", cookies={"key": "secret"})
+ client = TestClient(app, cookies={"key": "secret"})
+ response = client.get("/users/me")
assert response.status_code == 200, response.text
assert response.json() == {"username": "secret"}
def test_security_api_key_no_key():
+ client = TestClient(app)
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_security_api_key_cookie_description.py b/tests/test_security_api_key_cookie_description.py
index 2cd3565b4..ed4e65239 100644
--- a/tests/test_security_api_key_cookie_description.py
+++ b/tests/test_security_api_key_cookie_description.py
@@ -22,8 +22,6 @@ def read_current_user(current_user: User = Depends(get_current_user)):
return current_user
-client = TestClient(app)
-
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
@@ -56,18 +54,21 @@ openapi_schema = {
def test_openapi_schema():
+ client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_api_key():
- response = client.get("/users/me", cookies={"key": "secret"})
+ client = TestClient(app, cookies={"key": "secret"})
+ response = client.get("/users/me")
assert response.status_code == 200, response.text
assert response.json() == {"username": "secret"}
def test_security_api_key_no_key():
+ client = TestClient(app)
response = client.get("/users/me")
assert response.status_code == 403, response.text
assert response.json() == {"detail": "Not authenticated"}
diff --git a/tests/test_security_api_key_cookie_optional.py b/tests/test_security_api_key_cookie_optional.py
index 96a64f09a..3e7aa81c0 100644
--- a/tests/test_security_api_key_cookie_optional.py
+++ b/tests/test_security_api_key_cookie_optional.py
@@ -29,8 +29,6 @@ def read_current_user(current_user: User = Depends(get_current_user)):
return current_user
-client = TestClient(app)
-
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
@@ -58,18 +56,21 @@ openapi_schema = {
def test_openapi_schema():
+ client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == openapi_schema
def test_security_api_key():
- response = client.get("/users/me", cookies={"key": "secret"})
+ client = TestClient(app, cookies={"key": "secret"})
+ response = client.get("/users/me")
assert response.status_code == 200, response.text
assert response.json() == {"username": "secret"}
def test_security_api_key_no_key():
+ client = TestClient(app)
response = client.get("/users/me")
assert response.status_code == 200, response.text
assert response.json() == {"msg": "Create an account first"}
diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py
index 289bd5c74..91824d223 100644
--- a/tests/test_security_http_basic_optional.py
+++ b/tests/test_security_http_basic_optional.py
@@ -4,7 +4,6 @@ from typing import Optional
from fastapi import FastAPI, Security
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.testclient import TestClient
-from requests.auth import HTTPBasicAuth
app = FastAPI()
@@ -51,8 +50,7 @@ def test_openapi_schema():
def test_security_http_basic():
- auth = HTTPBasicAuth(username="john", password="secret")
- response = client.get("/users/me", auth=auth)
+ response = client.get("/users/me", auth=("john", "secret"))
assert response.status_code == 200, response.text
assert response.json() == {"username": "john", "password": "secret"}
diff --git a/tests/test_security_http_basic_realm.py b/tests/test_security_http_basic_realm.py
index 54867c2e0..6d760c0f9 100644
--- a/tests/test_security_http_basic_realm.py
+++ b/tests/test_security_http_basic_realm.py
@@ -3,7 +3,6 @@ from base64 import b64encode
from fastapi import FastAPI, Security
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.testclient import TestClient
-from requests.auth import HTTPBasicAuth
app = FastAPI()
@@ -48,8 +47,7 @@ def test_openapi_schema():
def test_security_http_basic():
- auth = HTTPBasicAuth(username="john", password="secret")
- response = client.get("/users/me", auth=auth)
+ response = client.get("/users/me", auth=("john", "secret"))
assert response.status_code == 200, response.text
assert response.json() == {"username": "john", "password": "secret"}
diff --git a/tests/test_security_http_basic_realm_description.py b/tests/test_security_http_basic_realm_description.py
index 6ff9d9d07..7cc547561 100644
--- a/tests/test_security_http_basic_realm_description.py
+++ b/tests/test_security_http_basic_realm_description.py
@@ -3,7 +3,6 @@ from base64 import b64encode
from fastapi import FastAPI, Security
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.testclient import TestClient
-from requests.auth import HTTPBasicAuth
app = FastAPI()
@@ -54,8 +53,7 @@ def test_openapi_schema():
def test_security_http_basic():
- auth = HTTPBasicAuth(username="john", password="secret")
- response = client.get("/users/me", auth=auth)
+ response = client.get("/users/me", auth=("john", "secret"))
assert response.status_code == 200, response.text
assert response.json() == {"username": "john", "password": "secret"}
diff --git a/tests/test_starlette_urlconvertors.py b/tests/test_starlette_urlconvertors.py
index 5a980cbf6..5ef1b819c 100644
--- a/tests/test_starlette_urlconvertors.py
+++ b/tests/test_starlette_urlconvertors.py
@@ -1,4 +1,4 @@
-from fastapi import FastAPI, Path
+from fastapi import FastAPI, Path, Query
from fastapi.testclient import TestClient
app = FastAPI()
@@ -19,6 +19,11 @@ def path_convertor(param: str = Path()):
return {"path": param}
+@app.get("/query/")
+def query_convertor(param: str = Query()):
+ return {"query": param}
+
+
client = TestClient(app)
@@ -45,6 +50,13 @@ def test_route_converters_path():
assert response.json() == {"path": "some/example"}
+def test_route_converters_query():
+ # Test query conversion
+ response = client.get("/query", params={"param": "Qué tal!"})
+ assert response.status_code == 200, response.text
+ assert response.json() == {"query": "Qué tal!"}
+
+
def test_url_path_for_path_convertor():
assert (
app.url_path_for("path_convertor", param="some/example") == "/path/some/example"
diff --git a/tests/test_tuples.py b/tests/test_tuples.py
index 18ec2d048..6e2cc0db6 100644
--- a/tests/test_tuples.py
+++ b/tests/test_tuples.py
@@ -252,16 +252,14 @@ def test_tuple_with_model_invalid():
def test_tuple_form_valid():
- response = client.post("/tuple-form/", data=[("values", "1"), ("values", "2")])
+ response = client.post("/tuple-form/", data={"values": ("1", "2")})
assert response.status_code == 200, response.text
assert response.json() == [1, 2]
def test_tuple_form_invalid():
- response = client.post(
- "/tuple-form/", data=[("values", "1"), ("values", "2"), ("values", "3")]
- )
+ response = client.post("/tuple-form/", data={"values": ("1", "2", "3")})
assert response.status_code == 422, response.text
- response = client.post("/tuple-form/", data=[("values", "1")])
+ response = client.post("/tuple-form/", data={"values": ("1")})
assert response.status_code == 422, response.text
diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py
index 17165c0fc..157fa5caf 100644
--- a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py
+++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py
@@ -9,6 +9,6 @@ def test_middleware():
assert response.status_code == 200, response.text
client = TestClient(app)
- response = client.get("/", allow_redirects=False)
+ response = client.get("/", follow_redirects=False)
assert response.status_code == 307, response.text
assert response.headers["location"] == "https://testserver/"
diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py
index 8dbaf15db..65cdc758a 100644
--- a/tests/test_tutorial/test_body/test_tutorial001.py
+++ b/tests/test_tutorial/test_body/test_tutorial001.py
@@ -176,7 +176,7 @@ def test_post_broken_body():
response = client.post(
"/items/",
headers={"content-type": "application/json"},
- data="{some broken json}",
+ content="{some broken json}",
)
assert response.status_code == 422, response.text
assert response.json() == {
@@ -214,7 +214,7 @@ def test_post_form_for_json():
def test_explicit_content_type():
response = client.post(
"/items/",
- data='{"name": "Foo", "price": 50.5}',
+ content='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/json"},
)
assert response.status_code == 200, response.text
@@ -223,7 +223,7 @@ def test_explicit_content_type():
def test_geo_json():
response = client.post(
"/items/",
- data='{"name": "Foo", "price": 50.5}',
+ content='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/geo+json"},
)
assert response.status_code == 200, response.text
@@ -232,7 +232,7 @@ def test_geo_json():
def test_no_content_type_is_json():
response = client.post(
"/items/",
- data='{"name": "Foo", "price": 50.5}',
+ content='{"name": "Foo", "price": 50.5}',
)
assert response.status_code == 200, response.text
assert response.json() == {
@@ -255,17 +255,19 @@ def test_wrong_headers():
]
}
- response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"})
+ response = client.post(
+ "/items/", content=data, headers={"Content-Type": "text/plain"}
+ )
assert response.status_code == 422, response.text
assert response.json() == invalid_dict
response = client.post(
- "/items/", data=data, headers={"Content-Type": "application/geo+json-seq"}
+ "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
)
assert response.status_code == 422, response.text
assert response.json() == invalid_dict
response = client.post(
- "/items/", data=data, headers={"Content-Type": "application/not-really-json"}
+ "/items/", content=data, headers={"Content-Type": "application/not-really-json"}
)
assert response.status_code == 422, response.text
assert response.json() == invalid_dict
diff --git a/tests/test_tutorial/test_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py
index dd9d9911e..83bcb68f3 100644
--- a/tests/test_tutorial/test_body/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_body/test_tutorial001_py310.py
@@ -185,7 +185,7 @@ def test_post_broken_body(client: TestClient):
response = client.post(
"/items/",
headers={"content-type": "application/json"},
- data="{some broken json}",
+ content="{some broken json}",
)
assert response.status_code == 422, response.text
assert response.json() == {
@@ -225,7 +225,7 @@ def test_post_form_for_json(client: TestClient):
def test_explicit_content_type(client: TestClient):
response = client.post(
"/items/",
- data='{"name": "Foo", "price": 50.5}',
+ content='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/json"},
)
assert response.status_code == 200, response.text
@@ -235,7 +235,7 @@ def test_explicit_content_type(client: TestClient):
def test_geo_json(client: TestClient):
response = client.post(
"/items/",
- data='{"name": "Foo", "price": 50.5}',
+ content='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/geo+json"},
)
assert response.status_code == 200, response.text
@@ -245,7 +245,7 @@ def test_geo_json(client: TestClient):
def test_no_content_type_is_json(client: TestClient):
response = client.post(
"/items/",
- data='{"name": "Foo", "price": 50.5}',
+ content='{"name": "Foo", "price": 50.5}',
)
assert response.status_code == 200, response.text
assert response.json() == {
@@ -269,17 +269,19 @@ def test_wrong_headers(client: TestClient):
]
}
- response = client.post("/items/", data=data, headers={"Content-Type": "text/plain"})
+ response = client.post(
+ "/items/", content=data, headers={"Content-Type": "text/plain"}
+ )
assert response.status_code == 422, response.text
assert response.json() == invalid_dict
response = client.post(
- "/items/", data=data, headers={"Content-Type": "application/geo+json-seq"}
+ "/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
)
assert response.status_code == 422, response.text
assert response.json() == invalid_dict
response = client.post(
- "/items/", data=data, headers={"Content-Type": "application/not-really-json"}
+ "/items/", content=data, headers={"Content-Type": "application/not-really-json"}
)
assert response.status_code == 422, response.text
assert response.json() == invalid_dict
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
index edccffec1..38ae211db 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py
@@ -3,8 +3,6 @@ from fastapi.testclient import TestClient
from docs_src.cookie_params.tutorial001 import app
-client = TestClient(app)
-
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
@@ -88,6 +86,7 @@ openapi_schema = {
],
)
def test(path, cookies, expected_status, expected_response):
- response = client.get(path, cookies=cookies)
+ client = TestClient(app, cookies=cookies)
+ response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
index 5caa5c440..5ad52fb5e 100644
--- a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
+++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py
@@ -70,14 +70,6 @@ openapi_schema = {
}
-@pytest.fixture(name="client")
-def get_client():
- from docs_src.cookie_params.tutorial001_py310 import app
-
- client = TestClient(app)
- return client
-
-
@needs_py310
@pytest.mark.parametrize(
"path,cookies,expected_status,expected_response",
@@ -94,7 +86,10 @@ def get_client():
("/items", {"session": "cookiesession"}, 200, {"ads_id": None}),
],
)
-def test(path, cookies, expected_status, expected_response, client: TestClient):
- response = client.get(path, cookies=cookies)
+def test(path, cookies, expected_status, expected_response):
+ from docs_src.cookie_params.tutorial001_py310 import app
+
+ client = TestClient(app, cookies=cookies)
+ response = client.get(path)
assert response.status_code == expected_status
assert response.json() == expected_response
diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
index 3eb5822e2..e6da630e8 100644
--- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
+++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py
@@ -26,7 +26,7 @@ def test_gzip_request(compress):
data = gzip.compress(data)
headers["Content-Encoding"] = "gzip"
headers["Content-Type"] = "application/json"
- response = client.post("/sum", data=data, headers=headers)
+ response = client.post("/sum", content=data, headers=headers)
assert response.json() == {"sum": n}
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py
index 72bbfd277..9b10916e5 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial006.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py
@@ -32,6 +32,6 @@ def test_openapi_schema():
def test_get():
- response = client.get("/typer", allow_redirects=False)
+ response = client.get("/typer", follow_redirects=False)
assert response.status_code == 307, response.text
assert response.headers["location"] == "https://typer.tiangolo.com"
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py
index ac5a76d34..b3e60e86a 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial006b.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py
@@ -27,6 +27,6 @@ def test_openapi_schema():
def test_redirect_response_class():
- response = client.get("/fastapi", allow_redirects=False)
+ response = client.get("/fastapi", follow_redirects=False)
assert response.status_code == 307
assert response.headers["location"] == "https://fastapi.tiangolo.com"
diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py
index 009225e8c..0cb6ddaa3 100644
--- a/tests/test_tutorial/test_custom_response/test_tutorial006c.py
+++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py
@@ -27,6 +27,6 @@ def test_openapi_schema():
def test_redirect_status_code():
- response = client.get("/pydantic", allow_redirects=False)
+ response = client.get("/pydantic", follow_redirects=False)
assert response.status_code == 302
assert response.headers["location"] == "https://pydantic-docs.helpmanual.io/"
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
index 5533b2957..330b4e2c7 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py
@@ -47,7 +47,7 @@ def test_openapi_schema():
def test_post():
- response = client.post("/items/", data=b"this is actually not validated")
+ response = client.post("/items/", content=b"this is actually not validated")
assert response.status_code == 200, response.text
assert response.json() == {
"size": 30,
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
index cb5dbc8eb..076f60b2f 100644
--- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
+++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py
@@ -58,7 +58,7 @@ def test_post():
- x-men
- x-avengers
"""
- response = client.post("/items/", data=yaml_data)
+ response = client.post("/items/", content=yaml_data)
assert response.status_code == 200, response.text
assert response.json() == {
"name": "Deadpoolio",
@@ -74,7 +74,7 @@ def test_post_broken_yaml():
x - x-men
x - x-avengers
"""
- response = client.post("/items/", data=yaml_data)
+ response = client.post("/items/", content=yaml_data)
assert response.status_code == 422, response.text
assert response.json() == {"detail": "Invalid YAML"}
@@ -88,7 +88,7 @@ def test_post_invalid():
- x-avengers
- sneaky: object
"""
- response = client.post("/items/", data=yaml_data)
+ response = client.post("/items/", content=yaml_data)
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py
index 3b0a36ebc..bbfef9f7c 100644
--- a/tests/test_tutorial/test_security/test_tutorial006.py
+++ b/tests/test_tutorial/test_security/test_tutorial006.py
@@ -1,7 +1,6 @@
from base64 import b64encode
from fastapi.testclient import TestClient
-from requests.auth import HTTPBasicAuth
from docs_src.security.tutorial006 import app
@@ -38,8 +37,7 @@ def test_openapi_schema():
def test_security_http_basic():
- auth = HTTPBasicAuth(username="john", password="secret")
- response = client.get("/users/me", auth=auth)
+ response = client.get("/users/me", auth=("john", "secret"))
assert response.status_code == 200, response.text
assert response.json() == {"username": "john", "password": "secret"}
diff --git a/tests/test_tutorial/test_websockets/test_tutorial002.py b/tests/test_tutorial/test_websockets/test_tutorial002.py
index a8523c9c4..bb5ccbf8e 100644
--- a/tests/test_tutorial/test_websockets/test_tutorial002.py
+++ b/tests/test_tutorial/test_websockets/test_tutorial002.py
@@ -4,20 +4,18 @@ from fastapi.websockets import WebSocketDisconnect
from docs_src.websockets.tutorial002 import app
-client = TestClient(app)
-
def test_main():
+ client = TestClient(app)
response = client.get("/")
assert response.status_code == 200, response.text
assert b"" in response.content
def test_websocket_with_cookie():
+ client = TestClient(app, cookies={"session": "fakesession"})
with pytest.raises(WebSocketDisconnect):
- with client.websocket_connect(
- "/items/foo/ws", cookies={"session": "fakesession"}
- ) as websocket:
+ with client.websocket_connect("/items/foo/ws") as websocket:
message = "Message one"
websocket.send_text(message)
data = websocket.receive_text()
@@ -33,6 +31,7 @@ def test_websocket_with_cookie():
def test_websocket_with_header():
+ client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/bar/ws?token=some-token") as websocket:
message = "Message one"
@@ -50,6 +49,7 @@ def test_websocket_with_header():
def test_websocket_with_header_and_query():
+ client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/2/ws?q=3&token=some-token") as websocket:
message = "Message one"
@@ -71,6 +71,7 @@ def test_websocket_with_header_and_query():
def test_websocket_no_credentials():
+ client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/foo/ws"):
pytest.fail(
@@ -79,6 +80,7 @@ def test_websocket_no_credentials():
def test_websocket_invalid_data():
+ client = TestClient(app)
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("/items/foo/ws?q=bar&token=some-token"):
pytest.fail(
diff --git a/tests/test_ws_router.py b/tests/test_ws_router.py
index 206d743ba..c312821e9 100644
--- a/tests/test_ws_router.py
+++ b/tests/test_ws_router.py
@@ -111,7 +111,7 @@ def test_router_ws_depends():
def test_router_ws_depends_with_override():
client = TestClient(app)
- app.dependency_overrides[ws_dependency] = lambda: "Override"
+ app.dependency_overrides[ws_dependency] = lambda: "Override" # noqa: E731
with client.websocket_connect("/router-ws-depends/") as websocket:
assert websocket.receive_text() == "Override"