From 5aacc7b6a0a3658bd0b956347102e398dd0ea5cf Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:53:47 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=94=A8=20Run=20tests=20with=20`pytest?= =?UTF-8?q?-xdist`=20and=20`pytest-cov`=20(#14992)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- pyproject.toml | 8 +- scripts/test-cov-html.sh | 3 +- scripts/test-cov.sh | 6 + scripts/test.sh | 2 +- .../test_tutorial002.py | 3 +- .../test_tutorial004.py | 3 +- .../test_background_tasks/test_tutorial001.py | 2 + .../test_background_tasks/test_tutorial002.py | 3 +- .../test_custom_docs_ui/test_tutorial001.py | 6 + .../test_custom_docs_ui/test_tutorial002.py | 6 + .../test_events/test_tutorial002.py | 4 + .../test_tutorial/test_settings/test_app01.py | 2 +- .../test_static_files/test_tutorial001.py | 5 + .../test_templates/test_tutorial001.py | 3 + .../test_websockets/test_tutorial003.py | 3 + tests/utils.py | 2 + uv.lock | 114 +++++++++++++++++- 18 files changed, 165 insertions(+), 12 deletions(-) create mode 100755 scripts/test-cov.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6046a4560d..f4b7862150 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -107,7 +107,7 @@ jobs: run: uv pip install "git+https://github.com/Kludex/starlette@main" - run: mkdir coverage - name: Test - run: uv run --no-sync bash scripts/test.sh + run: uv run --no-sync bash scripts/test-cov.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} diff --git a/pyproject.toml b/pyproject.toml index fa298ad5b1..55a5870f0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ github-actions = [ tests = [ { include-group = "docs-tests" }, "anyio[trio] >=3.2.1,<5.0.0", - "coverage[toml] >=6.5.0,<8.0", + "coverage[toml] >=7.13,<8.0", "dirty-equals >=0.9.0", "flask >=3.0.0,<4.0.0", "inline-snapshot >=0.21.1", @@ -178,6 +178,10 @@ tests = [ "types-orjson >=3.6.2", "types-ujson >=5.10.0.20240515", "a2wsgi >=1.9.0,<=2.0.0", + "pytest-xdist[psutil]>=2.5.0", + "pytest-cov>=4.0.0", + "pytest-sugar>=1.0.0", + "pytest-timeout>=2.4.0", ] translations = [ "gitpython >=3.1.46", @@ -229,6 +233,7 @@ strict_xfail = true filterwarnings = [ "error", ] +timeout = "20" [tool.coverage.run] parallel = true @@ -240,7 +245,6 @@ source = [ ] relative_files = true context = '${CONTEXT}' -dynamic_context = "test_function" omit = [ "tests/benchmarks/*", "docs_src/response_model/tutorial003_04_py39.py", diff --git a/scripts/test-cov-html.sh b/scripts/test-cov-html.sh index f87f906dc2..3397a57609 100755 --- a/scripts/test-cov-html.sh +++ b/scripts/test-cov-html.sh @@ -3,5 +3,4 @@ set -e set -x -bash scripts/test.sh ${@} -bash scripts/coverage.sh +bash scripts/test-cov.sh --cov-report=term-missing --cov-report=html ${@} diff --git a/scripts/test-cov.sh b/scripts/test-cov.sh new file mode 100755 index 0000000000..1f960430f8 --- /dev/null +++ b/scripts/test-cov.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +set -x + +bash scripts/test.sh --cov --cov-context=test ${@} diff --git a/scripts/test.sh b/scripts/test.sh index 0bffcd1cdd..f7ec7e0a67 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -4,4 +4,4 @@ set -e set -x export PYTHONPATH=./docs_src -coverage run -m pytest tests scripts/tests/ ${@} +pytest -n auto --dist loadgroup tests scripts/tests/ ${@} diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index 586779e44f..f1b53a96ce 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -6,7 +6,7 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py310 +from tests.utils import needs_py310, workdir_lock @pytest.fixture( @@ -29,6 +29,7 @@ def test_path_operation(client: TestClient): assert response.json() == {"id": "foo", "value": "there goes my hero"} +@workdir_lock def test_path_operation_img(client: TestClient): shutil.copy("./docs/en/docs/img/favicon.png", "./image.png") response = client.get("/items/foo?img=1") diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index fe56fbb8b5..c6d517f205 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -6,7 +6,7 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py310 +from tests.utils import needs_py310, workdir_lock @pytest.fixture( @@ -29,6 +29,7 @@ def test_path_operation(client: TestClient): assert response.json() == {"id": "foo", "value": "there goes my hero"} +@workdir_lock def test_path_operation_img(client: TestClient): shutil.copy("./docs/en/docs/img/favicon.png", "./image.png") response = client.get("/items/foo?img=1") diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial001.py b/tests/test_tutorial/test_background_tasks/test_tutorial001.py index 100583f77a..1fc919c399 100644 --- a/tests/test_tutorial/test_background_tasks/test_tutorial001.py +++ b/tests/test_tutorial/test_background_tasks/test_tutorial001.py @@ -4,10 +4,12 @@ from pathlib import Path from fastapi.testclient import TestClient from docs_src.background_tasks.tutorial001_py310 import app +from tests.utils import workdir_lock client = TestClient(app) +@workdir_lock def test(): log = Path("log.txt") if log.is_file(): diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002.py b/tests/test_tutorial/test_background_tasks/test_tutorial002.py index f29402c833..31b58cde1f 100644 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002.py +++ b/tests/test_tutorial/test_background_tasks/test_tutorial002.py @@ -5,7 +5,7 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310 +from tests.utils import needs_py310, workdir_lock @pytest.fixture( @@ -22,6 +22,7 @@ def get_client(request: pytest.FixtureRequest): return client +@workdir_lock def test(client: TestClient): log = Path("log.txt") if log.is_file(): diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py index f7eb7fd97d..ae25497f32 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py @@ -4,6 +4,8 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient +from tests.utils import workdir_lock + @pytest.fixture(scope="module") def client(): @@ -17,6 +19,7 @@ def client(): static_dir.rmdir() +@workdir_lock def test_swagger_ui_html(client: TestClient): response = client.get("/docs") assert response.status_code == 200, response.text @@ -24,18 +27,21 @@ def test_swagger_ui_html(client: TestClient): assert "https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" in response.text +@workdir_lock def test_swagger_ui_oauth2_redirect_html(client: TestClient): response = client.get("/docs/oauth2-redirect") assert response.status_code == 200, response.text assert "window.opener.swaggerUIRedirectOauth2" in response.text +@workdir_lock def test_redoc_html(client: TestClient): response = client.get("/redoc") assert response.status_code == 200, response.text assert "https://unpkg.com/redoc@2/bundles/redoc.standalone.js" in response.text +@workdir_lock def test_api(client: TestClient): response = client.get("/users/john") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index 28945d8387..ac67170d74 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -4,6 +4,8 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient +from tests.utils import workdir_lock + @pytest.fixture(scope="module") def client(): @@ -17,6 +19,7 @@ def client(): static_dir.rmdir() +@workdir_lock def test_swagger_ui_html(client: TestClient): response = client.get("/docs") assert response.status_code == 200, response.text @@ -24,18 +27,21 @@ def test_swagger_ui_html(client: TestClient): assert "/static/swagger-ui.css" in response.text +@workdir_lock def test_swagger_ui_oauth2_redirect_html(client: TestClient): response = client.get("/docs/oauth2-redirect") assert response.status_code == 200, response.text assert "window.opener.swaggerUIRedirectOauth2" in response.text +@workdir_lock def test_redoc_html(client: TestClient): response = client.get("/redoc") assert response.status_code == 200, response.text assert "/static/redoc.standalone.js" in response.text +@workdir_lock def test_api(client: TestClient): response = client.get("/users/john") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py index 47af7a9527..262d936f98 100644 --- a/tests/test_tutorial/test_events/test_tutorial002.py +++ b/tests/test_tutorial/test_events/test_tutorial002.py @@ -3,6 +3,8 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot +from tests.utils import workdir_lock + @pytest.fixture(name="app", scope="module") def get_app(): @@ -11,6 +13,7 @@ def get_app(): yield app +@workdir_lock def test_events(app: FastAPI): with TestClient(app) as client: response = client.get("/items/") @@ -20,6 +23,7 @@ def test_events(app: FastAPI): assert "Application shutdown" in log.read() +@workdir_lock def test_openapi_schema(app: FastAPI): with TestClient(app) as client: response = client.get("/openapi.json") diff --git a/tests/test_tutorial/test_settings/test_app01.py b/tests/test_tutorial/test_settings/test_app01.py index da27308e80..57ae3e6a68 100644 --- a/tests/test_tutorial/test_settings/test_app01.py +++ b/tests/test_tutorial/test_settings/test_app01.py @@ -22,7 +22,7 @@ def get_mod_name(request: pytest.FixtureRequest): @pytest.fixture(name="client") def get_test_client(mod_name: str, monkeypatch: MonkeyPatch) -> TestClient: if mod_name in sys.modules: - del sys.modules[mod_name] + del sys.modules[mod_name] # pragma: no cover monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") main_mod = importlib.import_module(mod_name) return TestClient(main_mod.app) diff --git a/tests/test_tutorial/test_static_files/test_tutorial001.py b/tests/test_tutorial/test_static_files/test_tutorial001.py index 1d57310f30..d2b120fbb9 100644 --- a/tests/test_tutorial/test_static_files/test_tutorial001.py +++ b/tests/test_tutorial/test_static_files/test_tutorial001.py @@ -5,6 +5,8 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot +from tests.utils import workdir_lock + @pytest.fixture(scope="module") def client(): @@ -20,17 +22,20 @@ def client(): static_dir.rmdir() +@workdir_lock def test_static_files(client: TestClient): response = client.get("/static/sample.txt") assert response.status_code == 200, response.text assert response.text == "This is a sample static file." +@workdir_lock def test_static_files_not_found(client: TestClient): response = client.get("/static/non_existent_file.txt") assert response.status_code == 404, response.text +@workdir_lock def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_templates/test_tutorial001.py b/tests/test_tutorial/test_templates/test_tutorial001.py index 04bc3fce86..f0377e963c 100644 --- a/tests/test_tutorial/test_templates/test_tutorial001.py +++ b/tests/test_tutorial/test_templates/test_tutorial001.py @@ -3,7 +3,10 @@ import shutil from fastapi.testclient import TestClient +from tests.utils import workdir_lock + +@workdir_lock def test_main(): if os.path.isdir("./static"): # pragma: nocover shutil.rmtree("./static") diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py index 781146371d..f481e84334 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial003.py +++ b/tests/test_tutorial/test_websockets/test_tutorial003.py @@ -1,4 +1,5 @@ import importlib +import time from types import ModuleType import pytest @@ -42,11 +43,13 @@ def test_websocket_handle_disconnection(client: TestClient): connection.send_text("Hello from 1234") data1 = connection.receive_text() assert data1 == "You wrote: Hello from 1234" + time.sleep(0.01) # Give server time to process broadcast data2 = connection_two.receive_text() client1_says = "Client #1234 says: Hello from 1234" assert data2 == client1_says data1 = connection.receive_text() assert data1 == client1_says connection_two.close() + time.sleep(0.01) # Give server time to process broadcast data1 = connection.receive_text() assert data1 == "Client #5678 left the chat" diff --git a/tests/utils.py b/tests/utils.py index 09c4e13b00..fff7348b9c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -9,6 +9,8 @@ needs_py314 = pytest.mark.skipif( sys.version_info < (3, 14), reason="requires python3.14+" ) +workdir_lock = pytest.mark.xdist_group("workdir_lock") + def skip_module_if_py_gte_314(): """Skip entire module on Python 3.14+ at import time.""" diff --git a/uv.lock b/uv.lock index 9fa1c2dd74..0ffdfe2cec 100644 --- a/uv.lock +++ b/uv.lock @@ -1037,6 +1037,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "executing" version = "2.2.1" @@ -1142,6 +1151,10 @@ dev = [ { name = "pyjwt" }, { name = "pytest" }, { name = "pytest-codspeed" }, + { name = "pytest-cov" }, + { name = "pytest-sugar" }, + { name = "pytest-timeout" }, + { name = "pytest-xdist", extra = ["psutil"] }, { name = "python-slugify" }, { name = "pyyaml" }, { name = "ruff" }, @@ -1201,6 +1214,10 @@ tests = [ { name = "pyjwt" }, { name = "pytest" }, { name = "pytest-codspeed" }, + { name = "pytest-cov" }, + { name = "pytest-sugar" }, + { name = "pytest-timeout" }, + { name = "pytest-xdist", extra = ["psutil"] }, { name = "pyyaml" }, { name = "ruff" }, { name = "sqlmodel" }, @@ -1257,7 +1274,7 @@ dev = [ { name = "anyio", extras = ["trio"], specifier = ">=3.2.1,<5.0.0" }, { name = "black", specifier = ">=25.1.0" }, { name = "cairosvg", specifier = ">=2.8.2" }, - { name = "coverage", extras = ["toml"], specifier = ">=6.5.0,<8.0" }, + { name = "coverage", extras = ["toml"], specifier = ">=7.13,<8.0" }, { name = "dirty-equals", specifier = ">=0.9.0" }, { name = "flask", specifier = ">=3.0.0,<4.0.0" }, { name = "gitpython", specifier = ">=3.1.46" }, @@ -1283,6 +1300,10 @@ dev = [ { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=9.0.0" }, { name = "pytest-codspeed", specifier = ">=4.2.0" }, + { name = "pytest-cov", specifier = ">=4.0.0" }, + { name = "pytest-sugar", specifier = ">=1.0.0" }, + { name = "pytest-timeout", specifier = ">=2.4.0" }, + { name = "pytest-xdist", extras = ["psutil"], specifier = ">=2.5.0" }, { name = "python-slugify", specifier = ">=8.0.4" }, { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, { name = "ruff", specifier = ">=0.14.14" }, @@ -1331,7 +1352,7 @@ github-actions = [ tests = [ { name = "a2wsgi", specifier = ">=1.9.0,<=2.0.0" }, { name = "anyio", extras = ["trio"], specifier = ">=3.2.1,<5.0.0" }, - { name = "coverage", extras = ["toml"], specifier = ">=6.5.0,<8.0" }, + { name = "coverage", extras = ["toml"], specifier = ">=7.13,<8.0" }, { name = "dirty-equals", specifier = ">=0.9.0" }, { name = "flask", specifier = ">=3.0.0,<4.0.0" }, { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, @@ -1342,6 +1363,10 @@ tests = [ { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=9.0.0" }, { name = "pytest-codspeed", specifier = ">=4.2.0" }, + { name = "pytest-cov", specifier = ">=4.0.0" }, + { name = "pytest-sugar", specifier = ">=1.0.0" }, + { name = "pytest-timeout", specifier = ">=2.4.0" }, + { name = "pytest-xdist", extras = ["psutil"], specifier = ">=2.5.0" }, { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, { name = "ruff", specifier = ">=0.14.14" }, { name = "sqlmodel", specifier = ">=0.0.31" }, @@ -3823,6 +3848,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + [[package]] name = "pwdlib" version = "0.3.0" @@ -4377,6 +4430,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/0e/8cb71fd3ed4ed08c07aec1245aea7bc1b661ba55fd9c392db76f1978d453/pytest_codspeed-4.2.0-py3-none-any.whl", hash = "sha256:e81bbb45c130874ef99aca97929d72682733527a49f84239ba575b5cb843bab0", size = 113726, upload-time = "2025-10-24T09:02:54.785Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "pytest-sugar" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/4e/60fed105549297ba1a700e1ea7b828044842ea27d72c898990510b79b0e2/pytest-sugar-1.1.1.tar.gz", hash = "sha256:73b8b65163ebf10f9f671efab9eed3d56f20d2ca68bda83fa64740a92c08f65d", size = 16533, upload-time = "2025-08-23T12:19:35.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/d5/81d38a91c1fdafb6711f053f5a9b92ff788013b19821257c2c38c1e132df/pytest_sugar-1.1.1-py3-none-any.whl", hash = "sha256:2f8319b907548d5b9d03a171515c1d43d2e38e32bd8182a1781eb20b43344cc8", size = 11440, upload-time = "2025-08-23T12:19:34.894Z" }, +] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[package.optional-dependencies] +psutil = [ + { name = "psutil" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" From 873e48fb15fb6c5cd35cb1ab7f78789f1cdd3279 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Feb 2026 10:54:09 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e0adeb1dc2..1bca6b1b2b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 🔨 Run tests with `pytest-xdist` and `pytest-cov`. PR [#14992](https://github.com/fastapi/fastapi/pull/14992) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.133.1 ### Features From 0901b4092c3688e6e9ac46c5717720cbe9731245 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:34:37 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9D=20Rename=20`docs=5Fsrc/websock?= =?UTF-8?q?ets`=20to=20`docs=5Fsrc/websockets=5F`=20to=20avoid=20import=20?= =?UTF-8?q?errors=20(#14979)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/de/docs/advanced/websockets.md | 10 +++++----- docs/en/docs/advanced/websockets.md | 10 +++++----- docs/es/docs/advanced/websockets.md | 10 +++++----- docs/fr/docs/advanced/websockets.md | 10 +++++----- docs/ja/docs/advanced/websockets.md | 10 +++++----- docs/ko/docs/advanced/websockets.md | 10 +++++----- docs/pt/docs/advanced/websockets.md | 10 +++++----- docs/ru/docs/advanced/websockets.md | 10 +++++----- docs/tr/docs/advanced/websockets.md | 10 +++++----- docs/uk/docs/advanced/websockets.md | 10 +++++----- docs/zh-hant/docs/advanced/websockets.md | 10 +++++----- docs/zh/docs/advanced/websockets.md | 10 +++++----- docs_src/{websockets => websockets_}/__init__.py | 0 .../{websockets => websockets_}/tutorial001_py310.py | 0 .../tutorial002_an_py310.py | 0 .../{websockets => websockets_}/tutorial002_py310.py | 0 .../{websockets => websockets_}/tutorial003_py310.py | 0 .../test_tutorial/test_websockets/test_tutorial001.py | 2 +- .../test_tutorial/test_websockets/test_tutorial002.py | 2 +- .../test_tutorial/test_websockets/test_tutorial003.py | 2 +- 20 files changed, 63 insertions(+), 63 deletions(-) rename docs_src/{websockets => websockets_}/__init__.py (100%) rename docs_src/{websockets => websockets_}/tutorial001_py310.py (100%) rename docs_src/{websockets => websockets_}/tutorial002_an_py310.py (100%) rename docs_src/{websockets => websockets_}/tutorial002_py310.py (100%) rename docs_src/{websockets => websockets_}/tutorial003_py310.py (100%) diff --git a/docs/de/docs/advanced/websockets.md b/docs/de/docs/advanced/websockets.md index b1a49c5aad..22c131838e 100644 --- a/docs/de/docs/advanced/websockets.md +++ b/docs/de/docs/advanced/websockets.md @@ -38,13 +38,13 @@ In der Produktion hätten Sie eine der oben genannten Optionen. Aber es ist der einfachste Weg, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Einen `websocket` erstellen { #create-a-websocket } Erstellen Sie in Ihrer **FastAPI**-Anwendung einen `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Technische Details @@ -58,7 +58,7 @@ Sie könnten auch `from starlette.websockets import WebSocket` verwenden. In Ihrer WebSocket-Route können Sie Nachrichten `await`en und Nachrichten senden. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Sie können Binär-, Text- und JSON-Daten empfangen und senden. @@ -109,7 +109,7 @@ In WebSocket-Endpunkten können Sie Folgendes aus `fastapi` importieren und verw Diese funktionieren auf die gleiche Weise wie für andere FastAPI-Endpunkte/*Pfadoperationen*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | Info @@ -154,7 +154,7 @@ Damit können Sie den WebSocket verbinden und dann Nachrichten senden und empfan Wenn eine WebSocket-Verbindung geschlossen wird, löst `await websocket.receive_text()` eine `WebSocketDisconnect`-Exception aus, die Sie dann wie in folgendem Beispiel abfangen und behandeln können. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Zum Ausprobieren: diff --git a/docs/en/docs/advanced/websockets.md b/docs/en/docs/advanced/websockets.md index ae17742bb5..b3bc9c045b 100644 --- a/docs/en/docs/advanced/websockets.md +++ b/docs/en/docs/advanced/websockets.md @@ -38,13 +38,13 @@ In production you would have one of the options above. But it's the simplest way to focus on the server-side of WebSockets and have a working example: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Create a `websocket` { #create-a-websocket } In your **FastAPI** application, create a `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Technical Details @@ -58,7 +58,7 @@ You could also use `from starlette.websockets import WebSocket`. In your WebSocket route you can `await` for messages and send messages. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} You can receive and send binary, text, and JSON data. @@ -109,7 +109,7 @@ In WebSocket endpoints you can import from `fastapi` and use: They work the same way as for other FastAPI endpoints/*path operations*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ With that you can connect the WebSocket and then send and receive messages: When a WebSocket connection is closed, the `await websocket.receive_text()` will raise a `WebSocketDisconnect` exception, which you can then catch and handle like in this example. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} To try it out: diff --git a/docs/es/docs/advanced/websockets.md b/docs/es/docs/advanced/websockets.md index 2a7fed6c59..e9391c36ca 100644 --- a/docs/es/docs/advanced/websockets.md +++ b/docs/es/docs/advanced/websockets.md @@ -38,13 +38,13 @@ En producción tendrías una de las opciones anteriores. Pero es la forma más sencilla de enfocarse en el lado del servidor de WebSockets y tener un ejemplo funcional: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Crear un `websocket` { #create-a-websocket } En tu aplicación de **FastAPI**, crea un `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Detalles Técnicos @@ -58,7 +58,7 @@ También podrías usar `from starlette.websockets import WebSocket`. En tu ruta de WebSocket puedes `await` para recibir mensajes y enviar mensajes. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Puedes recibir y enviar datos binarios, de texto y JSON. @@ -109,7 +109,7 @@ En endpoints de WebSocket puedes importar desde `fastapi` y usar: Funcionan de la misma manera que para otros endpoints de FastAPI/*path operations*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | Información @@ -154,7 +154,7 @@ Con eso puedes conectar el WebSocket y luego enviar y recibir mensajes: Cuando una conexión de WebSocket se cierra, el `await websocket.receive_text()` lanzará una excepción `WebSocketDisconnect`, que puedes capturar y manejar como en este ejemplo. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Para probarlo: diff --git a/docs/fr/docs/advanced/websockets.md b/docs/fr/docs/advanced/websockets.md index 6f5c3e7033..d78f89c374 100644 --- a/docs/fr/docs/advanced/websockets.md +++ b/docs/fr/docs/advanced/websockets.md @@ -38,13 +38,13 @@ En production, vous auriez l'une des options ci-dessus. Mais c'est la façon la plus simple de se concentrer sur la partie serveur des WebSockets et d'avoir un exemple fonctionnel : -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Créer un `websocket` { #create-a-websocket } Dans votre application **FastAPI**, créez un `websocket` : -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Détails techniques @@ -58,7 +58,7 @@ Vous pourriez aussi utiliser `from starlette.websockets import WebSocket`. Dans votre route WebSocket, vous pouvez `await` des messages et envoyer des messages. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Vous pouvez recevoir et envoyer des données binaires, texte et JSON. @@ -109,7 +109,7 @@ Dans les endpoints WebSocket, vous pouvez importer depuis `fastapi` et utiliser Ils fonctionnent de la même manière que pour les autres endpoints/*chemins d'accès* FastAPI : -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ Avec cela, vous pouvez connecter le WebSocket puis envoyer et recevoir des messa Lorsqu'une connexion WebSocket est fermée, l'instruction `await websocket.receive_text()` lèvera une exception `WebSocketDisconnect`, que vous pouvez ensuite intercepter et gérer comme dans cet exemple. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Pour l'essayer : diff --git a/docs/ja/docs/advanced/websockets.md b/docs/ja/docs/advanced/websockets.md index cb5e376de6..efc02079bb 100644 --- a/docs/ja/docs/advanced/websockets.md +++ b/docs/ja/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets しかし、これはWebSocketsのサーバーサイドに焦点を当て、動作する例を示す最も簡単な方法です。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## `websocket` を作成する { #create-a-websocket } **FastAPI** アプリケーションで、`websocket` を作成します。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 技術詳細 @@ -58,7 +58,7 @@ $ pip install websockets WebSocketルートでは、メッセージを待機して送信するために `await` を使用できます。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} バイナリやテキストデータ、JSONデータを送受信できます。 @@ -109,7 +109,7 @@ WebSocketエンドポイントでは、`fastapi` から以下をインポート これらは、他のFastAPI エンドポイント/*path operations* の場合と同じように機能します。 -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | 情報 @@ -154,7 +154,7 @@ $ fastapi dev main.py WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。 -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 試してみるには、 diff --git a/docs/ko/docs/advanced/websockets.md b/docs/ko/docs/advanced/websockets.md index 384f93952b..cb59097f6e 100644 --- a/docs/ko/docs/advanced/websockets.md +++ b/docs/ko/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets 그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## `websocket` 생성하기 { #create-a-websocket } **FastAPI** 애플리케이션에서 `websocket`을 생성합니다: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 기술 세부사항 @@ -58,7 +58,7 @@ $ pip install websockets WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} 여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다. @@ -109,7 +109,7 @@ WebSocket 엔드포인트에서 `fastapi`에서 다음을 가져와 사용할 이들은 다른 FastAPI 엔드포인트/*경로 처리*와 동일하게 동작합니다: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | 정보 @@ -154,7 +154,7 @@ $ fastapi dev main.py WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 그러면 이 예제처럼 이를 잡아 처리할 수 있습니다. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 테스트해보기: diff --git a/docs/pt/docs/advanced/websockets.md b/docs/pt/docs/advanced/websockets.md index c294b7603e..f148defd4c 100644 --- a/docs/pt/docs/advanced/websockets.md +++ b/docs/pt/docs/advanced/websockets.md @@ -38,13 +38,13 @@ Na produção, você teria uma das opções acima. Mas é a maneira mais simples de focar no lado do servidor de WebSockets e ter um exemplo funcional: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Crie um `websocket` { #create-a-websocket } Em sua aplicação **FastAPI**, crie um `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Detalhes Técnicos @@ -58,7 +58,7 @@ A **FastAPI** fornece o mesmo `WebSocket` diretamente apenas como uma conveniên Em sua rota WebSocket você pode esperar (`await`) por mensagens e enviar mensagens. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Você pode receber e enviar dados binários, de texto e JSON. @@ -109,7 +109,7 @@ Nos endpoints WebSocket você pode importar do `fastapi` e usar: Eles funcionam da mesma forma que para outros endpoints FastAPI/*operações de rota*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | Informação @@ -154,7 +154,7 @@ Com isso você pode conectar o WebSocket e então enviar e receber mensagens: Quando uma conexão WebSocket é fechada, o `await websocket.receive_text()` levantará uma exceção `WebSocketDisconnect`, que você pode então capturar e lidar como neste exemplo. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Para testar: diff --git a/docs/ru/docs/advanced/websockets.md b/docs/ru/docs/advanced/websockets.md index 446cc2505e..d565d507bc 100644 --- a/docs/ru/docs/advanced/websockets.md +++ b/docs/ru/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Создание `websocket` { #create-a-websocket } Создайте `websocket` в своем **FastAPI** приложении: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Технические детали @@ -58,7 +58,7 @@ $ pip install websockets Через эндпоинт веб-сокета вы можете получать и отправлять сообщения. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Вы можете получать и отправлять двоичные, текстовые и JSON данные. @@ -109,7 +109,7 @@ $ fastapi dev main.py Они работают так же, как и в других FastAPI эндпоинтах/*операциях пути*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | Примечание @@ -154,7 +154,7 @@ $ fastapi dev main.py Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере: -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Чтобы воспроизвести пример: diff --git a/docs/tr/docs/advanced/websockets.md b/docs/tr/docs/advanced/websockets.md index 16b4be7e8d..a5ab27597e 100644 --- a/docs/tr/docs/advanced/websockets.md +++ b/docs/tr/docs/advanced/websockets.md @@ -38,13 +38,13 @@ Production'da yukarıdaki seçeneklerden birini kullanırsınız. Ama WebSockets'in server tarafına odaklanmak ve çalışan bir örnek görmek için en basit yol bu: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Bir `websocket` Oluşturun { #create-a-websocket } **FastAPI** uygulamanızda bir `websocket` oluşturun: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Teknik Detaylar @@ -58,7 +58,7 @@ Ama WebSockets'in server tarafına odaklanmak ve çalışan bir örnek görmek i WebSocket route'unuzda mesajları `await` edebilir ve mesaj gönderebilirsiniz. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Binary, text ve JSON verisi alıp gönderebilirsiniz. @@ -109,7 +109,7 @@ WebSocket endpoint'lerinde `fastapi` içinden import edip şunları kullanabilir Diğer FastAPI endpoint'leri/*path operations* ile aynı şekilde çalışırlar: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | Bilgi @@ -154,7 +154,7 @@ Bununla WebSocket'e bağlanabilir, ardından mesaj gönderip alabilirsiniz: Bir WebSocket bağlantısı kapandığında, `await websocket.receive_text()` bir `WebSocketDisconnect` exception'ı raise eder; ardından bunu bu örnekteki gibi yakalayıp (catch) yönetebilirsiniz. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Denemek için: diff --git a/docs/uk/docs/advanced/websockets.md b/docs/uk/docs/advanced/websockets.md index 21bb61d7e9..bb06ac00b7 100644 --- a/docs/uk/docs/advanced/websockets.md +++ b/docs/uk/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets Але це найпростіший спосіб зосередитися на серверній частині WebSockets і мати робочий приклад: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## Створіть `websocket` { #create-a-websocket } У вашому застосунку **FastAPI** створіть `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Технічні деталі @@ -58,7 +58,7 @@ $ pip install websockets У вашому маршруті WebSocket ви можете `await` повідомлення і надсилати повідомлення. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Ви можете отримувати та надсилати бінарні, текстові та JSON-дані. @@ -109,7 +109,7 @@ $ fastapi dev main.py Вони працюють так само, як для інших ендпойнтів FastAPI/*операцій шляху*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ $ fastapi dev main.py Коли з'єднання WebSocket закривається, `await websocket.receive_text()` підніме виняток `WebSocketDisconnect`, який ви можете перехопити й обробити, як у цьому прикладі. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Щоб спробувати: diff --git a/docs/zh-hant/docs/advanced/websockets.md b/docs/zh-hant/docs/advanced/websockets.md index c4e904f6f6..22e3fdcb9e 100644 --- a/docs/zh-hant/docs/advanced/websockets.md +++ b/docs/zh-hant/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets 但這是能讓我們專注於 WebSocket 伺服端並跑起一個可運作範例的最簡單方式: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## 建立一個 `websocket` { #create-a-websocket } 在你的 **FastAPI** 應用中,建立一個 `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 技術細節 @@ -58,7 +58,7 @@ $ pip install websockets 在你的 WebSocket 路由中,你可以 `await` 接收訊息並傳送訊息。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} 你可以接收與傳送二進位、文字與 JSON 資料。 @@ -109,7 +109,7 @@ $ fastapi dev main.py 它們的運作方式與其他 FastAPI 端點/*路徑操作* 相同: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ $ fastapi dev main.py 當 WebSocket 連線關閉時,`await websocket.receive_text()` 會拋出 `WebSocketDisconnect` 例外,你可以像範例中那樣捕捉並處理。 -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 試用方式: diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md index 513e1aaecd..a4cdae3a22 100644 --- a/docs/zh/docs/advanced/websockets.md +++ b/docs/zh/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets 但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: -{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[2,6:38,41:43] *} ## 创建 `websocket` { #create-a-websocket } 在您的 **FastAPI** 应用程序中,创建一个 `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 技术细节 @@ -58,7 +58,7 @@ $ pip install websockets 在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} 您可以接收和发送二进制、文本和 JSON 数据。 @@ -109,7 +109,7 @@ $ fastapi dev main.py 它们的工作方式与其他 FastAPI 端点/*路径操作* 相同: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ $ fastapi dev main.py 当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 尝试以下操作: diff --git a/docs_src/websockets/__init__.py b/docs_src/websockets_/__init__.py similarity index 100% rename from docs_src/websockets/__init__.py rename to docs_src/websockets_/__init__.py diff --git a/docs_src/websockets/tutorial001_py310.py b/docs_src/websockets_/tutorial001_py310.py similarity index 100% rename from docs_src/websockets/tutorial001_py310.py rename to docs_src/websockets_/tutorial001_py310.py diff --git a/docs_src/websockets/tutorial002_an_py310.py b/docs_src/websockets_/tutorial002_an_py310.py similarity index 100% rename from docs_src/websockets/tutorial002_an_py310.py rename to docs_src/websockets_/tutorial002_an_py310.py diff --git a/docs_src/websockets/tutorial002_py310.py b/docs_src/websockets_/tutorial002_py310.py similarity index 100% rename from docs_src/websockets/tutorial002_py310.py rename to docs_src/websockets_/tutorial002_py310.py diff --git a/docs_src/websockets/tutorial003_py310.py b/docs_src/websockets_/tutorial003_py310.py similarity index 100% rename from docs_src/websockets/tutorial003_py310.py rename to docs_src/websockets_/tutorial003_py310.py diff --git a/tests/test_tutorial/test_websockets/test_tutorial001.py b/tests/test_tutorial/test_websockets/test_tutorial001.py index 6f3bf1429a..3bf9ec15b2 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial001.py +++ b/tests/test_tutorial/test_websockets/test_tutorial001.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient from fastapi.websockets import WebSocketDisconnect -from docs_src.websockets.tutorial001_py310 import app +from docs_src.websockets_.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_websockets/test_tutorial002.py b/tests/test_tutorial/test_websockets/test_tutorial002.py index 0509b0d0d6..71fb9e68b8 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial002.py +++ b/tests/test_tutorial/test_websockets/test_tutorial002.py @@ -16,7 +16,7 @@ from ...utils import needs_py310 ], ) def get_app(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.websockets.{request.param}") + mod = importlib.import_module(f"docs_src.websockets_.{request.param}") return mod.app diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py index f481e84334..e5f24d4879 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial003.py +++ b/tests/test_tutorial/test_websockets/test_tutorial003.py @@ -13,7 +13,7 @@ from fastapi.testclient import TestClient ], ) def get_mod(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.websockets.{request.param}") + mod = importlib.import_module(f"docs_src.websockets_.{request.param}") return mod From 5a4d3aa26eb68c22294cbea4f2990ea60a1b0fed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Feb 2026 12:35:03 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1bca6b1b2b..baa4ecdae9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* 📝 Rename `docs_src/websockets` to `docs_src/websockets_` to avoid import errors. PR [#14979](https://github.com/fastapi/fastapi/pull/14979) by [@YuriiMotov](https://github.com/YuriiMotov). + ### Internal * 🔨 Run tests with `pytest-xdist` and `pytest-cov`. PR [#14992](https://github.com/fastapi/fastapi/pull/14992) by [@YuriiMotov](https://github.com/YuriiMotov).