mirror of
https://github.com/fastapi/fastapi.git
synced 2026-02-23 10:25:02 -05:00
Compare commits
9 Commits
0.130.0
...
fastapi-pe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62dbcc4ed8 | ||
|
|
a3c8c37272 | ||
|
|
2826124378 | ||
|
|
4da264f0f3 | ||
|
|
c5559a66dd | ||
|
|
1cea8f659c | ||
|
|
b423b73c35 | ||
|
|
70e8558352 | ||
|
|
48e9835732 |
46
.github/workflows/test.yml
vendored
46
.github/workflows/test.yml
vendored
@@ -68,10 +68,8 @@ jobs:
|
||||
python-version: "3.13"
|
||||
coverage: coverage
|
||||
uv-resolution: highest
|
||||
# Ubuntu with 3.13 needs coverage for CodSpeed benchmarks
|
||||
- os: ubuntu-latest
|
||||
python-version: "3.13"
|
||||
coverage: coverage
|
||||
uv-resolution: highest
|
||||
codspeed: codspeed
|
||||
- os: ubuntu-latest
|
||||
@@ -109,20 +107,10 @@ jobs:
|
||||
run: uv pip install "git+https://github.com/Kludex/starlette@main"
|
||||
- run: mkdir coverage
|
||||
- name: Test
|
||||
if: matrix.codspeed != 'codspeed'
|
||||
run: uv run --no-sync bash scripts/test.sh
|
||||
env:
|
||||
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
|
||||
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
|
||||
- name: CodSpeed benchmarks
|
||||
if: matrix.codspeed == 'codspeed'
|
||||
uses: CodSpeedHQ/action@v4
|
||||
env:
|
||||
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}
|
||||
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}
|
||||
with:
|
||||
mode: simulation
|
||||
run: uv run --no-sync coverage run -m pytest tests/ --codspeed
|
||||
# Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow
|
||||
- name: Store coverage files
|
||||
if: matrix.coverage == 'coverage'
|
||||
@@ -132,6 +120,39 @@ jobs:
|
||||
path: coverage
|
||||
include-hidden-files: true
|
||||
|
||||
benchmark:
|
||||
needs:
|
||||
- changes
|
||||
if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
UV_PYTHON: "3.13"
|
||||
UV_RESOLUTION: highest
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-dependency-glob: |
|
||||
pyproject.toml
|
||||
uv.lock
|
||||
- name: Install Dependencies
|
||||
run: uv sync --no-dev --group tests --extra all
|
||||
- name: CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@v4
|
||||
with:
|
||||
mode: simulation
|
||||
run: uv run --no-sync pytest tests/benchmarks --codspeed
|
||||
|
||||
coverage-combine:
|
||||
needs:
|
||||
- test
|
||||
@@ -176,6 +197,7 @@ jobs:
|
||||
if: always()
|
||||
needs:
|
||||
- coverage-combine
|
||||
- benchmark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dump GitHub context
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,13 @@ from fastapi.responses import (
|
||||
|
||||
## FastAPI Responses
|
||||
|
||||
There are a couple of custom FastAPI response classes, you can use them to optimize JSON performance.
|
||||
There were a couple of custom FastAPI response classes that were intended to optimize JSON performance.
|
||||
|
||||
However, they are now deprecated as you will now get better performance by using a [Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/).
|
||||
|
||||
That way, Pydantic will serialize the data into JSON bytes on the Rust side, which will achieve better performance than these custom JSON responses.
|
||||
|
||||
Read more about it in [Custom Response - HTML, Stream, File, others - `orjson` or Response Model](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model).
|
||||
|
||||
::: fastapi.responses.UJSONResponse
|
||||
options:
|
||||
|
||||
@@ -7,6 +7,17 @@ hide:
|
||||
|
||||
## Latest Changes
|
||||
|
||||
### Internal
|
||||
|
||||
* 👷 Do not run codspeed with coverage as it's not tracked. PR [#14966](https://github.com/fastapi/fastapi/pull/14966) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.131.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* 🗑️ Deprecate `ORJSONResponse` and `UJSONResponse`. PR [#14964](https://github.com/fastapi/fastapi/pull/14964) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.130.0
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.130.0"
|
||||
__version__ = "0.131.0"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
from fastapi.exceptions import FastAPIDeprecationWarning
|
||||
from starlette.responses import FileResponse as FileResponse # noqa
|
||||
from starlette.responses import HTMLResponse as HTMLResponse # noqa
|
||||
from starlette.responses import JSONResponse as JSONResponse # noqa
|
||||
@@ -7,6 +8,7 @@ from starlette.responses import PlainTextResponse as PlainTextResponse # noqa
|
||||
from starlette.responses import RedirectResponse as RedirectResponse # noqa
|
||||
from starlette.responses import Response as Response # noqa
|
||||
from starlette.responses import StreamingResponse as StreamingResponse # noqa
|
||||
from typing_extensions import deprecated
|
||||
|
||||
try:
|
||||
import ujson
|
||||
@@ -20,12 +22,29 @@ except ImportError: # pragma: nocover
|
||||
orjson = None # type: ignore
|
||||
|
||||
|
||||
@deprecated(
|
||||
"UJSONResponse is deprecated, FastAPI now serializes data directly to JSON "
|
||||
"bytes via Pydantic when a return type or response model is set, which is "
|
||||
"faster and doesn't need a custom response class. Read more in the FastAPI "
|
||||
"docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model "
|
||||
"and https://fastapi.tiangolo.com/tutorial/response-model/",
|
||||
category=FastAPIDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
class UJSONResponse(JSONResponse):
|
||||
"""
|
||||
JSON response using the high-performance ujson library to serialize data to JSON.
|
||||
"""JSON response using the ujson library to serialize data to JSON.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
|
||||
**Deprecated**: `UJSONResponse` is deprecated. FastAPI now serializes data
|
||||
directly to JSON bytes via Pydantic when a return type or response model is
|
||||
set, which is faster and doesn't need a custom response class.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model)
|
||||
and the
|
||||
[FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
|
||||
|
||||
**Note**: `ujson` is not included with FastAPI and must be installed
|
||||
separately, e.g. `pip install ujson`.
|
||||
"""
|
||||
|
||||
def render(self, content: Any) -> bytes:
|
||||
@@ -33,12 +52,29 @@ class UJSONResponse(JSONResponse):
|
||||
return ujson.dumps(content, ensure_ascii=False).encode("utf-8")
|
||||
|
||||
|
||||
@deprecated(
|
||||
"ORJSONResponse is deprecated, FastAPI now serializes data directly to JSON "
|
||||
"bytes via Pydantic when a return type or response model is set, which is "
|
||||
"faster and doesn't need a custom response class. Read more in the FastAPI "
|
||||
"docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model "
|
||||
"and https://fastapi.tiangolo.com/tutorial/response-model/",
|
||||
category=FastAPIDeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
class ORJSONResponse(JSONResponse):
|
||||
"""
|
||||
JSON response using the high-performance orjson library to serialize data to JSON.
|
||||
"""JSON response using the orjson library to serialize data to JSON.
|
||||
|
||||
Read more about it in the
|
||||
[FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
|
||||
**Deprecated**: `ORJSONResponse` is deprecated. FastAPI now serializes data
|
||||
directly to JSON bytes via Pydantic when a return type or response model is
|
||||
set, which is faster and doesn't need a custom response class.
|
||||
|
||||
Read more in the
|
||||
[FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model)
|
||||
and the
|
||||
[FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
|
||||
|
||||
**Note**: `orjson` is not included with FastAPI and must be installed
|
||||
separately, e.g. `pip install orjson`.
|
||||
"""
|
||||
|
||||
def render(self, content: Any) -> bytes:
|
||||
|
||||
@@ -105,10 +105,6 @@ all = [
|
||||
"itsdangerous >=1.1.0",
|
||||
# For Starlette's schema generation, would not be used with FastAPI
|
||||
"pyyaml >=5.3.1",
|
||||
# For UJSONResponse
|
||||
"ujson >=5.8.0",
|
||||
# For ORJSONResponse
|
||||
"orjson >=3.9.3",
|
||||
# To validate email fields
|
||||
"email-validator >=2.0.0",
|
||||
# Uvicorn with uvloop
|
||||
@@ -151,6 +147,10 @@ docs = [
|
||||
docs-tests = [
|
||||
"httpx >=0.23.0,<1.0.0",
|
||||
"ruff >=0.14.14",
|
||||
# For UJSONResponse
|
||||
"ujson >=5.8.0",
|
||||
# For ORJSONResponse
|
||||
"orjson >=3.9.3",
|
||||
]
|
||||
github-actions = [
|
||||
"httpx >=0.27.0,<1.0.0",
|
||||
@@ -242,6 +242,7 @@ relative_files = true
|
||||
context = '${CONTEXT}'
|
||||
dynamic_context = "test_function"
|
||||
omit = [
|
||||
"tests/benchmarks/*",
|
||||
"docs_src/response_model/tutorial003_04_py39.py",
|
||||
"docs_src/response_model/tutorial003_04_py310.py",
|
||||
"docs_src/dependencies/tutorial013_an_py310.py", # temporary code example?
|
||||
|
||||
@@ -5,6 +5,7 @@ import time
|
||||
from collections import Counter
|
||||
from collections.abc import Container
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import ceil
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -15,12 +16,63 @@ from pydantic import BaseModel, SecretStr
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
github_graphql_url = "https://api.github.com/graphql"
|
||||
questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0"
|
||||
questions_category_id = "DIC_kwDOCZduT84B6E2a"
|
||||
|
||||
|
||||
POINTS_PER_MINUTE_LIMIT = 84 # 5000 points per hour
|
||||
|
||||
|
||||
class RateLimiter:
|
||||
def __init__(self) -> None:
|
||||
self.last_query_cost: int = 1
|
||||
self.remaining_points: int = 5000
|
||||
self.reset_at: datetime = datetime.fromtimestamp(0, timezone.utc)
|
||||
self.last_request_start_time: datetime = datetime.fromtimestamp(0, timezone.utc)
|
||||
self.speed_multiplier: float = 1.0
|
||||
|
||||
def __enter__(self) -> "RateLimiter":
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
|
||||
# Handle primary rate limits
|
||||
primary_limit_wait_time = 0.0
|
||||
if self.remaining_points <= self.last_query_cost:
|
||||
primary_limit_wait_time = (self.reset_at - now).total_seconds() + 2
|
||||
logging.warning(
|
||||
f"Approaching GitHub API rate limit, remaining points: {self.remaining_points}, "
|
||||
f"reset time in {primary_limit_wait_time} seconds"
|
||||
)
|
||||
|
||||
# Handle secondary rate limits
|
||||
secondary_limit_wait_time = 0.0
|
||||
points_per_minute = POINTS_PER_MINUTE_LIMIT * self.speed_multiplier
|
||||
interval = 60 / (points_per_minute / self.last_query_cost)
|
||||
time_since_last_request = (now - self.last_request_start_time).total_seconds()
|
||||
if time_since_last_request < interval:
|
||||
secondary_limit_wait_time = interval - time_since_last_request
|
||||
|
||||
final_wait_time = ceil(max(primary_limit_wait_time, secondary_limit_wait_time))
|
||||
logging.info(f"Sleeping for {final_wait_time} seconds to respect rate limit")
|
||||
time.sleep(max(final_wait_time, 1))
|
||||
|
||||
self.last_request_start_time = datetime.now(tz=timezone.utc)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
pass
|
||||
|
||||
def update_request_info(self, cost: int, remaining: int, reset_at: str) -> None:
|
||||
self.last_query_cost = cost
|
||||
self.remaining_points = remaining
|
||||
self.reset_at = datetime.fromisoformat(reset_at.replace("Z", "+00:00"))
|
||||
|
||||
|
||||
rate_limiter = RateLimiter()
|
||||
|
||||
|
||||
discussions_query = """
|
||||
query Q($after: String, $category_id: ID) {
|
||||
repository(name: "fastapi", owner: "fastapi") {
|
||||
discussions(first: 100, after: $after, categoryId: $category_id) {
|
||||
discussions(first: 30, after: $after, categoryId: $category_id) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
@@ -58,6 +110,11 @@ query Q($after: String, $category_id: ID) {
|
||||
}
|
||||
}
|
||||
}
|
||||
rateLimit {
|
||||
cost
|
||||
remaining
|
||||
resetAt
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -120,7 +177,7 @@ class Settings(BaseSettings):
|
||||
github_token: SecretStr
|
||||
github_repository: str
|
||||
httpx_timeout: int = 30
|
||||
sleep_interval: int = 5
|
||||
speed_multiplier: float = 1.0
|
||||
|
||||
|
||||
def get_graphql_response(
|
||||
@@ -158,11 +215,18 @@ def get_graphql_question_discussion_edges(
|
||||
settings: Settings,
|
||||
after: str | None = None,
|
||||
) -> list[DiscussionsEdge]:
|
||||
data = get_graphql_response(
|
||||
settings=settings,
|
||||
query=discussions_query,
|
||||
after=after,
|
||||
category_id=questions_category_id,
|
||||
with rate_limiter:
|
||||
data = get_graphql_response(
|
||||
settings=settings,
|
||||
query=discussions_query,
|
||||
after=after,
|
||||
category_id=questions_category_id,
|
||||
)
|
||||
|
||||
rate_limiter.update_request_info(
|
||||
cost=data["data"]["rateLimit"]["cost"],
|
||||
remaining=data["data"]["rateLimit"]["remaining"],
|
||||
reset_at=data["data"]["rateLimit"]["resetAt"],
|
||||
)
|
||||
graphql_response = DiscussionsResponse.model_validate(data)
|
||||
return graphql_response.data.repository.discussions.edges
|
||||
@@ -185,8 +249,6 @@ def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]:
|
||||
for discussion_edge in discussion_edges:
|
||||
discussion_nodes.append(discussion_edge.node)
|
||||
last_edge = discussion_edges[-1]
|
||||
# Handle GitHub secondary rate limits, requests per minute
|
||||
time.sleep(settings.sleep_interval)
|
||||
discussion_edges = get_graphql_question_discussion_edges(
|
||||
settings=settings, after=last_edge.cursor
|
||||
)
|
||||
@@ -318,6 +380,7 @@ def main() -> None:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
settings = Settings()
|
||||
logging.info(f"Using config: {settings.model_dump_json()}")
|
||||
rate_limiter.speed_multiplier = settings.speed_multiplier
|
||||
g = Github(settings.github_token.get_secret_value())
|
||||
repo = g.get_repo(settings.github_repository)
|
||||
|
||||
|
||||
73
tests/test_deprecated_responses.py
Normal file
73
tests/test_deprecated_responses.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import FastAPIDeprecationWarning
|
||||
from fastapi.responses import ORJSONResponse, UJSONResponse
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
price: float
|
||||
|
||||
|
||||
# ORJSON
|
||||
|
||||
|
||||
def _make_orjson_app() -> FastAPI:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
|
||||
app = FastAPI(default_response_class=ORJSONResponse)
|
||||
|
||||
@app.get("/items")
|
||||
def get_items() -> Item:
|
||||
return Item(name="widget", price=9.99)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def test_orjson_response_returns_correct_data():
|
||||
app = _make_orjson_app()
|
||||
client = TestClient(app)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
|
||||
response = client.get("/items")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"name": "widget", "price": 9.99}
|
||||
|
||||
|
||||
def test_orjson_response_emits_deprecation_warning():
|
||||
with pytest.warns(FastAPIDeprecationWarning, match="ORJSONResponse is deprecated"):
|
||||
ORJSONResponse(content={"hello": "world"})
|
||||
|
||||
|
||||
# UJSON
|
||||
|
||||
|
||||
def _make_ujson_app() -> FastAPI:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
|
||||
app = FastAPI(default_response_class=UJSONResponse)
|
||||
|
||||
@app.get("/items")
|
||||
def get_items() -> Item:
|
||||
return Item(name="widget", price=9.99)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def test_ujson_response_returns_correct_data():
|
||||
app = _make_ujson_app()
|
||||
client = TestClient(app)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
|
||||
response = client.get("/items")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"name": "widget", "price": 9.99}
|
||||
|
||||
|
||||
def test_ujson_response_emits_deprecation_warning():
|
||||
with pytest.warns(FastAPIDeprecationWarning, match="UJSONResponse is deprecated"):
|
||||
UJSONResponse(content={"hello": "world"})
|
||||
@@ -1,9 +1,14 @@
|
||||
import warnings
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import FastAPIDeprecationWarning
|
||||
from fastapi.responses import ORJSONResponse
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
|
||||
app = FastAPI(default_response_class=ORJSONResponse)
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
|
||||
app = FastAPI(default_response_class=ORJSONResponse)
|
||||
|
||||
|
||||
@app.get("/orjson_non_str_keys")
|
||||
@@ -16,6 +21,8 @@ client = TestClient(app)
|
||||
|
||||
|
||||
def test_orjson_non_str_keys():
|
||||
with client:
|
||||
response = client.get("/orjson_non_str_keys")
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
|
||||
with client:
|
||||
response = client.get("/orjson_non_str_keys")
|
||||
assert response.json() == {"msg": "Hello World", "1": 1}
|
||||
|
||||
@@ -17,12 +17,14 @@ def get_client(request: pytest.FixtureRequest):
|
||||
return client
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
|
||||
def test_get_custom_response(client: TestClient):
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"item_id": "Foo"}]
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
|
||||
def test_openapi_schema(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
from fastapi.exceptions import FastAPIDeprecationWarning
|
||||
from fastapi.testclient import TestClient
|
||||
from inline_snapshot import snapshot
|
||||
|
||||
from docs_src.custom_response.tutorial001b_py310 import app
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", FastAPIDeprecationWarning)
|
||||
from docs_src.custom_response.tutorial001b_py310 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
|
||||
def test_get_custom_response():
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [{"item_id": "Foo"}]
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
|
||||
20
uv.lock
generated
20
uv.lock
generated
@@ -1083,12 +1083,10 @@ all = [
|
||||
{ name = "httpx" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "orjson" },
|
||||
{ name = "pydantic-extra-types" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "ujson" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
standard = [
|
||||
@@ -1134,6 +1132,7 @@ dev = [
|
||||
{ name = "mkdocs-redirects" },
|
||||
{ name = "mkdocstrings", extra = ["python"] },
|
||||
{ name = "mypy" },
|
||||
{ name = "orjson" },
|
||||
{ name = "pillow" },
|
||||
{ name = "playwright" },
|
||||
{ name = "prek" },
|
||||
@@ -1151,6 +1150,7 @@ dev = [
|
||||
{ name = "typer" },
|
||||
{ name = "types-orjson" },
|
||||
{ name = "types-ujson" },
|
||||
{ name = "ujson" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "black" },
|
||||
@@ -1165,15 +1165,19 @@ docs = [
|
||||
{ name = "mkdocs-material" },
|
||||
{ name = "mkdocs-redirects" },
|
||||
{ name = "mkdocstrings", extra = ["python"] },
|
||||
{ name = "orjson" },
|
||||
{ name = "pillow" },
|
||||
{ name = "python-slugify" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "ruff" },
|
||||
{ name = "typer" },
|
||||
{ name = "ujson" },
|
||||
]
|
||||
docs-tests = [
|
||||
{ name = "httpx" },
|
||||
{ name = "orjson" },
|
||||
{ name = "ruff" },
|
||||
{ name = "ujson" },
|
||||
]
|
||||
github-actions = [
|
||||
{ name = "httpx" },
|
||||
@@ -1192,6 +1196,7 @@ tests = [
|
||||
{ name = "httpx" },
|
||||
{ name = "inline-snapshot" },
|
||||
{ name = "mypy" },
|
||||
{ name = "orjson" },
|
||||
{ name = "pwdlib", extra = ["argon2"] },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "pytest" },
|
||||
@@ -1202,6 +1207,7 @@ tests = [
|
||||
{ name = "strawberry-graphql" },
|
||||
{ name = "types-orjson" },
|
||||
{ name = "types-ujson" },
|
||||
{ name = "ujson" },
|
||||
]
|
||||
translations = [
|
||||
{ name = "gitpython" },
|
||||
@@ -1225,7 +1231,6 @@ requires-dist = [
|
||||
{ name = "jinja2", marker = "extra == 'all'", specifier = ">=3.1.5" },
|
||||
{ name = "jinja2", marker = "extra == 'standard'", specifier = ">=3.1.5" },
|
||||
{ name = "jinja2", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=3.1.5" },
|
||||
{ name = "orjson", marker = "extra == 'all'", specifier = ">=3.9.3" },
|
||||
{ name = "pydantic", specifier = ">=2.7.0" },
|
||||
{ name = "pydantic-extra-types", marker = "extra == 'all'", specifier = ">=2.0.0" },
|
||||
{ name = "pydantic-extra-types", marker = "extra == 'standard'", specifier = ">=2.0.0" },
|
||||
@@ -1240,7 +1245,6 @@ requires-dist = [
|
||||
{ name = "starlette", specifier = ">=0.40.0,<1.0.0" },
|
||||
{ name = "typing-extensions", specifier = ">=4.8.0" },
|
||||
{ name = "typing-inspection", specifier = ">=0.4.2" },
|
||||
{ name = "ujson", marker = "extra == 'all'", specifier = ">=5.8.0" },
|
||||
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" },
|
||||
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" },
|
||||
{ name = "uvicorn", extras = ["standard"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.12.0" },
|
||||
@@ -1269,6 +1273,7 @@ dev = [
|
||||
{ name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
|
||||
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" },
|
||||
{ name = "mypy", specifier = ">=1.14.1" },
|
||||
{ name = "orjson", specifier = ">=3.9.3" },
|
||||
{ name = "pillow", specifier = ">=11.3.0" },
|
||||
{ name = "playwright", specifier = ">=1.57.0" },
|
||||
{ name = "prek", specifier = ">=0.2.22" },
|
||||
@@ -1286,6 +1291,7 @@ dev = [
|
||||
{ name = "typer", specifier = ">=0.21.1" },
|
||||
{ name = "types-orjson", specifier = ">=3.6.2" },
|
||||
{ name = "types-ujson", specifier = ">=5.10.0.20240515" },
|
||||
{ name = "ujson", specifier = ">=5.8.0" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "black", specifier = ">=25.1.0" },
|
||||
@@ -1300,15 +1306,19 @@ docs = [
|
||||
{ name = "mkdocs-material", specifier = ">=9.7.0" },
|
||||
{ name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
|
||||
{ name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" },
|
||||
{ name = "orjson", specifier = ">=3.9.3" },
|
||||
{ name = "pillow", specifier = ">=11.3.0" },
|
||||
{ name = "python-slugify", specifier = ">=8.0.4" },
|
||||
{ name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
|
||||
{ name = "ruff", specifier = ">=0.14.14" },
|
||||
{ name = "typer", specifier = ">=0.21.1" },
|
||||
{ name = "ujson", specifier = ">=5.8.0" },
|
||||
]
|
||||
docs-tests = [
|
||||
{ name = "httpx", specifier = ">=0.23.0,<1.0.0" },
|
||||
{ name = "orjson", specifier = ">=3.9.3" },
|
||||
{ name = "ruff", specifier = ">=0.14.14" },
|
||||
{ name = "ujson", specifier = ">=5.8.0" },
|
||||
]
|
||||
github-actions = [
|
||||
{ name = "httpx", specifier = ">=0.27.0,<1.0.0" },
|
||||
@@ -1327,6 +1337,7 @@ tests = [
|
||||
{ name = "httpx", specifier = ">=0.23.0,<1.0.0" },
|
||||
{ name = "inline-snapshot", specifier = ">=0.21.1" },
|
||||
{ name = "mypy", specifier = ">=1.14.1" },
|
||||
{ name = "orjson", specifier = ">=3.9.3" },
|
||||
{ name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" },
|
||||
{ name = "pyjwt", specifier = ">=2.9.0" },
|
||||
{ name = "pytest", specifier = ">=9.0.0" },
|
||||
@@ -1337,6 +1348,7 @@ tests = [
|
||||
{ name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" },
|
||||
{ name = "types-orjson", specifier = ">=3.6.2" },
|
||||
{ name = "types-ujson", specifier = ">=5.10.0.20240515" },
|
||||
{ name = "ujson", specifier = ">=5.8.0" },
|
||||
]
|
||||
translations = [
|
||||
{ name = "gitpython", specifier = ">=3.1.46" },
|
||||
|
||||
Reference in New Issue
Block a user