mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-24 18:57:45 -05:00
Compare commits
17 Commits
rename-web
...
xdist
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d61938836b | ||
|
|
06b05f9434 | ||
|
|
cd5f00f949 | ||
|
|
88cf069a98 | ||
|
|
65462231f5 | ||
|
|
f9b4f2c4cb | ||
|
|
9f5f5fd920 | ||
|
|
24b2144fa5 | ||
|
|
e13ffa2a21 | ||
|
|
ef128efaed | ||
|
|
ec390db700 | ||
|
|
daba0aa328 | ||
|
|
0c3581d5c4 | ||
|
|
c73bc94537 | ||
|
|
6c68838615 | ||
|
|
29d082ba24 | ||
|
|
2686c7fbbf |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -7,6 +7,18 @@ hide:
|
||||
|
||||
## Latest Changes
|
||||
|
||||
## 0.133.0
|
||||
|
||||
### Upgrades
|
||||
|
||||
* ⬆️ Add support for Starlette 1.0.0+. PR [#14987](https://github.com/fastapi/fastapi/pull/14987) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.132.1
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻️ Refactor logic to handle OpenAPI and Swagger UI escaping data. PR [#14986](https://github.com/fastapi/fastapi/pull/14986) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Internal
|
||||
|
||||
* 👥 Update FastAPI People - Experts. PR [#14972](https://github.com/fastapi/fastapi/pull/14972) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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 :
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
試してみるには、
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
테스트해보기:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
Чтобы воспроизвести пример:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
Щоб спробувати:
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
試用方式:
|
||||
|
||||
|
||||
@@ -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] *}
|
||||
|
||||
尝试以下操作:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.132.0"
|
||||
__version__ = "0.133.0"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
@@ -1101,16 +1101,18 @@ class FastAPI(Starlette):
|
||||
|
||||
def setup(self) -> None:
|
||||
if self.openapi_url:
|
||||
urls = (server_data.get("url") for server_data in self.servers)
|
||||
server_urls = {url for url in urls if url}
|
||||
|
||||
async def openapi(req: Request) -> JSONResponse:
|
||||
root_path = req.scope.get("root_path", "").rstrip("/")
|
||||
if root_path not in server_urls:
|
||||
if root_path and self.root_path_in_servers:
|
||||
self.servers.insert(0, {"url": root_path})
|
||||
server_urls.add(root_path)
|
||||
return JSONResponse(self.openapi())
|
||||
schema = self.openapi()
|
||||
if root_path and self.root_path_in_servers:
|
||||
server_urls = {s.get("url") for s in schema.get("servers", [])}
|
||||
if root_path not in server_urls:
|
||||
schema = dict(schema)
|
||||
schema["servers"] = [{"url": root_path}] + schema.get(
|
||||
"servers", []
|
||||
)
|
||||
return JSONResponse(schema)
|
||||
|
||||
self.add_route(self.openapi_url, openapi, include_in_schema=False)
|
||||
if self.openapi_url and self.docs_url:
|
||||
|
||||
@@ -5,6 +5,20 @@ from annotated_doc import Doc
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
|
||||
def _html_safe_json(value: Any) -> str:
|
||||
"""Serialize a value to JSON with HTML special characters escaped.
|
||||
|
||||
This prevents injection when the JSON is embedded inside a <script> tag.
|
||||
"""
|
||||
return (
|
||||
json.dumps(value)
|
||||
.replace("<", "\\u003c")
|
||||
.replace(">", "\\u003e")
|
||||
.replace("&", "\\u0026")
|
||||
)
|
||||
|
||||
|
||||
swagger_ui_default_parameters: Annotated[
|
||||
dict[str, Any],
|
||||
Doc(
|
||||
@@ -155,7 +169,7 @@ def get_swagger_ui_html(
|
||||
"""
|
||||
|
||||
for key, value in current_swagger_ui_parameters.items():
|
||||
html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"
|
||||
html += f"{_html_safe_json(key)}: {_html_safe_json(jsonable_encoder(value))},\n"
|
||||
|
||||
if oauth2_redirect_url:
|
||||
html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
|
||||
@@ -169,7 +183,7 @@ def get_swagger_ui_html(
|
||||
|
||||
if init_oauth:
|
||||
html += f"""
|
||||
ui.initOAuth({json.dumps(jsonable_encoder(init_oauth))})
|
||||
ui.initOAuth({_html_safe_json(jsonable_encoder(init_oauth))})
|
||||
"""
|
||||
|
||||
html += """
|
||||
|
||||
@@ -42,7 +42,7 @@ classifiers = [
|
||||
"Topic :: Internet :: WWW/HTTP",
|
||||
]
|
||||
dependencies = [
|
||||
"starlette>=0.40.0,<1.0.0",
|
||||
"starlette>=0.40.0",
|
||||
"pydantic>=2.7.0",
|
||||
"typing-extensions>=4.8.0",
|
||||
"typing-inspection>=0.4.2",
|
||||
@@ -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 = "5"
|
||||
|
||||
[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",
|
||||
|
||||
@@ -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 ${@}
|
||||
|
||||
6
scripts/test-cov.sh
Executable file
6
scripts/test-cov.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
bash scripts/test.sh --cov --cov-context=test ${@}
|
||||
@@ -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/ ${@}
|
||||
|
||||
75
tests/test_openapi_cache_root_path.py
Normal file
75
tests/test_openapi_cache_root_path.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
def test_root_path_does_not_persist_across_requests():
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
def read_root(): # pragma: no cover
|
||||
return {"ok": True}
|
||||
|
||||
# Attacker request with a spoofed root_path
|
||||
attacker_client = TestClient(app, root_path="/evil-api")
|
||||
response1 = attacker_client.get("/openapi.json")
|
||||
data1 = response1.json()
|
||||
assert any(s.get("url") == "/evil-api" for s in data1.get("servers", []))
|
||||
|
||||
# Subsequent legitimate request with no root_path
|
||||
clean_client = TestClient(app)
|
||||
response2 = clean_client.get("/openapi.json")
|
||||
data2 = response2.json()
|
||||
servers = [s.get("url") for s in data2.get("servers", [])]
|
||||
assert "/evil-api" not in servers
|
||||
|
||||
|
||||
def test_multiple_different_root_paths_do_not_accumulate():
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
def read_root(): # pragma: no cover
|
||||
return {"ok": True}
|
||||
|
||||
for prefix in ["/path-a", "/path-b", "/path-c"]:
|
||||
c = TestClient(app, root_path=prefix)
|
||||
c.get("/openapi.json")
|
||||
|
||||
# A clean request should not have any of them
|
||||
clean_client = TestClient(app)
|
||||
response = clean_client.get("/openapi.json")
|
||||
data = response.json()
|
||||
servers = [s.get("url") for s in data.get("servers", [])]
|
||||
for prefix in ["/path-a", "/path-b", "/path-c"]:
|
||||
assert prefix not in servers, (
|
||||
f"root_path '{prefix}' leaked into clean request: {servers}"
|
||||
)
|
||||
|
||||
|
||||
def test_legitimate_root_path_still_appears():
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
def read_root(): # pragma: no cover
|
||||
return {"ok": True}
|
||||
|
||||
client = TestClient(app, root_path="/api/v1")
|
||||
response = client.get("/openapi.json")
|
||||
data = response.json()
|
||||
servers = [s.get("url") for s in data.get("servers", [])]
|
||||
assert "/api/v1" in servers
|
||||
|
||||
|
||||
def test_configured_servers_not_mutated():
|
||||
configured_servers = [{"url": "https://prod.example.com"}]
|
||||
app = FastAPI(servers=configured_servers)
|
||||
|
||||
@app.get("/")
|
||||
def read_root(): # pragma: no cover
|
||||
return {"ok": True}
|
||||
|
||||
# Request with a rogue root_path
|
||||
attacker_client = TestClient(app, root_path="/evil")
|
||||
attacker_client.get("/openapi.json")
|
||||
|
||||
# The original servers list must be untouched
|
||||
assert configured_servers == [{"url": "https://prod.example.com"}]
|
||||
37
tests/test_swagger_ui_escape.py
Normal file
37
tests/test_swagger_ui_escape.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from fastapi.openapi.docs import get_swagger_ui_html
|
||||
|
||||
|
||||
def test_init_oauth_html_chars_are_escaped():
|
||||
xss_payload = "Evil</script><script>alert(1)</script>"
|
||||
html = get_swagger_ui_html(
|
||||
openapi_url="/openapi.json",
|
||||
title="Test",
|
||||
init_oauth={"appName": xss_payload},
|
||||
)
|
||||
body = html.body.decode()
|
||||
|
||||
assert "</script><script>" not in body
|
||||
assert "\\u003c/script\\u003e\\u003cscript\\u003e" in body
|
||||
|
||||
|
||||
def test_swagger_ui_parameters_html_chars_are_escaped():
|
||||
html = get_swagger_ui_html(
|
||||
openapi_url="/openapi.json",
|
||||
title="Test",
|
||||
swagger_ui_parameters={"customKey": "<img src=x onerror=alert(1)>"},
|
||||
)
|
||||
body = html.body.decode()
|
||||
assert "<img src=x onerror=alert(1)>" not in body
|
||||
assert "\\u003cimg" in body
|
||||
|
||||
|
||||
def test_normal_init_oauth_still_works():
|
||||
html = get_swagger_ui_html(
|
||||
openapi_url="/openapi.json",
|
||||
title="Test",
|
||||
init_oauth={"clientId": "my-client", "appName": "My App"},
|
||||
)
|
||||
body = html.body.decode()
|
||||
assert '"clientId": "my-client"' in body
|
||||
assert '"appName": "My App"' in body
|
||||
assert "ui.initOAuth" in body
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -12,7 +12,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
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
176
uv.lock
generated
176
uv.lock
generated
@@ -192,7 +192,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.78.0"
|
||||
version = "0.83.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -204,9 +204,9 @@ dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/51/32849a48f9b1cfe80a508fd269b20bd8f0b1357c70ba092890fde5a6a10b/anthropic-0.78.0.tar.gz", hash = "sha256:55fd978ab9b049c61857463f4c4e9e092b24f892519c6d8078cee1713d8af06e", size = 509136, upload-time = "2026-02-05T17:52:04.986Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/e5/02cd2919ec327b24234abb73082e6ab84c451182cc3cc60681af700f4c63/anthropic-0.83.0.tar.gz", hash = "sha256:a8732c68b41869266c3034541a31a29d8be0f8cd0a714f9edce3128b351eceb4", size = 534058, upload-time = "2026-02-19T19:26:38.904Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/03/2f50931a942e5e13f80e24d83406714672c57964be593fc046d81369335b/anthropic-0.78.0-py3-none-any.whl", hash = "sha256:2a9887d2e99d1b0f9fe08857a1e9fe5d2d4030455dbf9ac65aab052e2efaeac4", size = 405485, upload-time = "2026-02-05T17:52:03.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/75/b9d58e4e2a4b1fc3e75ffbab978f999baf8b7c4ba9f96e60edb918ba386b/anthropic-0.83.0-py3-none-any.whl", hash = "sha256:f069ef508c73b8f9152e8850830d92bd5ef185645dbacf234bb213344a274810", size = 456991, upload-time = "2026-02-19T19:26:40.114Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -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" },
|
||||
@@ -1242,7 +1259,7 @@ requires-dist = [
|
||||
{ name = "python-multipart", marker = "extra == 'standard'", specifier = ">=0.0.18" },
|
||||
{ name = "python-multipart", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.0.18" },
|
||||
{ name = "pyyaml", marker = "extra == 'all'", specifier = ">=5.3.1" },
|
||||
{ name = "starlette", specifier = ">=0.40.0,<1.0.0" },
|
||||
{ name = "starlette", specifier = ">=0.40.0" },
|
||||
{ name = "typing-extensions", specifier = ">=4.8.0" },
|
||||
{ name = "typing-inspection", specifier = ">=0.4.2" },
|
||||
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" },
|
||||
@@ -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" },
|
||||
@@ -1922,14 +1947,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "griffelib"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "griffe-typingdoc"
|
||||
version = "0.3.1"
|
||||
@@ -1955,6 +1972,14 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/3c/c2a9eee79bf2c8002d2fa370534bee93fdca39e8b1fc82e83d552d5d2c07/griffe_warnings_deprecated-1.1.1-py3-none-any.whl", hash = "sha256:4b7d765e82ca9139ed44ffe7bdebed0d3a46ce014ad5a35a2c22e9a16288737a", size = 6565, upload-time = "2026-02-20T15:35:23.577Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "griffelib"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "groq"
|
||||
version = "1.0.0"
|
||||
@@ -2162,26 +2187,23 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "huggingface-hub"
|
||||
version = "0.36.2"
|
||||
version = "1.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "filelock" },
|
||||
{ name = "fsspec" },
|
||||
{ name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
||||
{ name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
||||
{ name = "httpx" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "shellingham" },
|
||||
{ name = "tqdm" },
|
||||
{ name = "typer-slim" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/fc/eb9bc06130e8bbda6a616e1b80a7aa127681c448d6b49806f61db2670b61/huggingface_hub-1.4.1.tar.gz", hash = "sha256:b41131ec35e631e7383ab26d6146b8d8972abc8b6309b963b306fbcca87f5ed5", size = 642156, upload-time = "2026-02-06T09:20:03.013Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
inference = [
|
||||
{ name = "aiohttp" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/ae/2f6d96b4e6c5478d87d606a1934b5d436c4a2bce6bb7c6fdece891c128e3/huggingface_hub-1.4.1-py3-none-any.whl", hash = "sha256:9931d075fb7a79af5abc487106414ec5fba2c0ae86104c0c62fd6cae38873d18", size = 553326, upload-time = "2026-02-06T09:20:00.728Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3826,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"
|
||||
@@ -3995,7 +4045,7 @@ groq = [
|
||||
{ name = "groq" },
|
||||
]
|
||||
huggingface = [
|
||||
{ name = "huggingface-hub", extra = ["inference"] },
|
||||
{ name = "huggingface-hub" },
|
||||
]
|
||||
logfire = [
|
||||
{ name = "logfire", extra = ["httpx"] },
|
||||
@@ -4147,7 +4197,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-evals"
|
||||
version = "1.56.0"
|
||||
version = "1.62.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -4157,9 +4207,9 @@ dependencies = [
|
||||
{ name = "pyyaml" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/f2/8c59284a2978af3fbda45ae3217218eaf8b071207a9290b54b7613983e5d/pydantic_evals-1.56.0.tar.gz", hash = "sha256:206635107127af6a3ee4b1fc8f77af6afb14683615a2d6b3609f79467c1c0d28", size = 47210, upload-time = "2026-02-06T01:13:25.714Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/90/080f6722412263395d1d6d066ee90fa8bc2722ce097844220c2d9c946877/pydantic_evals-1.62.0.tar.gz", hash = "sha256:198c4bee936718a4acf6f504056b113e60b34eb49021df8889a394e14c803693", size = 56434, upload-time = "2026-02-19T05:07:11.793Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/51/9875d19ff6d584aaeb574aba76b49d931b822546fc60b29c4fc0da98170d/pydantic_evals-1.56.0-py3-none-any.whl", hash = "sha256:d1efb410c97135aabd2a22453b10c981b2b9851985e9354713af67ae0973b7a9", size = 56407, upload-time = "2026-02-06T01:13:17.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/b9/dc8dba744ec02b16c6fd1abe3fd8ef1b00fd05c72feef5069851b811952f/pydantic_evals-1.62.0-py3-none-any.whl", hash = "sha256:0ca7e10037ed90393c54b6cff41370d6d4bac63f8c878715599c58863c303db1", size = 67341, upload-time = "2026-02-19T05:07:03.83Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4380,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"
|
||||
@@ -5558,6 +5665,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer-slim"
|
||||
version = "0.21.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-doc" },
|
||||
{ name = "click" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a5/ca/0d9d822fd8a4c7e830cba36a2557b070d4b4a9558a0460377a61f8fb315d/typer_slim-0.21.2.tar.gz", hash = "sha256:78f20d793036a62aaf9c3798306142b08261d4b2a941c6e463081239f062a2f9", size = 120497, upload-time = "2026-02-10T19:33:45.836Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/03/e09325cfc40a33a82b31ba1a3f1d97e85246736856a45a43b19fcb48b1c2/typer_slim-0.21.2-py3-none-any.whl", hash = "sha256:4705082bb6c66c090f60e47c8be09a93158c139ce0aa98df7c6c47e723395e5f", size = 56790, upload-time = "2026-02-10T19:33:47.221Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-orjson"
|
||||
version = "3.6.2"
|
||||
|
||||
Reference in New Issue
Block a user