Compare commits

..

21 Commits

Author SHA1 Message Date
Yurii Motov
d61938836b Fix coverage 2026-02-24 22:28:58 +01:00
Yurii Motov
06b05f9434 Update coverage version lower bound to 7.13 to awoid issues on Python 3.12 2026-02-24 22:16:29 +01:00
Yurii Motov
cd5f00f949 Add pytest-timeout to tests dependencies 2026-02-24 22:16:29 +01:00
Yurii Motov
88cf069a98 Add test-cov.sh 2026-02-24 22:16:29 +01:00
Yurii Motov
65462231f5 Update pytest-sugar lower version bound to 1.0.0 2026-02-24 20:26:50 +01:00
Yurii Motov
f9b4f2c4cb Update pytest-xdist lower version bound to 2.5.0 (support for --dist=loadgroup option) 2026-02-24 18:50:15 +01:00
Motov Yurii
9f5f5fd920 Merge branch 'master' into xdist 2026-02-24 18:45:10 +01:00
Yurii Motov
24b2144fa5 Fix race conditions with accessing working directory 2026-02-24 18:40:40 +01:00
Yurii Motov
e13ffa2a21 Update test.sh and test-cov-html.sh to run tests with pytest --cov 2026-02-24 18:40:40 +01:00
Yurii Motov
ef128efaed Add pytest-cov and pytest-sugar to tests dependencies 2026-02-24 18:40:40 +01:00
Yurii Motov
ec390db700 Add xdist to test dependencies 2026-02-24 18:36:36 +01:00
Sebastián Ramírez
daba0aa328 🔖 Release version 0.133.0 2026-02-24 10:51:43 +01:00
github-actions[bot]
0c3581d5c4 📝 Update release notes
[skip ci]
2026-02-24 09:50:02 +00:00
Sebastián Ramírez
c73bc94537 ⬆️ Add support for Starlette 1.0.0+ (#14987) 2026-02-24 09:49:32 +00:00
Sebastián Ramírez
6c68838615 🔖 Release version 0.132.1 2026-02-24 10:32:08 +01:00
github-actions[bot]
29d082ba24 📝 Update release notes
[skip ci]
2026-02-24 09:28:36 +00:00
Sebastián Ramírez
2686c7fbbf ♻️ Refactor logic to handle OpenAPI and Swagger UI escaping data (#14986) 2026-02-24 09:28:10 +00:00
github-actions[bot]
2f9c914d44 📝 Update release notes
[skip ci]
2026-02-23 18:48:43 +00:00
Sebastián Ramírez
0cf27ecf88 👥 Update FastAPI People - Experts (#14972)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com>
2026-02-23 19:47:59 +01:00
github-actions[bot]
3f30ca1a5e 📝 Update release notes
[skip ci]
2026-02-23 18:32:32 +00:00
Motov Yurii
6af3832126 👷 Allow skipping benchmark job in test workflow (#14974) 2026-02-23 19:31:54 +01:00
24 changed files with 825 additions and 512 deletions

View File

@@ -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 }}
@@ -208,4 +208,4 @@ jobs:
uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
allowed-skips: coverage-combine,test
allowed-skips: coverage-combine,test,benchmark

View File

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,23 @@ 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).
* 👷 Allow skipping `benchmark` job in `test` workflow. PR [#14974](https://github.com/fastapi/fastapi/pull/14974) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.132.0
### Breaking Changes

View File

@@ -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

View File

@@ -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:

View File

@@ -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 += """

View File

@@ -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",

View File

@@ -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
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
set -x
bash scripts/test.sh --cov --cov-context=test ${@}

View File

@@ -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/ ${@}

View 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"}]

View 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

View File

@@ -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")

View File

@@ -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")

View File

@@ -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():

View 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():

View 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

View 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 "/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

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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")

View File

@@ -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
View File

@@ -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"