diff --git a/README.md b/README.md index 9864fa1ef..a42cedae6 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The key features are: * **Robust**: Get production-ready code. With automatic interactive documentation. * **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. -* estimation based on tests on an internal development team, building production applications. +* estimation based on tests conducted by an internal development team, building production applications. ## Sponsors @@ -61,6 +61,7 @@ The key features are: + diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index b8cc31dbe..50b114530 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -33,6 +33,9 @@ gold: - url: https://serpapi.com/?utm_source=fastapi_website title: "SerpApi: Web Search API" img: https://fastapi.tiangolo.com/img/sponsors/serpapi.png + - url: https://www.greptile.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=fastapi_sponsor_page + title: "Greptile: The AI Code Reviewer" + img: https://fastapi.tiangolo.com/img/sponsors/greptile.png silver: - url: https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display title: Pay as you go for market data diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 14f55805c..d648be5fc 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -47,3 +47,4 @@ logins: - railwayapp - subtotal - requestly + - greptileai diff --git a/docs/en/data/topic_repos.yml b/docs/en/data/topic_repos.yml index 1bb6fd70d..cb7e3c033 100644 --- a/docs/en/data/topic_repos.yml +++ b/docs/en/data/topic_repos.yml @@ -1,368 +1,388 @@ - name: full-stack-fastapi-template html_url: https://github.com/fastapi/full-stack-fastapi-template - stars: 38779 + stars: 39475 owner_login: fastapi owner_html_url: https://github.com/fastapi - name: Hello-Python html_url: https://github.com/mouredev/Hello-Python - stars: 32726 + stars: 33090 owner_login: mouredev owner_html_url: https://github.com/mouredev - name: serve html_url: https://github.com/jina-ai/serve - stars: 21779 + stars: 21798 owner_login: jina-ai owner_html_url: https://github.com/jina-ai - name: HivisionIDPhotos html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos - stars: 20028 + stars: 20258 owner_login: Zeyi-Lin owner_html_url: https://github.com/Zeyi-Lin - name: sqlmodel html_url: https://github.com/fastapi/sqlmodel - stars: 17038 + stars: 17212 owner_login: fastapi owner_html_url: https://github.com/fastapi - name: Douyin_TikTok_Download_API html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API - stars: 14786 + stars: 15145 owner_login: Evil0ctal owner_html_url: https://github.com/Evil0ctal - name: fastapi-best-practices html_url: https://github.com/zhanymkanov/fastapi-best-practices - stars: 13968 + stars: 14644 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov - name: machine-learning-zoomcamp html_url: https://github.com/DataTalksClub/machine-learning-zoomcamp - stars: 12171 + stars: 12320 owner_login: DataTalksClub owner_html_url: https://github.com/DataTalksClub - name: fastapi_mcp html_url: https://github.com/tadata-org/fastapi_mcp - stars: 10976 + stars: 11174 owner_login: tadata-org owner_html_url: https://github.com/tadata-org -- name: awesome-fastapi - html_url: https://github.com/mjhea0/awesome-fastapi - stars: 10618 - owner_login: mjhea0 - owner_html_url: https://github.com/mjhea0 - name: SurfSense html_url: https://github.com/MODSetter/SurfSense - stars: 10243 + stars: 10858 owner_login: MODSetter owner_html_url: https://github.com/MODSetter +- name: awesome-fastapi + html_url: https://github.com/mjhea0/awesome-fastapi + stars: 10758 + owner_login: mjhea0 + owner_html_url: https://github.com/mjhea0 - name: XHS-Downloader html_url: https://github.com/JoeanAmier/XHS-Downloader - stars: 9062 + stars: 9313 owner_login: JoeanAmier owner_html_url: https://github.com/JoeanAmier - name: FastUI html_url: https://github.com/pydantic/FastUI - stars: 8892 + stars: 8915 owner_login: pydantic owner_html_url: https://github.com/pydantic - name: polar html_url: https://github.com/polarsource/polar - stars: 8084 + stars: 8339 owner_login: polarsource owner_html_url: https://github.com/polarsource - name: FileCodeBox html_url: https://github.com/vastsa/FileCodeBox - stars: 7494 + stars: 7721 owner_login: vastsa owner_html_url: https://github.com/vastsa - name: nonebot2 html_url: https://github.com/nonebot/nonebot2 - stars: 7128 + stars: 7170 owner_login: nonebot owner_html_url: https://github.com/nonebot - name: hatchet html_url: https://github.com/hatchet-dev/hatchet - stars: 6155 + stars: 6253 owner_login: hatchet-dev owner_html_url: https://github.com/hatchet-dev -- name: serge - html_url: https://github.com/serge-chat/serge - stars: 5754 - owner_login: serge-chat - owner_html_url: https://github.com/serge-chat - name: fastapi-users html_url: https://github.com/fastapi-users/fastapi-users - stars: 5683 + stars: 5849 owner_login: fastapi-users owner_html_url: https://github.com/fastapi-users +- name: serge + html_url: https://github.com/serge-chat/serge + stars: 5756 + owner_login: serge-chat + owner_html_url: https://github.com/serge-chat - name: strawberry html_url: https://github.com/strawberry-graphql/strawberry - stars: 4452 + stars: 4569 owner_login: strawberry-graphql owner_html_url: https://github.com/strawberry-graphql - name: chatgpt-web-share html_url: https://github.com/chatpire/chatgpt-web-share - stars: 4296 + stars: 4294 owner_login: chatpire owner_html_url: https://github.com/chatpire - name: poem html_url: https://github.com/poem-web/poem - stars: 4235 + stars: 4276 owner_login: poem-web owner_html_url: https://github.com/poem-web - name: dynaconf html_url: https://github.com/dynaconf/dynaconf - stars: 4174 + stars: 4202 owner_login: dynaconf owner_html_url: https://github.com/dynaconf - name: atrilabs-engine html_url: https://github.com/Atri-Labs/atrilabs-engine - stars: 4094 + stars: 4093 owner_login: Atri-Labs owner_html_url: https://github.com/Atri-Labs - name: Kokoro-FastAPI html_url: https://github.com/remsky/Kokoro-FastAPI - stars: 3875 + stars: 4019 owner_login: remsky owner_html_url: https://github.com/remsky - name: logfire html_url: https://github.com/pydantic/logfire - stars: 3717 + stars: 3805 owner_login: pydantic owner_html_url: https://github.com/pydantic - name: LitServe html_url: https://github.com/Lightning-AI/LitServe - stars: 3615 + stars: 3719 owner_login: Lightning-AI owner_html_url: https://github.com/Lightning-AI +- name: fastapi-admin + html_url: https://github.com/fastapi-admin/fastapi-admin + stars: 3632 + owner_login: fastapi-admin + owner_html_url: https://github.com/fastapi-admin - name: datamodel-code-generator html_url: https://github.com/koxudaxi/datamodel-code-generator - stars: 3554 + stars: 3609 owner_login: koxudaxi owner_html_url: https://github.com/koxudaxi - name: huma html_url: https://github.com/danielgtaylor/huma - stars: 3521 + stars: 3603 owner_login: danielgtaylor owner_html_url: https://github.com/danielgtaylor -- name: fastapi-admin - html_url: https://github.com/fastapi-admin/fastapi-admin - stars: 3497 - owner_login: fastapi-admin - owner_html_url: https://github.com/fastapi-admin - name: farfalle html_url: https://github.com/rashadphz/farfalle - stars: 3476 + stars: 3490 owner_login: rashadphz owner_html_url: https://github.com/rashadphz - name: tracecat html_url: https://github.com/TracecatHQ/tracecat - stars: 3310 + stars: 3379 owner_login: TracecatHQ owner_html_url: https://github.com/TracecatHQ - name: opyrator html_url: https://github.com/ml-tooling/opyrator - stars: 3134 + stars: 3135 owner_login: ml-tooling owner_html_url: https://github.com/ml-tooling - name: docarray html_url: https://github.com/docarray/docarray - stars: 3108 + stars: 3114 owner_login: docarray owner_html_url: https://github.com/docarray +- name: devpush + html_url: https://github.com/hunvreus/devpush + stars: 3097 + owner_login: hunvreus + owner_html_url: https://github.com/hunvreus - name: fastapi-realworld-example-app html_url: https://github.com/nsidnev/fastapi-realworld-example-app - stars: 2945 + stars: 3050 owner_login: nsidnev owner_html_url: https://github.com/nsidnev - name: uvicorn-gunicorn-fastapi-docker html_url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker - stars: 2809 + stars: 2911 owner_login: tiangolo owner_html_url: https://github.com/tiangolo -- name: devpush - html_url: https://github.com/hunvreus/devpush - stars: 2784 - owner_login: hunvreus - owner_html_url: https://github.com/hunvreus - name: mcp-context-forge html_url: https://github.com/IBM/mcp-context-forge - stars: 2763 + stars: 2899 owner_login: IBM owner_html_url: https://github.com/IBM - name: best-of-web-python html_url: https://github.com/ml-tooling/best-of-web-python - stars: 2630 + stars: 2648 owner_login: ml-tooling owner_html_url: https://github.com/ml-tooling -- name: fastapi-react - html_url: https://github.com/Buuntu/fastapi-react - stars: 2464 - owner_login: Buuntu - owner_html_url: https://github.com/Buuntu - name: FastAPI-template html_url: https://github.com/s3rius/FastAPI-template - stars: 2453 + stars: 2637 owner_login: s3rius owner_html_url: https://github.com/s3rius -- name: RasaGPT - html_url: https://github.com/paulpierre/RasaGPT - stars: 2444 - owner_login: paulpierre - owner_html_url: https://github.com/paulpierre +- name: YC-Killer + html_url: https://github.com/sahibzada-allahyar/YC-Killer + stars: 2599 + owner_login: sahibzada-allahyar + owner_html_url: https://github.com/sahibzada-allahyar +- name: fastapi-react + html_url: https://github.com/Buuntu/fastapi-react + stars: 2569 + owner_login: Buuntu + owner_html_url: https://github.com/Buuntu +- name: Yuxi-Know + html_url: https://github.com/xerrors/Yuxi-Know + stars: 2563 + owner_login: xerrors + owner_html_url: https://github.com/xerrors - name: sqladmin html_url: https://github.com/aminalaee/sqladmin - stars: 2423 + stars: 2558 owner_login: aminalaee owner_html_url: https://github.com/aminalaee -- name: nextpy - html_url: https://github.com/dot-agent/nextpy - stars: 2325 - owner_login: dot-agent - owner_html_url: https://github.com/dot-agent +- name: RasaGPT + html_url: https://github.com/paulpierre/RasaGPT + stars: 2451 + owner_login: paulpierre + owner_html_url: https://github.com/paulpierre - name: supabase-py html_url: https://github.com/supabase/supabase-py - stars: 2292 + stars: 2344 owner_login: supabase owner_html_url: https://github.com/supabase +- name: nextpy + html_url: https://github.com/dot-agent/nextpy + stars: 2335 + owner_login: dot-agent + owner_html_url: https://github.com/dot-agent +- name: fastapi-utils + html_url: https://github.com/fastapiutils/fastapi-utils + stars: 2291 + owner_login: fastapiutils + owner_html_url: https://github.com/fastapiutils - name: 30-Days-of-Python html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python - stars: 2214 + stars: 2220 owner_login: codingforentrepreneurs owner_html_url: https://github.com/codingforentrepreneurs -- name: Yuxi-Know - html_url: https://github.com/xerrors/Yuxi-Know - stars: 2212 - owner_login: xerrors - owner_html_url: https://github.com/xerrors - name: langserve html_url: https://github.com/langchain-ai/langserve - stars: 2191 + stars: 2215 owner_login: langchain-ai owner_html_url: https://github.com/langchain-ai -- name: fastapi-utils - html_url: https://github.com/fastapiutils/fastapi-utils - stars: 2185 - owner_login: fastapiutils - owner_html_url: https://github.com/fastapiutils - name: solara html_url: https://github.com/widgetti/solara - stars: 2111 + stars: 2122 owner_login: widgetti owner_html_url: https://github.com/widgetti - name: mangum html_url: https://github.com/Kludex/mangum - stars: 2011 + stars: 2029 owner_login: Kludex owner_html_url: https://github.com/Kludex - name: agentkit html_url: https://github.com/BCG-X-Official/agentkit - stars: 1826 + stars: 1912 owner_login: BCG-X-Official owner_html_url: https://github.com/BCG-X-Official -- name: python-week-2022 - html_url: https://github.com/rochacbruno/python-week-2022 - stars: 1815 - owner_login: rochacbruno - owner_html_url: https://github.com/rochacbruno - name: manage-fastapi html_url: https://github.com/ycd/manage-fastapi - stars: 1787 + stars: 1885 owner_login: ycd owner_html_url: https://github.com/ycd -- name: ormar - html_url: https://github.com/collerek/ormar - stars: 1780 - owner_login: collerek - owner_html_url: https://github.com/collerek -- name: vue-fastapi-admin - html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin - stars: 1758 - owner_login: mizhexiaoxiao - owner_html_url: https://github.com/mizhexiaoxiao - name: openapi-python-client html_url: https://github.com/openapi-generators/openapi-python-client - stars: 1731 + stars: 1862 owner_login: openapi-generators owner_html_url: https://github.com/openapi-generators - name: piccolo html_url: https://github.com/piccolo-orm/piccolo - stars: 1711 + stars: 1836 owner_login: piccolo-orm owner_html_url: https://github.com/piccolo-orm -- name: fastapi-cache - html_url: https://github.com/long2ice/fastapi-cache - stars: 1677 - owner_login: long2ice - owner_html_url: https://github.com/long2ice +- name: vue-fastapi-admin + html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin + stars: 1831 + owner_login: mizhexiaoxiao + owner_html_url: https://github.com/mizhexiaoxiao +- name: python-week-2022 + html_url: https://github.com/rochacbruno/python-week-2022 + stars: 1817 + owner_login: rochacbruno + owner_html_url: https://github.com/rochacbruno - name: slowapi html_url: https://github.com/laurentS/slowapi - stars: 1669 + stars: 1798 owner_login: laurentS owner_html_url: https://github.com/laurentS -- name: langchain-serve - html_url: https://github.com/jina-ai/langchain-serve - stars: 1632 - owner_login: jina-ai - owner_html_url: https://github.com/jina-ai +- name: fastapi-cache + html_url: https://github.com/long2ice/fastapi-cache + stars: 1789 + owner_login: long2ice + owner_html_url: https://github.com/long2ice +- name: ormar + html_url: https://github.com/collerek/ormar + stars: 1783 + owner_login: collerek + owner_html_url: https://github.com/collerek - name: termpair html_url: https://github.com/cs01/termpair - stars: 1621 + stars: 1716 owner_login: cs01 owner_html_url: https://github.com/cs01 - name: FastAPI-boilerplate html_url: https://github.com/benavlabs/FastAPI-boilerplate - stars: 1596 + stars: 1660 owner_login: benavlabs owner_html_url: https://github.com/benavlabs -- name: coronavirus-tracker-api - html_url: https://github.com/ExpDev07/coronavirus-tracker-api - stars: 1573 - owner_login: ExpDev07 - owner_html_url: https://github.com/ExpDev07 -- name: fastapi-crudrouter - html_url: https://github.com/awtkns/fastapi-crudrouter - stars: 1553 - owner_login: awtkns - owner_html_url: https://github.com/awtkns +- name: fastapi-langgraph-agent-production-ready-template + html_url: https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template + stars: 1638 + owner_login: wassim249 + owner_html_url: https://github.com/wassim249 +- name: langchain-serve + html_url: https://github.com/jina-ai/langchain-serve + stars: 1635 + owner_login: jina-ai + owner_html_url: https://github.com/jina-ai - name: awesome-fastapi-projects html_url: https://github.com/Kludex/awesome-fastapi-projects - stars: 1485 + stars: 1589 owner_login: Kludex owner_html_url: https://github.com/Kludex - name: fastapi-pagination html_url: https://github.com/uriyyo/fastapi-pagination - stars: 1473 + stars: 1585 owner_login: uriyyo owner_html_url: https://github.com/uriyyo +- name: coronavirus-tracker-api + html_url: https://github.com/ExpDev07/coronavirus-tracker-api + stars: 1574 + owner_login: ExpDev07 + owner_html_url: https://github.com/ExpDev07 +- name: fastapi-crudrouter + html_url: https://github.com/awtkns/fastapi-crudrouter + stars: 1559 + owner_login: awtkns + owner_html_url: https://github.com/awtkns - name: bracket html_url: https://github.com/evroon/bracket - stars: 1470 + stars: 1489 owner_login: evroon owner_html_url: https://github.com/evroon -- name: fastapi-langgraph-agent-production-ready-template - html_url: https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template - stars: 1456 - owner_login: wassim249 - owner_html_url: https://github.com/wassim249 +- name: fastapi-amis-admin + html_url: https://github.com/amisadmin/fastapi-amis-admin + stars: 1475 + owner_login: amisadmin + owner_html_url: https://github.com/amisadmin - name: fastapi-boilerplate html_url: https://github.com/teamhide/fastapi-boilerplate - stars: 1424 + stars: 1436 owner_login: teamhide owner_html_url: https://github.com/teamhide - name: awesome-python-resources html_url: https://github.com/DjangoEx/awesome-python-resources - stars: 1420 + stars: 1426 owner_login: DjangoEx owner_html_url: https://github.com/DjangoEx -- name: fastapi-amis-admin - html_url: https://github.com/amisadmin/fastapi-amis-admin - stars: 1363 - owner_login: amisadmin - owner_html_url: https://github.com/amisadmin - name: fastcrud html_url: https://github.com/benavlabs/fastcrud - stars: 1362 + stars: 1414 owner_login: benavlabs owner_html_url: https://github.com/benavlabs +- name: prometheus-fastapi-instrumentator + html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator + stars: 1388 + owner_login: trallnag + owner_html_url: https://github.com/trallnag +- name: fastapi_best_architecture + html_url: https://github.com/fastapi-practices/fastapi_best_architecture + stars: 1378 + owner_login: fastapi-practices + owner_html_url: https://github.com/fastapi-practices +- name: fastapi-code-generator + html_url: https://github.com/koxudaxi/fastapi-code-generator + stars: 1375 + owner_login: koxudaxi + owner_html_url: https://github.com/koxudaxi - name: budgetml html_url: https://github.com/ebhy/budgetml stars: 1345 @@ -370,126 +390,106 @@ owner_html_url: https://github.com/ebhy - name: fastapi-tutorial html_url: https://github.com/liaogx/fastapi-tutorial - stars: 1315 + stars: 1327 owner_login: liaogx owner_html_url: https://github.com/liaogx -- name: fastapi_best_architecture - html_url: https://github.com/fastapi-practices/fastapi_best_architecture - stars: 1311 - owner_login: fastapi-practices - owner_html_url: https://github.com/fastapi-practices -- name: fastapi-code-generator - html_url: https://github.com/koxudaxi/fastapi-code-generator - stars: 1270 - owner_login: koxudaxi - owner_html_url: https://github.com/koxudaxi -- name: prometheus-fastapi-instrumentator - html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator - stars: 1264 - owner_login: trallnag - owner_html_url: https://github.com/trallnag +- name: fastapi-alembic-sqlmodel-async + html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async + stars: 1259 + owner_login: jonra1993 + owner_html_url: https://github.com/jonra1993 +- name: fastapi-scaff + html_url: https://github.com/atpuxiner/fastapi-scaff + stars: 1255 + owner_login: atpuxiner + owner_html_url: https://github.com/atpuxiner - name: bedrock-chat html_url: https://github.com/aws-samples/bedrock-chat - stars: 1243 + stars: 1254 owner_login: aws-samples owner_html_url: https://github.com/aws-samples - name: bolt-python html_url: https://github.com/slackapi/bolt-python - stars: 1238 + stars: 1253 owner_login: slackapi owner_html_url: https://github.com/slackapi - name: fastapi_production_template html_url: https://github.com/zhanymkanov/fastapi_production_template - stars: 1209 + stars: 1217 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov -- name: fastapi-scaff - html_url: https://github.com/atpuxiner/fastapi-scaff - stars: 1200 - owner_login: atpuxiner - owner_html_url: https://github.com/atpuxiner - name: langchain-extract html_url: https://github.com/langchain-ai/langchain-extract - stars: 1173 + stars: 1176 owner_login: langchain-ai owner_html_url: https://github.com/langchain-ai -- name: fastapi-alembic-sqlmodel-async - html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async - stars: 1162 - owner_login: jonra1993 - owner_html_url: https://github.com/jonra1993 -- name: odmantic - html_url: https://github.com/art049/odmantic - stars: 1137 - owner_login: art049 - owner_html_url: https://github.com/art049 - name: restish html_url: https://github.com/rest-sh/restish - stars: 1129 + stars: 1140 owner_login: rest-sh owner_html_url: https://github.com/rest-sh -- name: kubetorch - html_url: https://github.com/run-house/kubetorch - stars: 1065 - owner_login: run-house - owner_html_url: https://github.com/run-house -- name: flock - html_url: https://github.com/Onelevenvy/flock - stars: 1039 - owner_login: Onelevenvy - owner_html_url: https://github.com/Onelevenvy +- name: odmantic + html_url: https://github.com/art049/odmantic + stars: 1138 + owner_login: art049 + owner_html_url: https://github.com/art049 - name: authx html_url: https://github.com/yezz123/authx - stars: 1017 + stars: 1119 owner_login: yezz123 owner_html_url: https://github.com/yezz123 -- name: autollm - html_url: https://github.com/viddexa/autollm - stars: 997 - owner_login: viddexa - owner_html_url: https://github.com/viddexa -- name: lanarky - html_url: https://github.com/ajndkr/lanarky - stars: 993 - owner_login: ajndkr - owner_html_url: https://github.com/ajndkr -- name: RuoYi-Vue3-FastAPI - html_url: https://github.com/insistence/RuoYi-Vue3-FastAPI - stars: 974 - owner_login: insistence - owner_html_url: https://github.com/insistence +- name: NoteDiscovery + html_url: https://github.com/gamosoft/NoteDiscovery + stars: 1107 + owner_login: gamosoft + owner_html_url: https://github.com/gamosoft +- name: flock + html_url: https://github.com/Onelevenvy/flock + stars: 1055 + owner_login: Onelevenvy + owner_html_url: https://github.com/Onelevenvy +- name: fastapi-observability + html_url: https://github.com/blueswen/fastapi-observability + stars: 1038 + owner_login: blueswen + owner_html_url: https://github.com/blueswen - name: aktools html_url: https://github.com/akfamily/aktools - stars: 972 + stars: 1027 owner_login: akfamily owner_html_url: https://github.com/akfamily +- name: RuoYi-Vue3-FastAPI + html_url: https://github.com/insistence/RuoYi-Vue3-FastAPI + stars: 1016 + owner_login: insistence + owner_html_url: https://github.com/insistence +- name: autollm + html_url: https://github.com/viddexa/autollm + stars: 1002 + owner_login: viddexa + owner_html_url: https://github.com/viddexa - name: titiler html_url: https://github.com/developmentseed/titiler - stars: 965 + stars: 999 owner_login: developmentseed owner_html_url: https://github.com/developmentseed -- name: secure - html_url: https://github.com/TypeError/secure - stars: 953 - owner_login: TypeError - owner_html_url: https://github.com/TypeError -- name: energy-forecasting - html_url: https://github.com/iusztinpaul/energy-forecasting - stars: 949 - owner_login: iusztinpaul - owner_html_url: https://github.com/iusztinpaul +- name: lanarky + html_url: https://github.com/ajndkr/lanarky + stars: 994 + owner_login: ajndkr + owner_html_url: https://github.com/ajndkr - name: every-pdf html_url: https://github.com/DDULDDUCK/every-pdf - stars: 942 + stars: 985 owner_login: DDULDDUCK owner_html_url: https://github.com/DDULDDUCK -- name: langcorn - html_url: https://github.com/msoedov/langcorn - stars: 933 - owner_login: msoedov - owner_html_url: https://github.com/msoedov -- name: fastapi-observability - html_url: https://github.com/blueswen/fastapi-observability - stars: 923 - owner_login: blueswen - owner_html_url: https://github.com/blueswen +- name: enterprise-deep-research + html_url: https://github.com/SalesforceAIResearch/enterprise-deep-research + stars: 973 + owner_login: SalesforceAIResearch + owner_html_url: https://github.com/SalesforceAIResearch +- name: fastapi-mail + html_url: https://github.com/sabuhish/fastapi-mail + stars: 964 + owner_login: sabuhish + owner_html_url: https://github.com/sabuhish diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index f692a28e8..f4dbd4560 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -443,6 +443,14 @@ The docs UI will interact with the server that you select. /// +/// note | Technical Details + +The `servers` property in the OpenAPI specification is optional. + +If you don't specify the `servers` parameter and `root_path` is equal to `/`, the `servers` property in the generated OpenAPI schema will be omitted entirely by default, which is the equivalent of a single server with a `url` value of `/`. + +/// + ### Disable automatic server from `root_path` { #disable-automatic-server-from-root-path } If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`: diff --git a/docs/en/docs/img/sponsors/greptile-banner.png b/docs/en/docs/img/sponsors/greptile-banner.png new file mode 100644 index 000000000..e0909b39d Binary files /dev/null and b/docs/en/docs/img/sponsors/greptile-banner.png differ diff --git a/docs/en/docs/img/sponsors/greptile.png b/docs/en/docs/img/sponsors/greptile.png new file mode 100644 index 000000000..ae3d78cbd Binary files /dev/null and b/docs/en/docs/img/sponsors/greptile.png differ diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index 8a79b26a6..df03b7675 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -46,7 +46,7 @@ The key features are: * **Robust**: Get production-ready code. With automatic interactive documentation. * **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. -* estimation based on tests on an internal development team, building production applications. +* estimation based on tests conducted by an internal development team, building production applications. ## Sponsors { #sponsors } diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 93a4c5c97..74c7eb9ea 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,8 +7,50 @@ hide: ## Latest Changes +## 0.123.4 + +### Fixes + +* 🐛 Fix OpenAPI schema support for computed fields when using `separate_input_output_schemas=False`. PR [#13207](https://github.com/fastapi/fastapi/pull/13207) by [@vgrafe](https://github.com/vgrafe). + +### Docs + +* 📝 Fix docstring of `servers` parameter. PR [#14405](https://github.com/fastapi/fastapi/pull/14405) by [@YuriiMotov](https://github.com/YuriiMotov). + +## 0.123.3 + +### Fixes + +* 🐛 Fix Query\Header\Cookie parameter model alias. PR [#14360](https://github.com/fastapi/fastapi/pull/14360) by [@YuriiMotov](https://github.com/YuriiMotov). +* 🐛 Fix optional sequence handling in `serialize sequence value` with Pydantic V2. PR [#14297](https://github.com/fastapi/fastapi/pull/14297) by [@YuriiMotov](https://github.com/YuriiMotov). + +## 0.123.2 + +### Fixes + +* 🐛 Fix unformatted `{type_}` in FastAPIError. PR [#14416](https://github.com/fastapi/fastapi/pull/14416) by [@Just-Helpful](https://github.com/Just-Helpful). +* 🐛 Fix parsing extra non-body parameter list. PR [#14356](https://github.com/fastapi/fastapi/pull/14356) by [@YuriiMotov](https://github.com/YuriiMotov). +* 🐛 Fix parsing extra `Form` parameter list. PR [#14303](https://github.com/fastapi/fastapi/pull/14303) by [@YuriiMotov](https://github.com/YuriiMotov). +* 🐛 Fix support for form values with empty strings interpreted as missing (`None` if that's the default), for compatibility with HTML forms. PR [#13537](https://github.com/fastapi/fastapi/pull/13537) by [@MarinPostma](https://github.com/MarinPostma). + +### Docs + +* 📝 Add tip on how to install `pip` in case of `No module named pip` error in `virtual-environments.md`. PR [#14211](https://github.com/fastapi/fastapi/pull/14211) by [@zadevhub](https://github.com/zadevhub). +* 📝 Update Primary Key notes for the SQL databases tutorial to avoid confusion. PR [#14120](https://github.com/fastapi/fastapi/pull/14120) by [@FlaviusRaducu](https://github.com/FlaviusRaducu). +* 📝 Clarify estimation note in documentation. PR [#14070](https://github.com/fastapi/fastapi/pull/14070) by [@SaisakthiM](https://github.com/SaisakthiM). + +## 0.123.1 + +### Fixes + +* 🐛 Avoid accessing non-existing "$ref" key for Pydantic v2 compat remapping. PR [#14361](https://github.com/fastapi/fastapi/pull/14361) by [@svlandeg](https://github.com/svlandeg). +* 🐛 Fix `TypeError` when encoding a decimal with a `NaN` or `Infinity` value. PR [#12935](https://github.com/fastapi/fastapi/pull/12935) by [@kentwelcome](https://github.com/kentwelcome). + ### Internal +* 🐛 Fix Windows UnicodeEncodeError in CLI test. PR [#14295](https://github.com/fastapi/fastapi/pull/14295) by [@hemanth-thirthahalli](https://github.com/hemanth-thirthahalli). +* 🔧 Update sponsors: add Greptile. PR [#14429](https://github.com/fastapi/fastapi/pull/14429) by [@tiangolo](https://github.com/tiangolo). +* 👥 Update FastAPI GitHub topic repositories. PR [#14426](https://github.com/fastapi/fastapi/pull/14426) by [@tiangolo](https://github.com/tiangolo). * ⬆ Bump markdown-include-variants from 0.0.6 to 0.0.7. PR [#14423](https://github.com/fastapi/fastapi/pull/14423) by [@YuriiMotov](https://github.com/YuriiMotov). * 👥 Update FastAPI People - Sponsors. PR [#14422](https://github.com/fastapi/fastapi/pull/14422) by [@tiangolo](https://github.com/tiangolo). * 👥 Update FastAPI People - Contributors and Translators. PR [#14420](https://github.com/fastapi/fastapi/pull/14420) by [@tiangolo](https://github.com/tiangolo). diff --git a/docs/en/docs/tutorial/sql-databases.md b/docs/en/docs/tutorial/sql-databases.md index cfa1c9073..b42e9ba2f 100644 --- a/docs/en/docs/tutorial/sql-databases.md +++ b/docs/en/docs/tutorial/sql-databases.md @@ -65,7 +65,7 @@ There are a few differences: * `Field(primary_key=True)` tells SQLModel that the `id` is the **primary key** in the SQL database (you can learn more about SQL primary keys in the SQLModel docs). - By having the type as `int | None`, SQLModel will know that this column should be an `INTEGER` in the SQL database and that it should be `NULLABLE`. + **Note:** We use `int | None` for the primary key field so that in Python code we can *create an object without an `id`* (`id=None`), assuming the database will *generate it when saving*. SQLModel understands that the database will provide the `id` and *defines the column as a non-null `INTEGER`* in the database schema. See SQLModel docs on primary keys for details. * `Field(index=True)` tells SQLModel that it should create a **SQL index** for this column, that would allow faster lookups in the database when reading data filtered by this column. diff --git a/docs/en/docs/virtual-environments.md b/docs/en/docs/virtual-environments.md index e9b0a9fc4..c02e43ab9 100644 --- a/docs/en/docs/virtual-environments.md +++ b/docs/en/docs/virtual-environments.md @@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip +/// tip + +Sometimes, you might get a **`No module named pip`** error when trying to upgrade pip. + +If this happens, install and upgrade pip using the command below: + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +This command will install pip if it is not already installed and also ensures that the installed version of pip is at least as recent as the one available in `ensurepip`. + +/// + ## Add `.gitignore` { #add-gitignore } If you are using **Git** (you should), add a `.gitignore` file to exclude everything in your `.venv` from Git. diff --git a/docs/en/overrides/main.html b/docs/en/overrides/main.html index 01d39817b..a37ebf0a4 100644 --- a/docs/en/overrides/main.html +++ b/docs/en/overrides/main.html @@ -93,6 +93,12 @@ +
+ + + + +
{% endblock %} diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 25ed2bbeb..b1d2dcecc 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.0" +__version__ = "0.123.4" from starlette import status as status diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 5cd49343b..543a42dda 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -180,8 +180,13 @@ def get_schema_from_model_field( ], separate_input_output_schemas: bool = True, ) -> Dict[str, Any]: + computed_fields = field._type_adapter.core_schema.get("schema", {}).get( + "computed_fields", [] + ) override_mode: Union[Literal["validation"], None] = ( - None if separate_input_output_schemas else "validation" + None + if (separate_input_output_schemas or len(computed_fields) > 0) + else "validation" ) # This expects that GenerateJsonSchema was already used to generate the definitions json_schema = field_mapping[(field, override_mode or field.mode)] @@ -203,9 +208,14 @@ def get_definitions( Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], Dict[str, Dict[str, Any]], ]: + has_computed_fields: bool = any( + field._type_adapter.core_schema.get("schema", {}).get("computed_fields", []) + for field in fields + ) + schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) override_mode: Union[Literal["validation"], None] = ( - None if separate_input_output_schemas else "validation" + None if (separate_input_output_schemas or has_computed_fields) else "validation" ) validation_fields = [field for field in fields if field.mode == "validation"] serialization_fields = [field for field in fields if field.mode == "serialization"] @@ -304,7 +314,7 @@ def _remap_definitions_and_field_mappings( old_name_to_new_name_map = {} for field_key, schema in field_mapping.items(): model = field_key[0].type_ - if model not in model_name_map: + if model not in model_name_map or "$ref" not in schema: continue new_name = model_name_map[model] old_name = schema["$ref"].split("/")[-1] @@ -371,6 +381,13 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation + if origin_type is Union: # Handle optional sequences + union_args = get_args(field.field_info.annotation) + for union_arg in union_args: + if union_arg is type(None): + continue + origin_type = get_origin(union_arg) or union_arg + break assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type] return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return] diff --git a/fastapi/applications.py b/fastapi/applications.py index 0a47699ae..02193312b 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -301,7 +301,12 @@ class FastAPI(Starlette): browser tabs open). Or if you want to leave fixed the possible URLs. If the servers `list` is not provided, or is an empty `list`, the - default value would be a `dict` with a `url` value of `/`. + `servers` property in the generated OpenAPI will be: + + * a `dict` with a `url` value of the application's mounting point + (`root_path`) if it's different from `/`. + * otherwise, the `servers` property will be omitted from the OpenAPI + schema. Each item in the `list` is a `dict` containing: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index d43fa8a51..ef3f56417 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -787,13 +787,19 @@ def request_params_to_args( ) value = _get_multidict_value(field, received_params, alias=alias) if value is not None: - params_to_process[field.name] = value + params_to_process[field.alias] = value processed_keys.add(alias or field.alias) - processed_keys.add(field.name) - for key, value in received_params.items(): + for key in received_params.keys(): if key not in processed_keys: - params_to_process[key] = value + if hasattr(received_params, "getlist"): + value = received_params.getlist(key) + if isinstance(value, list) and (len(value) == 1): + params_to_process[key] = value[0] + else: + params_to_process[key] = value + else: + params_to_process[key] = received_params.get(key) if single_not_embedded_field: field_info = first_field.field_info @@ -902,9 +908,14 @@ async def _extract_form_body( value = serialize_sequence_value(field=field, value=results) if value is not None: values[field.alias] = value - for key, value in received_body.items(): - if key not in values: - values[key] = value + field_aliases = {field.alias for field in body_fields} + for key in received_body.keys(): + if key not in field_aliases: + param_values = received_body.getlist(key) + if len(param_values) == 1: + values[key] = param_values[0] + else: + values[key] = param_values return values diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 6fc6228e1..793951089 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -34,14 +34,14 @@ def isoformat(o: Union[datetime.date, datetime.time]) -> str: return o.isoformat() -# Taken from Pydantic v1 as is +# Adapted from Pydantic v1 # TODO: pv2 should this return strings instead? def decimal_encoder(dec_value: Decimal) -> Union[int, float]: """ - Encodes a Decimal as int of there's no exponent, otherwise float + Encodes a Decimal as int if there's no exponent, otherwise float This is useful when we use ConstrainedDecimal to represent Numeric(x,0) - where a integer (but not int typed) is used. Encoding this as a float + where an integer (but not int typed) is used. Encoding this as a float results in failed round-tripping between encode and parse. Our Id type is a prime example of this. @@ -50,8 +50,12 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: >>> decimal_encoder(Decimal("1")) 1 + + >>> decimal_encoder(Decimal("NaN")) + nan """ - if dec_value.as_tuple().exponent >= 0: # type: ignore[operator] + exponent = dec_value.as_tuple().exponent + if isinstance(exponent, int) and exponent >= 0: return int(dec_value) else: return float(dec_value) diff --git a/fastapi/utils.py b/fastapi/utils.py index 2e79ee6b1..b3b89ed2b 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -110,7 +110,9 @@ def create_model_field( try: return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] except RuntimeError: - raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None + raise fastapi.exceptions.FastAPIError( + _invalid_args_message.format(type_=type_) + ) from None elif PYDANTIC_V2: from ._compat import v2 @@ -121,7 +123,9 @@ def create_model_field( try: return v2.ModelField(**kwargs) # type: ignore[return-value,arg-type] except PydanticSchemaGenerationError: - raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None + raise fastapi.exceptions.FastAPIError( + _invalid_args_message.format(type_=type_) + ) from None # Pydantic v2 is not installed, but it's not a Pydantic v1 ModelField, it could be # a Pydantic v1 type, like a constrained int from fastapi._compat import v1 @@ -129,7 +133,9 @@ def create_model_field( try: return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] except RuntimeError: - raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None + raise fastapi.exceptions.FastAPIError( + _invalid_args_message.format(type_=type_) + ) from None def create_cloned_field( diff --git a/tests/test_compat.py b/tests/test_compat.py index 0184c9a2e..c3a97209a 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -136,6 +136,30 @@ def test_is_uploadfile_sequence_annotation(): assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]]) +@needs_pydanticv2 +def test_serialize_sequence_value_with_optional_list(): + """Test that serialize_sequence_value handles optional lists correctly.""" + from fastapi._compat import v2 + + field_info = FieldInfo(annotation=Union[List[str], None]) + field = v2.ModelField(name="items", field_info=field_info) + result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"]) + assert result == ["a", "b", "c"] + assert isinstance(result, list) + + +@needs_pydanticv2 +def test_serialize_sequence_value_with_none_first_in_union(): + """Test that serialize_sequence_value handles Union[None, List[...]] correctly.""" + from fastapi._compat import v2 + + field_info = FieldInfo(annotation=Union[None, List[str]]) + field = v2.ModelField(name="items", field_info=field_info) + result = v2.serialize_sequence_value(field=field, value=["x", "y"]) + assert result == ["x", "y"] + assert isinstance(result, list) + + @needs_py_lt_314 def test_is_pv1_scalar_field(): from fastapi._compat import v1 diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index a1b412168..f2e42999b 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -6,8 +6,9 @@ from .utils import needs_pydanticv2 @pytest.fixture(name="client") -def get_client(): - app = FastAPI() +def get_client(request): + separate_input_output_schemas = request.param + app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) from pydantic import BaseModel, computed_field @@ -32,6 +33,7 @@ def get_client(): return client +@pytest.mark.parametrize("client", [True, False], indirect=True) @pytest.mark.parametrize("path", ["/", "/responses"]) @needs_pydanticv2 def test_get(client: TestClient, path: str): @@ -40,6 +42,7 @@ def test_get(client: TestClient, path: str): assert response.json() == {"width": 3, "length": 4, "area": 12} +@pytest.mark.parametrize("client", [True, False], indirect=True) @needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") diff --git a/tests/test_fastapi_cli.py b/tests/test_fastapi_cli.py index a5c10778a..78a49a1fb 100644 --- a/tests/test_fastapi_cli.py +++ b/tests/test_fastapi_cli.py @@ -1,3 +1,4 @@ +import os import subprocess import sys from unittest.mock import patch @@ -20,6 +21,7 @@ def test_fastapi_cli(): ], capture_output=True, encoding="utf-8", + env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) assert result.returncode == 1, result.stdout assert "Path does not exist non_existent_file.py" in result.stdout diff --git a/tests/test_form_default.py b/tests/test_form_default.py new file mode 100644 index 000000000..2a12049d1 --- /dev/null +++ b/tests/test_form_default.py @@ -0,0 +1,35 @@ +from typing import Optional + +from fastapi import FastAPI, File, Form +from starlette.testclient import TestClient +from typing_extensions import Annotated + +app = FastAPI() + + +@app.post("/urlencoded") +async def post_url_encoded(age: Annotated[Optional[int], Form()] = None): + return age + + +@app.post("/multipart") +async def post_multi_part( + age: Annotated[Optional[int], Form()] = None, + file: Annotated[Optional[bytes], File()] = None, +): + return {"file": file, "age": age} + + +client = TestClient(app) + + +def test_form_default_url_encoded(): + response = client.post("/urlencoded", data={"age": ""}) + assert response.status_code == 200 + assert response.text == "null" + + +def test_form_default_multi_part(): + response = client.post("/multipart", data={"age": ""}) + assert response.status_code == 200 + assert response.json() == {"file": None, "age": None} diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py index 880ab3820..1db63f021 100644 --- a/tests/test_forms_single_model.py +++ b/tests/test_forms_single_model.py @@ -2,6 +2,7 @@ from typing import List, Optional from dirty_equals import IsDict from fastapi import FastAPI, Form +from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -17,11 +18,27 @@ class FormModel(BaseModel): alias_with: str = Field(alias="with", default="nothing") +class FormModelExtraAllow(BaseModel): + param: str + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + else: + + class Config: + extra = "allow" + + @app.post("/form/") def post_form(user: Annotated[FormModel, Form()]): return user +@app.post("/form-extra-allow/") +def post_form_extra_allow(params: Annotated[FormModelExtraAllow, Form()]): + return params + + client = TestClient(app) @@ -131,3 +148,33 @@ def test_no_data(): ] } ) + + +def test_extra_param_single(): + response = client.post( + "/form-extra-allow/", + data={ + "param": "123", + "extra_param": "456", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "param": "123", + "extra_param": "456", + } + + +def test_extra_param_list(): + response = client.post( + "/form-extra-allow/", + data={ + "param": "123", + "extra_params": ["456", "789"], + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "param": "123", + "extra_params": ["456", "789"], + } diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 447c5b4d6..3b6513e27 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from datetime import datetime, timezone from decimal import Decimal from enum import Enum +from math import isinf, isnan from pathlib import PurePath, PurePosixPath, PureWindowsPath from typing import Optional @@ -306,6 +307,20 @@ def test_decimal_encoder_int(): assert jsonable_encoder(data) == {"value": 2} +@needs_pydanticv2 +def test_decimal_encoder_nan(): + data = {"value": Decimal("NaN")} + assert isnan(jsonable_encoder(data)["value"]) + + +@needs_pydanticv2 +def test_decimal_encoder_infinity(): + data = {"value": Decimal("Infinity")} + assert isinf(jsonable_encoder(data)["value"]) + data = {"value": Decimal("-Infinity")} + assert isinf(jsonable_encoder(data)["value"]) + + def test_encode_deque_encodes_child_models(): class Model(BaseModel): test: str diff --git a/tests/test_optional_file_list.py b/tests/test_optional_file_list.py new file mode 100644 index 000000000..0228900cf --- /dev/null +++ b/tests/test_optional_file_list.py @@ -0,0 +1,30 @@ +from typing import List, Optional + +from fastapi import FastAPI, File +from fastapi.testclient import TestClient + +app = FastAPI() + + +@app.post("/files") +async def upload_files(files: Optional[List[bytes]] = File(None)): + if files is None: + return {"files_count": 0} + return {"files_count": len(files), "sizes": [len(f) for f in files]} + + +def test_optional_bytes_list(): + client = TestClient(app) + response = client.post( + "/files", + files=[("files", b"content1"), ("files", b"content2")], + ) + assert response.status_code == 200 + assert response.json() == {"files_count": 2, "sizes": [8, 8]} + + +def test_optional_bytes_list_no_files(): + client = TestClient(app) + response = client.post("/files") + assert response.status_code == 200 + assert response.json() == {"files_count": 0} diff --git a/tests/test_query_cookie_header_model_extra_params.py b/tests/test_query_cookie_header_model_extra_params.py new file mode 100644 index 000000000..f4ebefb3f --- /dev/null +++ b/tests/test_query_cookie_header_model_extra_params.py @@ -0,0 +1,111 @@ +from fastapi import Cookie, FastAPI, Header, Query +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel + +app = FastAPI() + + +class Model(BaseModel): + param: str + + if PYDANTIC_V2: + model_config = {"extra": "allow"} + else: + + class Config: + extra = "allow" + + +@app.get("/query") +async def query_model_with_extra(data: Model = Query()): + return data + + +@app.get("/header") +async def header_model_with_extra(data: Model = Header()): + return data + + +@app.get("/cookie") +async def cookies_model_with_extra(data: Model = Cookie()): + return data + + +def test_query_pass_extra_list(): + client = TestClient(app) + resp = client.get( + "/query", + params={ + "param": "123", + "param2": ["456", "789"], # Pass a list of values as extra parameter + }, + ) + assert resp.status_code == 200 + assert resp.json() == { + "param": "123", + "param2": ["456", "789"], + } + + +def test_query_pass_extra_single(): + client = TestClient(app) + resp = client.get( + "/query", + params={ + "param": "123", + "param2": "456", + }, + ) + assert resp.status_code == 200 + assert resp.json() == { + "param": "123", + "param2": "456", + } + + +def test_header_pass_extra_list(): + client = TestClient(app) + + resp = client.get( + "/header", + headers=[ + ("param", "123"), + ("param2", "456"), # Pass a list of values as extra parameter + ("param2", "789"), + ], + ) + assert resp.status_code == 200 + resp_json = resp.json() + assert "param2" in resp_json + assert resp_json["param2"] == ["456", "789"] + + +def test_header_pass_extra_single(): + client = TestClient(app) + + resp = client.get( + "/header", + headers=[ + ("param", "123"), + ("param2", "456"), + ], + ) + assert resp.status_code == 200 + resp_json = resp.json() + assert "param2" in resp_json + assert resp_json["param2"] == "456" + + +def test_cookie_pass_extra_list(): + client = TestClient(app) + client.cookies = [ + ("param", "123"), + ("param2", "456"), # Pass a list of values as extra parameter + ("param2", "789"), + ] + resp = client.get("/cookie") + assert resp.status_code == 200 + resp_json = resp.json() + assert "param2" in resp_json + assert resp_json["param2"] == "789" # Cookies only keep the last value diff --git a/tests/test_request_param_model_by_alias.py b/tests/test_request_param_model_by_alias.py new file mode 100644 index 000000000..a6f759f23 --- /dev/null +++ b/tests/test_request_param_model_by_alias.py @@ -0,0 +1,76 @@ +from dirty_equals import IsPartialDict +from fastapi import Cookie, FastAPI, Header, Query +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field + +app = FastAPI() + + +class Model(BaseModel): + param: str = Field(alias="param_alias") + + +@app.get("/query") +async def query_model(data: Model = Query()): + return {"param": data.param} + + +@app.get("/header") +async def header_model(data: Model = Header()): + return {"param": data.param} + + +@app.get("/cookie") +async def cookie_model(data: Model = Cookie()): + return {"param": data.param} + + +def test_query_model_with_alias(): + client = TestClient(app) + response = client.get("/query", params={"param_alias": "value"}) + assert response.status_code == 200, response.text + assert response.json() == {"param": "value"} + + +def test_header_model_with_alias(): + client = TestClient(app) + response = client.get("/header", headers={"param_alias": "value"}) + assert response.status_code == 200, response.text + assert response.json() == {"param": "value"} + + +def test_cookie_model_with_alias(): + client = TestClient(app) + client.cookies.set("param_alias", "value") + response = client.get("/cookie") + assert response.status_code == 200, response.text + assert response.json() == {"param": "value"} + + +def test_query_model_with_alias_by_name(): + client = TestClient(app) + response = client.get("/query", params={"param": "value"}) + assert response.status_code == 422, response.text + details = response.json() + if PYDANTIC_V2: + assert details["detail"][0]["input"] == {"param": "value"} + + +def test_header_model_with_alias_by_name(): + client = TestClient(app) + response = client.get("/header", headers={"param": "value"}) + assert response.status_code == 422, response.text + details = response.json() + if PYDANTIC_V2: + assert details["detail"][0]["input"] == IsPartialDict({"param": "value"}) + + +def test_cookie_model_with_alias_by_name(): + client = TestClient(app) + client.cookies.set("param", "value") + response = client.get("/cookie") + assert response.status_code == 422, response.text + details = response.json() + if PYDANTIC_V2: + assert details["detail"][0]["input"] == {"param": "value"} diff --git a/tests/test_schema_compat_pydantic_v2.py b/tests/test_schema_compat_pydantic_v2.py new file mode 100644 index 000000000..39626c0ec --- /dev/null +++ b/tests/test_schema_compat_pydantic_v2.py @@ -0,0 +1,92 @@ +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from pydantic import BaseModel + +from tests.utils import needs_py310, needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client(): + from enum import Enum + + app = FastAPI() + + class PlatformRole(str, Enum): + admin = "admin" + user = "user" + + class OtherRole(str, Enum): ... + + class User(BaseModel): + username: str + role: PlatformRole | OtherRole + + @app.get("/users") + async def get_user() -> User: + return {"username": "alice", "role": "admin"} + + client = TestClient(app) + return client + + +@needs_py310 +@needs_pydanticv2 +def test_get(client: TestClient): + response = client.get("/users") + assert response.json() == {"username": "alice", "role": "admin"} + + +@needs_py310 +@needs_pydanticv2 +def test_openapi_schema(client: TestClient): + response = client.get("openapi.json") + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users": { + "get": { + "summary": "Get User", + "operationId": "get_user_users_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + } + }, + "components": { + "schemas": { + "PlatformRole": { + "type": "string", + "enum": ["admin", "user"], + "title": "PlatformRole", + }, + "User": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "role": { + "anyOf": [ + {"$ref": "#/components/schemas/PlatformRole"}, + {"enum": [], "title": "OtherRole"}, + ], + "title": "Role", + }, + }, + "type": "object", + "required": ["username", "role"], + "title": "User", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial003.py b/tests/test_tutorial/test_header_param_models/test_tutorial003.py index 60940e1da..554a48d2e 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial003.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial003.py @@ -77,7 +77,7 @@ def test_header_param_model_no_underscore(client: TestClient): "user-agent": "testclient", "save-data": "true", "if-modified-since": "yesterday", - "x-tag": "two", + "x-tag": ["one", "two"], }, } )