Compare commits

...

143 Commits

Author SHA1 Message Date
Sebastián Ramírez
7128971f1d 🔖 Release version 0.115.8 2025-01-30 13:58:14 +00:00
github-actions
55f8a446c7 📝 Update release notes
[skip ci]
2025-01-30 12:23:00 +00:00
timothy
83ab6ac957 📝 Change the word "unwrap" to "unpack" in docs/en/docs/tutorial/extra-models.md (#13061)
Co-authored-by: timothy <53824764+jts8257@users.noreply.github.com>
Co-authored-by: Sofie Van Landeghem <svlandeg@users.noreply.github.com>
2025-01-30 12:21:44 +00:00
github-actions
3d02a920ab 📝 Update release notes
[skip ci]
2025-01-30 12:20:24 +00:00
Alejandra
1b00f8ae78 Simplify tests for body_multiple_params (#13237)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-30 12:20:01 +00:00
github-actions
d97647fd57 📝 Update release notes
[skip ci]
2025-01-30 12:19:41 +00:00
Ysabel
9667ce87a9 📝 Update Request Body's tutorial002 to deal with tax=0 case (#13230)
Co-authored-by: svlandeg <svlandeg@github.com>
2025-01-30 12:19:10 +00:00
github-actions
0541693bc7 📝 Update release notes
[skip ci]
2025-01-30 12:17:52 +00:00
github-actions
041b2e1c46 📝 Update release notes
[skip ci]
2025-01-30 12:17:34 +00:00
Shahriyar Rzayev
30b270be9a ♻️ Move duplicated code portion to a static method in the APIKeyBase super class (#3142)
Co-authored-by: Sofie Van Landeghem <svlandeg@users.noreply.github.com>
Co-authored-by: svlandeg <svlandeg@github.com>
2025-01-30 12:17:20 +00:00
Rahul Pai
d5ecbaceae 🐛 Fix OAuth2PasswordRequestForm and OAuth2PasswordRequestFormStrict fixed grant_type "password" RegEx (#9783)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com>
Co-authored-by: Sofie Van Landeghem <svlandeg@users.noreply.github.com>
Co-authored-by: svlandeg <svlandeg@github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2025-01-30 12:17:09 +00:00
github-actions
43d9a4d2b1 📝 Update release notes
[skip ci]
2025-01-30 12:04:59 +00:00
Alejandra
c5b5af7c53 Simplify tests for request_files (#13182)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-30 12:04:34 +00:00
github-actions
887270ff8a 📝 Update release notes
[skip ci]
2025-01-29 18:02:50 +00:00
Sebastián Ramírez
bd106fc750 ⬆️ Add support for Python 3.13 (#13274)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-29 18:02:27 +00:00
github-actions
eab0653a34 📝 Update release notes
[skip ci]
2025-01-28 23:35:44 +00:00
Sebastián Ramírez
8c6f10b64a ⬆️ Upgrade AnyIO max version for tests, new range: >=3.2.1,<5.0.0 (#13273) 2025-01-28 23:35:19 +00:00
github-actions
93e9fed2e8 📝 Update release notes
[skip ci]
2025-01-28 22:36:38 +00:00
Sebastián Ramírez
e747f1938a 🔧 Update Sponsors badges (#13271) 2025-01-28 22:36:15 +00:00
github-actions
92b745461c 📝 Update release notes
[skip ci]
2025-01-28 22:30:38 +00:00
Sebastián Ramírez
0a2b24653b ♻️ Fix notify_translations.py empty env var handling for PR label events vs workflow_dispatch (#13272) 2025-01-28 22:30:15 +00:00
github-actions
a058d8ecbc 📝 Update release notes
[skip ci]
2025-01-28 21:47:55 +00:00
Sebastián Ramírez
326fec16b9 ♻️ Refactor and move scripts/notify_translations.py, no need for a custom GitHub Action (#13270)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-28 21:47:33 +00:00
github-actions
8525b879ed 📝 Update release notes
[skip ci]
2025-01-28 20:41:36 +00:00
Sebastián Ramírez
3da797aeb8 👥 Update FastAPI People - Experts (#13269)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-28 20:41:08 +00:00
github-actions
e925c0ec8e 📝 Update release notes
[skip ci]
2025-01-28 20:35:19 +00:00
Sebastián Ramírez
ff68d0894a 🔨 Update FastAPI People Experts script, refactor and optimize data fetching to handle rate limits (#13267) 2025-01-28 20:34:56 +00:00
github-actions
d2f5097ded 📝 Update release notes
[skip ci]
2025-01-27 15:39:30 +00:00
k94-ishi
8f359273b5 🌐 Add Japanese translation for docs/ja/docs/environment-variables.md (#13226) 2025-01-27 15:39:04 +00:00
github-actions
18127b7907 📝 Update release notes
[skip ci]
2025-01-27 15:36:36 +00:00
Rishat-F
24eb8eeeba 🌐 Add Russian translation for docs/ru/docs/advanced/async-tests.md (#13227) 2025-01-27 15:36:13 +00:00
github-actions
1e44825ef2 📝 Update release notes
[skip ci]
2025-01-24 19:44:54 +00:00
Rishat-F
998a9139d3 🌐 Update Russian translation for docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md (#13252) 2025-01-24 19:44:31 +00:00
github-actions
6bbd315f3e 📝 Update release notes
[skip ci]
2025-01-24 17:05:37 +00:00
dependabot[bot]
0e2d8d64a4 ⬆ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4 (#13251)
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.3 to 1.12.4.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.3...v1.12.4)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-24 17:05:11 +00:00
github-actions
b6f6818d76 📝 Update release notes
[skip ci]
2025-01-23 09:47:05 +00:00
alv2017
4d60022c88 🌐 Add Russian translation for docs/ru/docs/tutorial/bigger-applications.md (#13154) 2025-01-23 09:46:41 +00:00
Sebastián Ramírez
fe513719ea 🔖 Release version 0.115.7 2025-01-22 22:50:29 +00:00
Sebastián Ramírez
7183f0d683 📝 Update release notes 2025-01-22 22:48:57 +00:00
github-actions
2b6f63df71 📝 Update release notes
[skip ci]
2025-01-22 18:25:24 +00:00
johnthagen
abd05a6d30 🔧 Add Pydantic 2 trove classifier (#13199) 2025-01-22 18:24:58 +00:00
github-actions
e39143d56d 📝 Update release notes
[skip ci]
2025-01-22 18:24:12 +00:00
Daniel Kusy
49e82ed2ac ⬆️ Upgrade python-multipart to >=0.0.18 (#13219) 2025-01-22 18:23:13 +00:00
github-actions
91c05b9245 📝 Update release notes
[skip ci]
2025-01-22 18:22:02 +00:00
Marcelo Trylesinski
82c74789e8 ⬆️ Bump Starlette to allow up to 0.45.0: >=0.40.0,<0.46.0 (#13117) 2025-01-22 18:21:40 +00:00
github-actions
1a38cc506e 📝 Update release notes
[skip ci]
2025-01-22 18:03:00 +00:00
Daniel Kusy
548f67d465 ⬆️ Upgrade jinja2 to >=3.1.5 (#13194) 2025-01-22 18:02:36 +00:00
github-actions
a215687c98 📝 Update release notes
[skip ci]
2025-01-22 13:42:19 +00:00
João Pedro
8fa18e5e6f 🌐 Update Portuguese Translation for docs/pt/docs/tutorial/request-forms.md (#13216) 2025-01-22 13:41:56 +00:00
github-actions
6ba09082a0 📝 Update release notes
[skip ci]
2025-01-19 22:41:00 +00:00
Alejandra
280fe73c03 Simplify tests for websockets (#13202) 2025-01-19 22:40:39 +00:00
github-actions
9f3edbf537 📝 Update release notes
[skip ci]
2025-01-19 22:39:42 +00:00
Alejandra
182c28e57a Simplify tests for request_form_models (#13183)
Co-authored-by: Sofie Van Landeghem <svlandeg@users.noreply.github.com>
2025-01-19 22:39:18 +00:00
github-actions
b5e40a6233 📝 Update release notes
[skip ci]
2025-01-19 22:37:13 +00:00
Alejandra
39698df806 Simplify tests for separate_openapi_schemas (#13201)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-19 22:36:49 +00:00
github-actions
6d51389f75 📝 Update release notes
[skip ci]
2025-01-19 22:36:07 +00:00
Alejandra
2007993433 Simplify tests for security (#13200) 2025-01-19 22:35:40 +00:00
github-actions
818eb1f365 📝 Update release notes
[skip ci]
2025-01-19 22:31:13 +00:00
Alejandra
3e12918325 Simplify tests for schema_extra_example (#13197)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-19 22:30:50 +00:00
github-actions
2c83a11a7d 📝 Update release notes
[skip ci]
2025-01-19 06:58:12 +00:00
Alejandra
081901cc99 Simplify tests for request_model (#13195) 2025-01-19 06:57:50 +00:00
github-actions
0069963bba 📝 Update release notes
[skip ci]
2025-01-19 06:43:44 +00:00
Alejandra
d309c9e140 Simplify tests for request_forms_and_files (#13185)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-19 06:43:21 +00:00
github-actions
9847cecf6f 📝 Update release notes
[skip ci]
2025-01-19 06:42:49 +00:00
Alejandra
c7d888a15f Simplify tests for request_forms (#13184) 2025-01-19 06:42:25 +00:00
github-actions
96808fd44f 📝 Update release notes
[skip ci]
2025-01-19 06:35:11 +00:00
Alejandra
09ccfce228 Simplify tests for path_query_params (#13181) 2025-01-19 06:34:48 +00:00
github-actions
ec46247595 📝 Update release notes
[skip ci]
2025-01-19 06:33:35 +00:00
Alejandra
2e8db846b4 Simplify tests for path_operation_configurations (#13180)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-01-19 06:33:10 +00:00
github-actions
b123c5c489 📝 Update release notes
[skip ci]
2025-01-19 06:32:34 +00:00
Alejandra
aa60185781 Simplify tests for header_params (#13179) 2025-01-19 06:32:11 +00:00
github-actions
d704f94cf0 📝 Update release notes
[skip ci]
2025-01-19 06:29:54 +00:00
Alejandra
3d017824ba Simplify tests for extra_models (#13178) 2025-01-19 06:29:33 +00:00
github-actions
ae93ec140a 📝 Update release notes
[skip ci]
2025-01-19 06:28:31 +00:00
Alejandra
8015f832d4 Simplify tests for extra_data_types (#13177) 2025-01-19 06:28:09 +00:00
github-actions
cd521dff74 📝 Update release notes
[skip ci]
2025-01-19 06:27:16 +00:00
Alejandra
736712173a Simplify tests for cookie_params (#13176) 2025-01-19 06:26:50 +00:00
github-actions
409a850c6c 📝 Update release notes
[skip ci]
2025-01-19 06:26:10 +00:00
Alejandra
920df4d1ac Simplify tests for dependencies (#13174) 2025-01-19 06:25:51 +00:00
github-actions
c0fddaa9a9 📝 Update release notes
[skip ci]
2025-01-19 06:21:52 +00:00
Alejandra
0a882e926e Simplify tests for body_updates (#13172) 2025-01-19 06:21:30 +00:00
github-actions
f30dd4fe40 📝 Update release notes
[skip ci]
2025-01-19 06:21:05 +00:00
Alejandra
55ef9270b8 Simplify tests for body_nested_models (#13171) 2025-01-19 06:20:41 +00:00
github-actions
4191f4d33a 📝 Update release notes
[skip ci]
2025-01-19 06:20:23 +00:00
Alejandra
1cedd8becf Simplify tests for body_multiple_params (#13170) 2025-01-19 06:19:58 +00:00
github-actions
7e06c4c97f 📝 Update release notes
[skip ci]
2025-01-18 13:10:40 +00:00
Sebastián Ramírez
af599c92ac 👥 Update FastAPI People - Sponsors (#13231)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-18 13:10:13 +00:00
github-actions
2acdc13608 📝 Update release notes
[skip ci]
2025-01-18 12:58:57 +00:00
Sebastián Ramírez
2ee101fb81 👷 Refactor FastAPI People Sponsors to use 2 tokens (#13228) 2025-01-18 12:58:36 +00:00
github-actions
ea0cdd120c 📝 Update release notes
[skip ci]
2025-01-17 22:34:04 +00:00
Sebastián Ramírez
35b24deef3 👷 Update token for FastAPI People - Sponsors (#13225) 2025-01-17 22:33:38 +00:00
github-actions
db48d9cf09 📝 Update release notes
[skip ci]
2025-01-17 17:53:06 +00:00
Sebastián Ramírez
9e0d4fa0ef 👷 Add independent CI automation for FastAPI People - Sponsors (#13221) 2025-01-17 17:51:19 +00:00
github-actions
e773d7e919 📝 Update release notes
[skip ci]
2025-01-15 20:18:49 +00:00
Rafael de Oliveira Marques
d9a640e06c 🌐 Update Portuguese translation for docs/pt/docs/advanced/settings.md (#13209) 2025-01-15 20:17:23 +00:00
github-actions
5f49397d19 📝 Update release notes
[skip ci]
2025-01-15 20:16:38 +00:00
Rafael de Oliveira Marques
16199c4a13 🌐 Add Portuguese translation for docs/pt/docs/tutorial/security/oauth2-jwt.md (#13205) 2025-01-15 20:16:13 +00:00
github-actions
2612fa3e9d 📝 Update release notes
[skip ci]
2025-01-13 13:36:46 +00:00
Gerry Sabar
5d18ae0d90 🌐 Add Indonesian translation for docs/id/docs/index.md (#13191) 2025-01-13 13:36:23 +00:00
github-actions
09a0295bf3 📝 Update release notes
[skip ci]
2025-01-10 20:31:39 +00:00
Guspan Tanadi
cda85623fb 🌐 Add Indonesian translation for docs/id/docs/tutorial/static-files.md (#13092) 2025-01-10 20:31:13 +00:00
github-actions
62be4a1600 📝 Update release notes
[skip ci]
2025-01-10 13:33:58 +00:00
Rafael de Oliveira Marques
e69182940e 🌐 Add Portuguese translation for docs/pt/docs/tutorial/security/get-current-user.md (#13188) 2025-01-10 13:33:35 +00:00
github-actions
f36927d0a6 📝 Update release notes
[skip ci]
2025-01-10 13:33:14 +00:00
Rafael de Oliveira Marques
e54cc8ffa3 🌐 Remove Wrong Portuguese translations location for docs/pt/docs/advanced/benchmarks.md (#13187) 2025-01-10 13:32:37 +00:00
github-actions
837e94573d 📝 Update release notes
[skip ci]
2025-01-09 20:41:29 +00:00
nillvitor
9d293b7086 🌐 Update Portuguese translations (#13156) 2025-01-09 20:41:07 +00:00
github-actions
3d2ef237ed 📝 Update release notes
[skip ci]
2025-01-08 19:29:11 +00:00
Alejandra
9b88c7c18a Simplify tests for body_fields (#13169) 2025-01-08 20:28:44 +01:00
github-actions
fe4b25e2d7 📝 Update release notes
[skip ci]
2025-01-08 19:26:39 +00:00
Alejandra
0cc031f477 Simplify tests for body (#13168) 2025-01-08 20:26:16 +01:00
github-actions
a8447c15e5 📝 Update release notes
[skip ci]
2025-01-08 19:25:29 +00:00
Alejandra
5d3f45c2d4 Simplify tests for bigger_applications (#13167) 2025-01-08 20:25:01 +01:00
github-actions
afd1502283 📝 Update release notes
[skip ci]
2025-01-08 19:24:05 +00:00
Alejandra
44adb29ce1 Simplify tests for background_tasks (#13166) 2025-01-08 20:23:42 +01:00
github-actions
144f09ea14 📝 Update release notes
[skip ci]
2025-01-06 18:27:10 +00:00
Yaroslav Luchinsky
4cd5a7ac1c 🌐 Update Russian translation for docs/ru/docs/tutorial/security/first-steps.md (#13159) 2025-01-06 18:26:39 +00:00
github-actions
d7bd68979f 📝 Update release notes
[skip ci]
2025-01-06 11:24:40 +00:00
Kinuax
b0e70cb37e ✏️ Update Strawberry integration docs (#13155) 2025-01-06 11:24:17 +00:00
github-actions
6e60d0a056 📝 Update release notes
[skip ci]
2025-01-05 14:45:08 +00:00
Alejandra
d784a90207 🔥 Remove unused Peewee tutorial files (#13158) 2025-01-05 14:44:44 +00:00
github-actions
924c96bf90 📝 Update release notes
[skip ci]
2025-01-04 10:54:27 +00:00
FakeDocument
dace29835c ✏️ Delete unnecessary backspace in docs/ja/docs/tutorial/path-params-numeric-validations.md (#12238) 2025-01-04 10:53:58 +00:00
github-actions
c99b945d87 📝 Update release notes
[skip ci]
2025-01-04 00:26:56 +00:00
Sebastián Ramírez
f229dd97c0 👷 Add retries to Smokeshow (#13151) 2025-01-04 00:26:30 +00:00
github-actions
023bc01967 📝 Update release notes
[skip ci]
2025-01-03 21:29:28 +00:00
Chai Landau
083c6dd481 🔧 Update Speakeasy sponsor graphic (#13147) 2025-01-03 21:29:07 +00:00
github-actions
cf4dfcbd59 📝 Update release notes
[skip ci]
2025-01-03 18:29:01 +00:00
Hamza Kyamanywa
8416e3ee23 📝 Update image in body-nested-model docs (#11063) 2025-01-03 18:28:35 +00:00
github-actions
01a1fc8d15 📝 Update release notes
[skip ci]
2025-01-03 16:26:28 +00:00
Zhongheng Cheng
87fb46bcac 📝 Update fastapi-cli UI examples in docs (#13107) 2025-01-03 16:26:01 +00:00
github-actions
91738b5ae7 📝 Update release notes
[skip ci]
2025-01-03 16:17:14 +00:00
Zhongheng Cheng
27c700a6d3 🌐 Update Chinese translation for docs/zh/docs/fastapi-cli.md (#13102) 2025-01-03 16:16:52 +00:00
github-actions
9eb712802e 📝 Update release notes
[skip ci]
2025-01-03 09:48:22 +00:00
Sebastián Ramírez
994340f839 Simplify tests for additional_status_codes (#13149) 2025-01-03 09:47:55 +00:00
github-actions
4f04377270 📝 Update release notes
[skip ci]
2025-01-02 21:01:23 +00:00
Sebastián Ramírez
ccae0c0cb9 👥 Update FastAPI GitHub topic repositories (#13146)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-02 21:00:17 +00:00
github-actions
aa27afdb24 📝 Update release notes
[skip ci]
2025-01-02 20:45:12 +00:00
Alejandra
1b8f823a05 👷‍♀️ Add script for GitHub Topic Repositories and update External Links (#13135)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2025-01-02 20:44:50 +00:00
github-actions
548dd233c3 📝 Update release notes
[skip ci]
2025-01-02 17:22:51 +00:00
Sebastián Ramírez
2d210f7313 👥 Update FastAPI People - Contributors and Translators (#13145)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-02 17:22:30 +00:00
github-actions
af7db9b95d 📝 Update release notes
[skip ci]
2025-01-02 17:03:46 +00:00
Sebastián Ramírez
b59885c9d5 👷 Add new GitHub Action to update contributors, translators, and translation reviewers (#13136) 2025-01-02 17:03:21 +00:00
257 changed files with 8853 additions and 27006 deletions

View File

@@ -1,7 +0,0 @@
FROM python:3.9
RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0"
COPY ./app /app
CMD ["python", "/app/main.py"]

View File

@@ -1,10 +0,0 @@
name: "Notify Translations"
description: "Notify in the issue for a translation when there's a new PR available"
author: "Sebastián Ramírez <tiangolo@gmail.com>"
inputs:
token:
description: 'Token, to read the GitHub API. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
required: true
runs:
using: 'docker'
image: 'Dockerfile'

View File

@@ -1,7 +0,0 @@
FROM python:3.9
RUN pip install httpx PyGithub "pydantic==2.0.2" pydantic-settings "pyyaml>=5.3.1,<6.0.0"
COPY ./app /app
CMD ["python", "/app/main.py"]

View File

@@ -1,10 +0,0 @@
name: "Generate FastAPI People"
description: "Generate the data for the FastAPI People page"
author: "Sebastián Ramírez <tiangolo@gmail.com>"
inputs:
token:
description: 'User token, to read the GitHub API. Can be passed in using {{ secrets.FASTAPI_PEOPLE }}'
required: true
runs:
using: 'docker'
image: 'Dockerfile'

View File

@@ -1,682 +0,0 @@
import logging
import subprocess
import sys
from collections import Counter, defaultdict
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Container, DefaultDict, Dict, List, Set, Union
import httpx
import yaml
from github import Github
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql"
questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0"
discussions_query = """
query Q($after: String, $category_id: ID) {
repository(name: "fastapi", owner: "fastapi") {
discussions(first: 100, after: $after, categoryId: $category_id) {
edges {
cursor
node {
number
author {
login
avatarUrl
url
}
title
createdAt
comments(first: 100) {
nodes {
createdAt
author {
login
avatarUrl
url
}
isAnswer
replies(first: 10) {
nodes {
createdAt
author {
login
avatarUrl
url
}
}
}
}
}
}
}
}
}
}
"""
prs_query = """
query Q($after: String) {
repository(name: "fastapi", owner: "fastapi") {
pullRequests(first: 100, after: $after) {
edges {
cursor
node {
number
labels(first: 100) {
nodes {
name
}
}
author {
login
avatarUrl
url
}
title
createdAt
state
comments(first: 100) {
nodes {
createdAt
author {
login
avatarUrl
url
}
}
}
reviews(first:100) {
nodes {
author {
login
avatarUrl
url
}
state
}
}
}
}
}
}
}
"""
sponsors_query = """
query Q($after: String) {
user(login: "fastapi") {
sponsorshipsAsMaintainer(first: 100, after: $after) {
edges {
cursor
node {
sponsorEntity {
... on Organization {
login
avatarUrl
url
}
... on User {
login
avatarUrl
url
}
}
tier {
name
monthlyPriceInDollars
}
}
}
}
}
}
"""
class Author(BaseModel):
login: str
avatarUrl: str
url: str
# Discussions
class CommentsNode(BaseModel):
createdAt: datetime
author: Union[Author, None] = None
class Replies(BaseModel):
nodes: List[CommentsNode]
class DiscussionsCommentsNode(CommentsNode):
replies: Replies
class Comments(BaseModel):
nodes: List[CommentsNode]
class DiscussionsComments(BaseModel):
nodes: List[DiscussionsCommentsNode]
class DiscussionsNode(BaseModel):
number: int
author: Union[Author, None] = None
title: str
createdAt: datetime
comments: DiscussionsComments
class DiscussionsEdge(BaseModel):
cursor: str
node: DiscussionsNode
class Discussions(BaseModel):
edges: List[DiscussionsEdge]
class DiscussionsRepository(BaseModel):
discussions: Discussions
class DiscussionsResponseData(BaseModel):
repository: DiscussionsRepository
class DiscussionsResponse(BaseModel):
data: DiscussionsResponseData
# PRs
class LabelNode(BaseModel):
name: str
class Labels(BaseModel):
nodes: List[LabelNode]
class ReviewNode(BaseModel):
author: Union[Author, None] = None
state: str
class Reviews(BaseModel):
nodes: List[ReviewNode]
class PullRequestNode(BaseModel):
number: int
labels: Labels
author: Union[Author, None] = None
title: str
createdAt: datetime
state: str
comments: Comments
reviews: Reviews
class PullRequestEdge(BaseModel):
cursor: str
node: PullRequestNode
class PullRequests(BaseModel):
edges: List[PullRequestEdge]
class PRsRepository(BaseModel):
pullRequests: PullRequests
class PRsResponseData(BaseModel):
repository: PRsRepository
class PRsResponse(BaseModel):
data: PRsResponseData
# Sponsors
class SponsorEntity(BaseModel):
login: str
avatarUrl: str
url: str
class Tier(BaseModel):
name: str
monthlyPriceInDollars: float
class SponsorshipAsMaintainerNode(BaseModel):
sponsorEntity: SponsorEntity
tier: Tier
class SponsorshipAsMaintainerEdge(BaseModel):
cursor: str
node: SponsorshipAsMaintainerNode
class SponsorshipAsMaintainer(BaseModel):
edges: List[SponsorshipAsMaintainerEdge]
class SponsorsUser(BaseModel):
sponsorshipsAsMaintainer: SponsorshipAsMaintainer
class SponsorsResponseData(BaseModel):
user: SponsorsUser
class SponsorsResponse(BaseModel):
data: SponsorsResponseData
class Settings(BaseSettings):
input_token: SecretStr
github_repository: str
httpx_timeout: int = 30
def get_graphql_response(
*,
settings: Settings,
query: str,
after: Union[str, None] = None,
category_id: Union[str, None] = None,
) -> Dict[str, Any]:
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
# category_id is only used by one query, but GraphQL allows unused variables, so
# keep it here for simplicity
variables = {"after": after, "category_id": category_id}
response = httpx.post(
github_graphql_url,
headers=headers,
timeout=settings.httpx_timeout,
json={"query": query, "variables": variables, "operationName": "Q"},
)
if response.status_code != 200:
logging.error(
f"Response was not 200, after: {after}, category_id: {category_id}"
)
logging.error(response.text)
raise RuntimeError(response.text)
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}, category_id: {category_id}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
def get_graphql_question_discussion_edges(
*,
settings: Settings,
after: Union[str, None] = None,
):
data = get_graphql_response(
settings=settings,
query=discussions_query,
after=after,
category_id=questions_category_id,
)
graphql_response = DiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.edges
def get_graphql_pr_edges(*, settings: Settings, after: Union[str, None] = None):
data = get_graphql_response(settings=settings, query=prs_query, after=after)
graphql_response = PRsResponse.model_validate(data)
return graphql_response.data.repository.pullRequests.edges
def get_graphql_sponsor_edges(*, settings: Settings, after: Union[str, None] = None):
data = get_graphql_response(settings=settings, query=sponsors_query, after=after)
graphql_response = SponsorsResponse.model_validate(data)
return graphql_response.data.user.sponsorshipsAsMaintainer.edges
class DiscussionExpertsResults(BaseModel):
commenters: Counter
last_month_commenters: Counter
three_months_commenters: Counter
six_months_commenters: Counter
one_year_commenters: Counter
authors: Dict[str, Author]
def get_discussion_nodes(settings: Settings) -> List[DiscussionsNode]:
discussion_nodes: List[DiscussionsNode] = []
discussion_edges = get_graphql_question_discussion_edges(settings=settings)
while discussion_edges:
for discussion_edge in discussion_edges:
discussion_nodes.append(discussion_edge.node)
last_edge = discussion_edges[-1]
discussion_edges = get_graphql_question_discussion_edges(
settings=settings, after=last_edge.cursor
)
return discussion_nodes
def get_discussions_experts(
discussion_nodes: List[DiscussionsNode],
) -> DiscussionExpertsResults:
commenters = Counter()
last_month_commenters = Counter()
three_months_commenters = Counter()
six_months_commenters = Counter()
one_year_commenters = Counter()
authors: Dict[str, Author] = {}
now = datetime.now(tz=timezone.utc)
one_month_ago = now - timedelta(days=30)
three_months_ago = now - timedelta(days=90)
six_months_ago = now - timedelta(days=180)
one_year_ago = now - timedelta(days=365)
for discussion in discussion_nodes:
discussion_author_name = None
if discussion.author:
authors[discussion.author.login] = discussion.author
discussion_author_name = discussion.author.login
discussion_commentors: dict[str, datetime] = {}
for comment in discussion.comments.nodes:
if comment.author:
authors[comment.author.login] = comment.author
if comment.author.login != discussion_author_name:
author_time = discussion_commentors.get(
comment.author.login, comment.createdAt
)
discussion_commentors[comment.author.login] = max(
author_time, comment.createdAt
)
for reply in comment.replies.nodes:
if reply.author:
authors[reply.author.login] = reply.author
if reply.author.login != discussion_author_name:
author_time = discussion_commentors.get(
reply.author.login, reply.createdAt
)
discussion_commentors[reply.author.login] = max(
author_time, reply.createdAt
)
for author_name, author_time in discussion_commentors.items():
commenters[author_name] += 1
if author_time > one_month_ago:
last_month_commenters[author_name] += 1
if author_time > three_months_ago:
three_months_commenters[author_name] += 1
if author_time > six_months_ago:
six_months_commenters[author_name] += 1
if author_time > one_year_ago:
one_year_commenters[author_name] += 1
discussion_experts_results = DiscussionExpertsResults(
authors=authors,
commenters=commenters,
last_month_commenters=last_month_commenters,
three_months_commenters=three_months_commenters,
six_months_commenters=six_months_commenters,
one_year_commenters=one_year_commenters,
)
return discussion_experts_results
def get_pr_nodes(settings: Settings) -> List[PullRequestNode]:
pr_nodes: List[PullRequestNode] = []
pr_edges = get_graphql_pr_edges(settings=settings)
while pr_edges:
for edge in pr_edges:
pr_nodes.append(edge.node)
last_edge = pr_edges[-1]
pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor)
return pr_nodes
class ContributorsResults(BaseModel):
contributors: Counter
commenters: Counter
reviewers: Counter
translation_reviewers: Counter
authors: Dict[str, Author]
def get_contributors(pr_nodes: List[PullRequestNode]) -> ContributorsResults:
contributors = Counter()
commenters = Counter()
reviewers = Counter()
translation_reviewers = Counter()
authors: Dict[str, Author] = {}
for pr in pr_nodes:
author_name = None
if pr.author:
authors[pr.author.login] = pr.author
author_name = pr.author.login
pr_commentors: Set[str] = set()
pr_reviewers: Set[str] = set()
for comment in pr.comments.nodes:
if comment.author:
authors[comment.author.login] = comment.author
if comment.author.login == author_name:
continue
pr_commentors.add(comment.author.login)
for author_name in pr_commentors:
commenters[author_name] += 1
for review in pr.reviews.nodes:
if review.author:
authors[review.author.login] = review.author
pr_reviewers.add(review.author.login)
for label in pr.labels.nodes:
if label.name == "lang-all":
translation_reviewers[review.author.login] += 1
break
for reviewer in pr_reviewers:
reviewers[reviewer] += 1
if pr.state == "MERGED" and pr.author:
contributors[pr.author.login] += 1
return ContributorsResults(
contributors=contributors,
commenters=commenters,
reviewers=reviewers,
translation_reviewers=translation_reviewers,
authors=authors,
)
def get_individual_sponsors(settings: Settings):
nodes: List[SponsorshipAsMaintainerNode] = []
edges = get_graphql_sponsor_edges(settings=settings)
while edges:
for edge in edges:
nodes.append(edge.node)
last_edge = edges[-1]
edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor)
tiers: DefaultDict[float, Dict[str, SponsorEntity]] = defaultdict(dict)
for node in nodes:
tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = (
node.sponsorEntity
)
return tiers
def get_top_users(
*,
counter: Counter,
authors: Dict[str, Author],
skip_users: Container[str],
min_count: int = 2,
):
users = []
for commenter, count in counter.most_common(50):
if commenter in skip_users:
continue
if count >= min_count:
author = authors[commenter]
users.append(
{
"login": commenter,
"count": count,
"avatarUrl": author.avatarUrl,
"url": author.url,
}
)
return users
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.input_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
discussion_nodes = get_discussion_nodes(settings=settings)
experts_results = get_discussions_experts(discussion_nodes=discussion_nodes)
pr_nodes = get_pr_nodes(settings=settings)
contributors_results = get_contributors(pr_nodes=pr_nodes)
authors = {**experts_results.authors, **contributors_results.authors}
maintainers_logins = {"tiangolo"}
bot_names = {"codecov", "github-actions", "pre-commit-ci", "dependabot"}
maintainers = []
for login in maintainers_logins:
user = authors[login]
maintainers.append(
{
"login": login,
"answers": experts_results.commenters[login],
"prs": contributors_results.contributors[login],
"avatarUrl": user.avatarUrl,
"url": user.url,
}
)
skip_users = maintainers_logins | bot_names
experts = get_top_users(
counter=experts_results.commenters,
authors=authors,
skip_users=skip_users,
)
last_month_experts = get_top_users(
counter=experts_results.last_month_commenters,
authors=authors,
skip_users=skip_users,
)
three_months_experts = get_top_users(
counter=experts_results.three_months_commenters,
authors=authors,
skip_users=skip_users,
)
six_months_experts = get_top_users(
counter=experts_results.six_months_commenters,
authors=authors,
skip_users=skip_users,
)
one_year_experts = get_top_users(
counter=experts_results.one_year_commenters,
authors=authors,
skip_users=skip_users,
)
top_contributors = get_top_users(
counter=contributors_results.contributors,
authors=authors,
skip_users=skip_users,
)
top_reviewers = get_top_users(
counter=contributors_results.reviewers,
authors=authors,
skip_users=skip_users,
)
top_translations_reviewers = get_top_users(
counter=contributors_results.translation_reviewers,
authors=authors,
skip_users=skip_users,
)
tiers = get_individual_sponsors(settings=settings)
keys = list(tiers.keys())
keys.sort(reverse=True)
sponsors = []
for key in keys:
sponsor_group = []
for login, sponsor in tiers[key].items():
sponsor_group.append(
{"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url}
)
sponsors.append(sponsor_group)
people = {
"maintainers": maintainers,
"experts": experts,
"last_month_experts": last_month_experts,
"three_months_experts": three_months_experts,
"six_months_experts": six_months_experts,
"one_year_experts": one_year_experts,
"top_contributors": top_contributors,
"top_reviewers": top_reviewers,
"top_translations_reviewers": top_translations_reviewers,
}
github_sponsors = {
"sponsors": sponsors,
}
# For local development
# people_path = Path("../../../../docs/en/data/people.yml")
people_path = Path("./docs/en/data/people.yml")
github_sponsors_path = Path("./docs/en/data/github_sponsors.yml")
people_old_content = people_path.read_text(encoding="utf-8")
github_sponsors_old_content = github_sponsors_path.read_text(encoding="utf-8")
new_people_content = yaml.dump(
people, sort_keys=False, width=200, allow_unicode=True
)
new_github_sponsors_content = yaml.dump(
github_sponsors, sort_keys=False, width=200, allow_unicode=True
)
if (
people_old_content == new_people_content
and github_sponsors_old_content == new_github_sponsors_content
):
logging.info("The FastAPI People data hasn't changed, finishing.")
sys.exit(0)
people_path.write_text(new_people_content, encoding="utf-8")
github_sponsors_path.write_text(new_github_sponsors_content, encoding="utf-8")
logging.info("Setting up GitHub Actions git user")
subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
subprocess.run(
["git", "config", "user.email", "github-actions@github.com"], check=True
)
branch_name = "fastapi-people"
logging.info(f"Creating a new branch {branch_name}")
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
logging.info("Adding updated file")
subprocess.run(
["git", "add", str(people_path), str(github_sponsors_path)], check=True
)
logging.info("Committing updated file")
message = "👥 Update FastAPI People"
result = subprocess.run(["git", "commit", "-m", message], check=True)
logging.info("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
logging.info("Creating PR")
pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
logging.info(f"Created PR: {pr.number}")
logging.info("Finished")

53
.github/workflows/contributors.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: FastAPI People Contributors
on:
schedule:
- cron: "0 3 1 * *"
workflow_dispatch:
inputs:
debug_enabled:
description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)"
required: false
default: "false"
env:
UV_SYSTEM_PYTHON: 1
jobs:
job:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v5
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}
- name: FastAPI People Contributors
run: python ./scripts/contributors.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}

View File

@@ -15,15 +15,14 @@ on:
required: false
default: 'false'
permissions:
discussions: write
env:
UV_SYSTEM_PYTHON: 1
jobs:
notify-translations:
job:
runs-on: ubuntu-latest
permissions:
discussions: write
steps:
- name: Dump GitHub context
env:
@@ -42,12 +41,19 @@ jobs:
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: ./.github/actions/notify-translations
with:
token: ${{ secrets.GITHUB_TOKEN }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Notify Translations
run: python ./scripts/notify_translations.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NUMBER: ${{ github.event.inputs.number || null }}
DEBUG: ${{ github.event.inputs.debug_enabled || 'false' }}

View File

@@ -6,29 +6,48 @@ on:
workflow_dispatch:
inputs:
debug_enabled:
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
description: Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)
required: false
default: 'false'
default: "false"
env:
UV_SYSTEM_PYTHON: 1
jobs:
fastapi-people:
job:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
# Ref: https://github.com/actions/runner/issues/2033
- name: Fix git safe.directory in container
run: mkdir -p /home/runner/work/_temp/_github_home && printf "[safe]\n\tdirectory = /github/workspace" > /home/runner/work/_temp/_github_home/.gitconfig
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v5
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- uses: ./.github/actions/people
with:
token: ${{ secrets.FASTAPI_PEOPLE }}
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}
- name: FastAPI People Experts
run: python ./scripts/people.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }}

View File

@@ -35,7 +35,7 @@ jobs:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
run: python -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.3
uses: pypa/gh-action-pypi-publish@v1.12.4
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}

View File

@@ -40,7 +40,17 @@ jobs:
path: htmlcov
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- run: smokeshow upload htmlcov
# Try 5 times to upload coverage to smokeshow
- name: Upload coverage to Smokeshow
run: |
for i in 1 2 3 4 5; do
if smokeshow upload htmlcov; then
echo "Smokeshow upload success!"
break
fi
echo "Smokeshow upload error, sleep 1 sec and try again."
sleep 1
done
env:
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 100

52
.github/workflows/sponsors.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: FastAPI People Sponsors
on:
schedule:
- cron: "0 6 1 * *"
workflow_dispatch:
inputs:
debug_enabled:
description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)"
required: false
default: "false"
env:
UV_SYSTEM_PYTHON: 1
jobs:
job:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v5
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install Dependencies
run: uv pip install -r requirements-github-actions.txt
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- name: FastAPI People Sponsors
run: python ./scripts/sponsors.py
env:
SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }}
PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}

View File

@@ -48,6 +48,7 @@ jobs:
strategy:
matrix:
python-version:
- "3.13"
- "3.12"
- "3.11"
- "3.10"
@@ -81,6 +82,10 @@ jobs:
- name: Install Pydantic v2
if: matrix.pydantic-version == 'pydantic-v2'
run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0"
# TODO: Remove this once Python 3.8 is no longer supported
- name: Install older AnyIO in Python 3.8
if: matrix.python-version == '3.8'
run: uv pip install "anyio[trio]<4.0.0"
- run: mkdir coverage
- name: Test
run: bash scripts/test.sh

40
.github/workflows/topic-repos.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Update Topic Repos
on:
schedule:
- cron: "0 12 1 * *"
workflow_dispatch:
env:
UV_SYSTEM_PYTHON: 1
jobs:
topic-repos:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v5
with:
version: "0.4.15"
enable-cache: true
cache-dependency-glob: |
requirements**.txt
pyproject.toml
- name: Install GitHub Actions dependencies
run: uv pip install -r requirements-github-actions.txt
- name: Update Topic Repos
run: python ./scripts/topic_repos.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }}

View File

@@ -0,0 +1,505 @@
tiangolo:
login: tiangolo
count: 697
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
dependabot:
login: dependabot
count: 89
avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4
url: https://github.com/apps/dependabot
github-actions:
login: github-actions
count: 26
avatarUrl: https://avatars.githubusercontent.com/in/15368?v=4
url: https://github.com/apps/github-actions
Kludex:
login: Kludex
count: 22
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4
url: https://github.com/Kludex
pre-commit-ci:
login: pre-commit-ci
count: 22
avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4
url: https://github.com/apps/pre-commit-ci
alejsdev:
login: alejsdev
count: 21
avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=356f39ff3f0211c720b06d3dbb060e98884085e3&v=4
url: https://github.com/alejsdev
dmontagu:
login: dmontagu
count: 17
avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4
url: https://github.com/dmontagu
euri10:
login: euri10
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/1104190?u=321a2e953e6645a7d09b732786c7a8061e0f8a8b&v=4
url: https://github.com/euri10
kantandane:
login: kantandane
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/3978368?u=cccc199291f991a73b1ebba5abc735a948e0bd16&v=4
url: https://github.com/kantandane
nilslindemann:
login: nilslindemann
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
zhaohan-dong:
login: zhaohan-dong
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/65422392?u=8260f8781f50248410ebfa4c9bf70e143fe5c9f2&v=4
url: https://github.com/zhaohan-dong
mariacamilagl:
login: mariacamilagl
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
url: https://github.com/mariacamilagl
handabaldeep:
login: handabaldeep
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/12239103?u=6c39ef15d14c6d5211f5dd775cc4842f8d7f2f3a&v=4
url: https://github.com/handabaldeep
vishnuvskvkl:
login: vishnuvskvkl
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/84698110?u=8af5de0520dd4fa195f53c2850a26f57c0f6bc64&v=4
url: https://github.com/vishnuvskvkl
alissadb:
login: alissadb
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/96190409?u=be42d85938c241be781505a5a872575be28b2906&v=4
url: https://github.com/alissadb
wshayes:
login: wshayes
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
samuelcolvin:
login: samuelcolvin
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4
url: https://github.com/samuelcolvin
waynerv:
login: waynerv
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv
svlandeg:
login: svlandeg
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4
url: https://github.com/svlandeg
krishnamadhavan:
login: krishnamadhavan
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/31798870?u=950693b28f3ae01105fd545c046e46ca3d31ab06&v=4
url: https://github.com/krishnamadhavan
jekirl:
login: jekirl
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/2546697?u=a027452387d85bd4a14834e19d716c99255fb3b7&v=4
url: https://github.com/jekirl
hitrust:
login: hitrust
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/3360631?u=5fa1f475ad784d64eb9666bdd43cc4d285dcc773&v=4
url: https://github.com/hitrust
adriangb:
login: adriangb
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4
url: https://github.com/adriangb
iudeen:
login: iudeen
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/10519440?u=2843b3303282bff8b212dcd4d9d6689452e4470c&v=4
url: https://github.com/iudeen
philipokiokio:
login: philipokiokio
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/55271518?u=d30994d339aaaf1f6bf1b8fc810132016fbd4fdc&v=4
url: https://github.com/philipokiokio
AlexWendland:
login: AlexWendland
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/3949212?u=c4c0c615e0ea33d00bfe16b779cf6ebc0f58071c&v=4
url: https://github.com/AlexWendland
divums:
login: divums
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1397556?v=4
url: https://github.com/divums
prostomarkeloff:
login: prostomarkeloff
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=6918e39a1224194ba636e897461a02a20126d7ad&v=4
url: https://github.com/prostomarkeloff
nsidnev:
login: nsidnev
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4
url: https://github.com/nsidnev
pawamoy:
login: pawamoy
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
url: https://github.com/pawamoy
patrickmckenna:
login: patrickmckenna
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/3589536?u=53aef07250d226d35e526768e26891964907b41a&v=4
url: https://github.com/patrickmckenna
hukkin:
login: hukkin
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/3275109?u=77bb83759127965eacbfe67e2ca983066e964fde&v=4
url: https://github.com/hukkin
marcosmmb:
login: marcosmmb
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/6181089?u=b8567a842b38c5570c315b2b7ca766fa7be6721e&v=4
url: https://github.com/marcosmmb
Serrones:
login: Serrones
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
url: https://github.com/Serrones
uriyyo:
login: uriyyo
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/32038156?u=a27b65a9ec3420586a827a0facccbb8b6df1ffb3&v=4
url: https://github.com/uriyyo
amacfie:
login: amacfie
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/889657?u=d70187989940b085bcbfa3bedad8dbc5f3ab1fe7&v=4
url: https://github.com/amacfie
rkbeatss:
login: rkbeatss
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/23391143?u=56ab6bff50be950fa8cae5cf736f2ae66e319ff3&v=4
url: https://github.com/rkbeatss
asheux:
login: asheux
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/22955146?u=4553ebf5b5a7c7fe031a46182083aa224faba2e1&v=4
url: https://github.com/asheux
n25a:
login: n25a
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/49960770?u=7d8a6d5f0a75a5e9a865a2527edfd48895ea27ae&v=4
url: https://github.com/n25a
ghandic:
login: ghandic
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/23500353?u=e2e1d736f924d9be81e8bfc565b6d8836ba99773&v=4
url: https://github.com/ghandic
TeoZosa:
login: TeoZosa
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/13070236?u=96fdae85800ef85dcfcc4b5f8281dc8778c8cb7d&v=4
url: https://github.com/TeoZosa
graingert:
login: graingert
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/413772?u=64b77b6aa405c68a9c6bcf45f84257c66eea5f32&v=4
url: https://github.com/graingert
ShahriyarR:
login: ShahriyarR
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/3852029?u=c9a1691e5ebdc94cbf543086099a6ed705cdb873&v=4
url: https://github.com/ShahriyarR
jaystone776:
login: jaystone776
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
url: https://github.com/jaystone776
zanieb:
login: zanieb
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/2586601?u=e5c86f7ff3b859e7e183187ac2b17fd6ee32b3ab&v=4
url: https://github.com/zanieb
MicaelJarniac:
login: MicaelJarniac
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/19514231?u=158c91874ea98d6e9e6f0c6db37ee2ce60c55ff2&v=4
url: https://github.com/MicaelJarniac
papb:
login: papb
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/20914054?u=890511fae7ea90d887e2a65ce44a1775abba38d5&v=4
url: https://github.com/papb
gitworkflows:
login: gitworkflows
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/118260833?v=4
url: https://github.com/gitworkflows
Nimitha-jagadeesha:
login: Nimitha-jagadeesha
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/58389915?v=4
url: https://github.com/Nimitha-jagadeesha
lucaromagnoli:
login: lucaromagnoli
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/38782977?u=e66396859f493b4ddcb3a837a1b2b2039c805417&v=4
url: https://github.com/lucaromagnoli
salmantec:
login: salmantec
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/41512228?u=443551b893ff2425c59d5d021644f098cf7c68d5&v=4
url: https://github.com/salmantec
OCE1960:
login: OCE1960
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/45076670?u=0e9a44712b92ffa89ddfbaa83c112f3f8e1d68e2&v=4
url: https://github.com/OCE1960
hamidrasti:
login: hamidrasti
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/43915620?v=4
url: https://github.com/hamidrasti
kkinder:
login: kkinder
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1115018?u=c5e90284a9f5c5049eae1bb029e3655c7dc913ed&v=4
url: https://github.com/kkinder
kabirkhan:
login: kabirkhan
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/13891834?u=e0eabf792376443ac853e7dca6f550db4166fe35&v=4
url: https://github.com/kabirkhan
zamiramir:
login: zamiramir
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/40475662?u=e58ef61034e8d0d6a312cc956fb09b9c3332b449&v=4
url: https://github.com/zamiramir
trim21:
login: trim21
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/13553903?u=3cadf0f02095c9621aa29df6875f53a80ca4fbfb&v=4
url: https://github.com/trim21
koxudaxi:
login: koxudaxi
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
url: https://github.com/koxudaxi
pablogamboa:
login: pablogamboa
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/12892536?u=326a57059ee0c40c4eb1b38413957236841c631b&v=4
url: https://github.com/pablogamboa
dconathan:
login: dconathan
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/15098095?v=4
url: https://github.com/dconathan
Jamim:
login: Jamim
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/5607572?u=0cf3027bec78ba4f0b89802430c136bc69847d7a&v=4
url: https://github.com/Jamim
svalouch:
login: svalouch
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/54674660?v=4
url: https://github.com/svalouch
frankie567:
login: frankie567
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4
url: https://github.com/frankie567
marier-nico:
login: marier-nico
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/30477068?u=c7df6af853c8f4163d1517814f3e9a0715c82713&v=4
url: https://github.com/marier-nico
Dustyposa:
login: Dustyposa
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
url: https://github.com/Dustyposa
aviramha:
login: aviramha
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/41201924?u=6883cc4fc13a7b2e60d4deddd4be06f9c5287880&v=4
url: https://github.com/aviramha
iwpnd:
login: iwpnd
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6152183?u=ec59396e9437fff488791c5ecdf6d23f1f1ebf3a&v=4
url: https://github.com/iwpnd
raphaelauv:
login: raphaelauv
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
url: https://github.com/raphaelauv
windson:
login: windson
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1826682?u=8b28dcd716c46289f191f8828e01d74edd058bef&v=4
url: https://github.com/windson
sm-Fifteen:
login: sm-Fifteen
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
url: https://github.com/sm-Fifteen
sattosan:
login: sattosan
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4
url: https://github.com/sattosan
michaeloliverx:
login: michaeloliverx
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/55017335?u=e606eb5cc397c07523be47637b1ee796904fbb59&v=4
url: https://github.com/michaeloliverx
voegtlel:
login: voegtlel
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/5764745?u=db8df3d70d427928ab6d7dbfc395a4a7109c1d1b&v=4
url: https://github.com/voegtlel
HarshaLaxman:
login: HarshaLaxman
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/19939186?u=a112f38b0f6b4d4402dc8b51978b5a0b2e5c5970&v=4
url: https://github.com/HarshaLaxman
RunningIkkyu:
login: RunningIkkyu
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4
url: https://github.com/RunningIkkyu
cassiobotaro:
login: cassiobotaro
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/3127847?u=a08022b191ddbd0a6159b2981d9d878b6d5bb71f&v=4
url: https://github.com/cassiobotaro
chenl:
login: chenl
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1677651?u=c618508eaad6d596cea36c8ea784b424288f6857&v=4
url: https://github.com/chenl
retnikt:
login: retnikt
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/24581770?v=4
url: https://github.com/retnikt
yankeexe:
login: yankeexe
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/13623913?u=f970e66421775a8d3cdab89c0c752eaead186f6d&v=4
url: https://github.com/yankeexe
patrickkwang:
login: patrickkwang
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1263870?u=4bf74020e15be490f19ef8322a76eec882220b96&v=4
url: https://github.com/patrickkwang
victorphoenix3:
login: victorphoenix3
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/48182195?u=e4875bd088623cb4ddeb7be194ec54b453aff035&v=4
url: https://github.com/victorphoenix3
davidefiocco:
login: davidefiocco
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4547987?v=4
url: https://github.com/davidefiocco
adriencaccia:
login: adriencaccia
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/19605940?u=980b0b366a02791a5600b2e9f9ac2037679acaa8&v=4
url: https://github.com/adriencaccia
jamescurtin:
login: jamescurtin
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/10189269?u=0b491fc600ca51f41cf1d95b49fa32a3eba1de57&v=4
url: https://github.com/jamescurtin
jmriebold:
login: jmriebold
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6983392?u=4efdc97bf2422dcc7e9ff65b9ff80087c8eb2a20&v=4
url: https://github.com/jmriebold
nukopy:
login: nukopy
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/42367320?u=6061be0bd060506f6d564a8df3ae73fab048cdfe&v=4
url: https://github.com/nukopy
imba-tjd:
login: imba-tjd
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/24759802?u=01e901a4fe004b4b126549d3ff1c4000fe3720b5&v=4
url: https://github.com/imba-tjd
paxcodes:
login: paxcodes
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/13646646?u=e7429cc7ab11211ef762f4cd3efea7db6d9ef036&v=4
url: https://github.com/paxcodes
kaustubhgupta:
login: kaustubhgupta
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/43691873?u=8dd738718ac7ffad4ef31e86b5d780a1141c695d&v=4
url: https://github.com/kaustubhgupta
wakabame:
login: wakabame
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/35513518?u=41ef6b0a55076e5c540620d68fb006e386c2ddb0&v=4
url: https://github.com/wakabame
nzig:
login: nzig
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/7372858?u=e769add36ed73c778cdb136eb10bf96b1e119671&v=4
url: https://github.com/nzig
yezz123:
login: yezz123
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/52716203?u=d7062cbc6eb7671d5dc9cc0e32a24ae335e0f225&v=4
url: https://github.com/yezz123
musicinmybrain:
login: musicinmybrain
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6898909?u=9010312053e7141383b9bdf538036c7f37fbaba0&v=4
url: https://github.com/musicinmybrain
softwarebloat:
login: softwarebloat
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/16540684?v=4
url: https://github.com/softwarebloat
Lancetnik:
login: Lancetnik
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/44573917?u=f9a18be7324333daf9cc314c35c3051f0a20a7a6&v=4
url: https://github.com/Lancetnik
yogabonito:
login: yogabonito
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/7026269?v=4
url: https://github.com/yogabonito
s111d:
login: s111d
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/4954856?v=4
url: https://github.com/s111d
estebanx64:
login: estebanx64
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=45f015f95e1c0f06df602be4ab688d4b854cc8a8&v=4
url: https://github.com/estebanx64
tamird:
login: tamird
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1535036?v=4
url: https://github.com/tamird
rabinlamadong:
login: rabinlamadong
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/170439781?v=4
url: https://github.com/rabinlamadong
AyushSinghal1794:
login: AyushSinghal1794
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/89984761?v=4
url: https://github.com/AyushSinghal1794

View File

@@ -2,57 +2,60 @@ sponsors:
- - login: bump-sh
avatarUrl: https://avatars.githubusercontent.com/u/33217836?v=4
url: https://github.com/bump-sh
- login: porter-dev
avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4
url: https://github.com/porter-dev
- login: Nixtla
avatarUrl: https://avatars.githubusercontent.com/u/79945230?v=4
url: https://github.com/Nixtla
- login: andrew-propelauth
avatarUrl: https://avatars.githubusercontent.com/u/89474256?u=1188c27cb744bbec36447a2cfd4453126b2ddb5c&v=4
url: https://github.com/andrew-propelauth
- login: liblaber
avatarUrl: https://avatars.githubusercontent.com/u/100821118?v=4
url: https://github.com/liblaber
- login: zanfaruqui
avatarUrl: https://avatars.githubusercontent.com/u/104461687?v=4
url: https://github.com/zanfaruqui
- login: Alek99
avatarUrl: https://avatars.githubusercontent.com/u/38776361?u=bd6c163fe787c2de1a26c881598e54b67e2482dd&v=4
url: https://github.com/Alek99
- login: cryptapi
avatarUrl: https://avatars.githubusercontent.com/u/44925437?u=61369138589bc7fee6c417f3fbd50fbd38286cc4&v=4
url: https://github.com/cryptapi
- login: Kong
avatarUrl: https://avatars.githubusercontent.com/u/962416?v=4
url: https://github.com/Kong
- login: codacy
avatarUrl: https://avatars.githubusercontent.com/u/1834093?v=4
url: https://github.com/codacy
- login: blockbee-io
avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4
url: https://github.com/blockbee-io
- login: zuplo
avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4
url: https://github.com/zuplo
- login: render-sponsorships
avatarUrl: https://avatars.githubusercontent.com/u/189296666?v=4
url: https://github.com/render-sponsorships
- login: porter-dev
avatarUrl: https://avatars.githubusercontent.com/u/62078005?v=4
url: https://github.com/porter-dev
- login: scalar
avatarUrl: https://avatars.githubusercontent.com/u/301879?v=4
url: https://github.com/scalar
- - login: ObliviousAI
avatarUrl: https://avatars.githubusercontent.com/u/65656077?v=4
url: https://github.com/ObliviousAI
- - login: databento
avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
url: https://github.com/databento
- login: svix
- - login: svix
avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
url: https://github.com/svix
- login: deepset-ai
avatarUrl: https://avatars.githubusercontent.com/u/51827949?v=4
url: https://github.com/deepset-ai
- login: mikeckennedy
avatarUrl: https://avatars.githubusercontent.com/u/2035561?u=ce6165b799ea3164cb6f5ff54ea08042057442af&v=4
url: https://github.com/mikeckennedy
- login: ndimares
avatarUrl: https://avatars.githubusercontent.com/u/6267663?u=cfb27efde7a7212be8142abb6c058a1aeadb41b1&v=4
url: https://github.com/ndimares
- - login: takashi-yoneya
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
url: https://github.com/takashi-yoneya
- login: stainless-api
avatarUrl: https://avatars.githubusercontent.com/u/88061651?v=4
url: https://github.com/stainless-api
- login: speakeasy-api
avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4
url: https://github.com/speakeasy-api
- login: databento
avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
url: https://github.com/databento
- - login: mercedes-benz
avatarUrl: https://avatars.githubusercontent.com/u/34240465?v=4
url: https://github.com/mercedes-benz
- login: xoflare
avatarUrl: https://avatars.githubusercontent.com/u/74335107?v=4
url: https://github.com/xoflare
- login: marvin-robot
avatarUrl: https://avatars.githubusercontent.com/u/41086007?u=b9fcab402d0cd0aec738b6574fe60855cb0cd36d&v=4
url: https://github.com/marvin-robot
- login: Ponte-Energy-Partners
avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4
url: https://github.com/Ponte-Energy-Partners
- login: BoostryJP
avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
url: https://github.com/BoostryJP
@@ -62,42 +65,63 @@ sponsors:
- - login: Trivie
avatarUrl: https://avatars.githubusercontent.com/u/8161763?v=4
url: https://github.com/Trivie
- - login: americanair
avatarUrl: https://avatars.githubusercontent.com/u/12281813?v=4
url: https://github.com/americanair
- - login: takashi-yoneya
avatarUrl: https://avatars.githubusercontent.com/u/33813153?u=2d0522bceba0b8b69adf1f2db866503bd96f944e&v=4
url: https://github.com/takashi-yoneya
- - login: mainframeindustries
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
url: https://github.com/mainframeindustries
- login: CanoaPBC
avatarUrl: https://avatars.githubusercontent.com/u/64223768?v=4
url: https://github.com/CanoaPBC
- login: mainframeindustries
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
url: https://github.com/mainframeindustries
- login: mangualero
avatarUrl: https://avatars.githubusercontent.com/u/3422968?u=c59272d7b5a912d6126fd6c6f17db71e20f506eb&v=4
url: https://github.com/mangualero
- login: birkjernstrom
avatarUrl: https://avatars.githubusercontent.com/u/281715?u=4be14b43f76b4bd497b1941309bb390250b405e6&v=4
url: https://github.com/birkjernstrom
- login: yasyf
avatarUrl: https://avatars.githubusercontent.com/u/709645?u=f36736b3c6a85f578886ecc42a740e7b436e7a01&v=4
url: https://github.com/yasyf
- - login: genzou9201
avatarUrl: https://avatars.githubusercontent.com/u/42960762?u=1ca6c18c59e8b327ae584c545b72de31ebc05275&v=4
url: https://github.com/genzou9201
- - login: primer-io
avatarUrl: https://avatars.githubusercontent.com/u/62146168?v=4
url: https://github.com/primer-io
- login: povilasb
avatarUrl: https://avatars.githubusercontent.com/u/1213442?u=b11f58ed6ceea6e8297c9b310030478ebdac894d&v=4
url: https://github.com/povilasb
- - login: jhundman
avatarUrl: https://avatars.githubusercontent.com/u/24263908?v=4
url: https://github.com/jhundman
- login: upciti
- - login: upciti
avatarUrl: https://avatars.githubusercontent.com/u/43346262?v=4
url: https://github.com/upciti
- login: freddiev4
avatarUrl: https://avatars.githubusercontent.com/u/8339018?u=1aad5b4f5a04cb750852b843d5e1d8f4ce339c2e&v=4
url: https://github.com/freddiev4
- - login: samuelcolvin
avatarUrl: https://avatars.githubusercontent.com/u/4039449?u=42eb3b833047c8c4b4f647a031eaef148c16d93f&v=4
url: https://github.com/samuelcolvin
- login: Kludex
avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=62adc405ef418f4b6c8caa93d3eb8ab107bc4927&v=4
url: https://github.com/Kludex
- login: vincentkoc
avatarUrl: https://avatars.githubusercontent.com/u/25068?u=cbf098fc04c0473523d373b0dd2145b4ec99ef93&v=4
url: https://github.com/vincentkoc
- login: ProteinQure
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
url: https://github.com/ProteinQure
- login: ddilidili
avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4
url: https://github.com/ddilidili
- login: otosky
avatarUrl: https://avatars.githubusercontent.com/u/42260747?u=69d089387c743d89427aa4ad8740cfb34045a9e0&v=4
url: https://github.com/otosky
- login: mjohnsey
avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
url: https://github.com/mjohnsey
- login: ashi-agrawal
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
url: https://github.com/ashi-agrawal
- login: sepsi77
avatarUrl: https://avatars.githubusercontent.com/u/18682303?v=4
url: https://github.com/sepsi77
- login: RaamEEIL
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
url: https://github.com/RaamEEIL
- login: jhundman
avatarUrl: https://avatars.githubusercontent.com/u/24263908?v=4
url: https://github.com/jhundman
- login: b-rad-c
avatarUrl: https://avatars.githubusercontent.com/u/25362581?u=5bb10629f4015b62bec1f9a366675d5085551af9&v=4
url: https://github.com/b-rad-c
@@ -105,7 +129,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4
url: https://github.com/ehaca
- login: raphaellaude
avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=9ae4b158c0d2cb29ebd46df6b6edb7de08a67566&v=4
avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=28faad3e62250ef91a0c3c5d0faba39592d9ab39&v=4
url: https://github.com/raphaellaude
- login: timlrx
avatarUrl: https://avatars.githubusercontent.com/u/28362229?u=9a745ca31372ee324af682715ae88ce8522f9094&v=4
@@ -116,78 +140,51 @@ sponsors:
- login: ygorpontelo
avatarUrl: https://avatars.githubusercontent.com/u/32963605?u=35f7103f9c4c4c2589ae5737ee882e9375ef072e&v=4
url: https://github.com/ygorpontelo
- login: ProteinQure
avatarUrl: https://avatars.githubusercontent.com/u/33707203?v=4
url: https://github.com/ProteinQure
- login: catherinenelson1
avatarUrl: https://avatars.githubusercontent.com/u/11951946?u=e714b957185b8cf3d301cced7fc3ad2842122c6a&v=4
url: https://github.com/catherinenelson1
- login: jsoques
avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
url: https://github.com/jsoques
- login: joeds13
avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4
url: https://github.com/joeds13
- login: dannywade
avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
url: https://github.com/dannywade
- login: khadrawy
avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
url: https://github.com/khadrawy
- login: mjohnsey
avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
url: https://github.com/mjohnsey
- login: ashi-agrawal
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
url: https://github.com/ashi-agrawal
- login: sepsi77
avatarUrl: https://avatars.githubusercontent.com/u/18682303?v=4
url: https://github.com/sepsi77
- login: wedwardbeck
avatarUrl: https://avatars.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
url: https://github.com/wedwardbeck
- login: RaamEEIL
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
url: https://github.com/RaamEEIL
- login: anthonycepeda
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
url: https://github.com/anthonycepeda
- login: patricioperezv
avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
url: https://github.com/patricioperezv
- login: chickenandstats
avatarUrl: https://avatars.githubusercontent.com/u/79477966?v=4
url: https://github.com/chickenandstats
- login: kaoru0310
avatarUrl: https://avatars.githubusercontent.com/u/80977929?u=1b61d10142b490e56af932ddf08a390fae8ee94f&v=4
url: https://github.com/kaoru0310
- login: DelfinaCare
avatarUrl: https://avatars.githubusercontent.com/u/83734439?v=4
url: https://github.com/DelfinaCare
- login: Eruditis
- login: Karine-Bauch
avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4
url: https://github.com/Karine-Bauch
- login: eruditis
avatarUrl: https://avatars.githubusercontent.com/u/95244703?v=4
url: https://github.com/Eruditis
url: https://github.com/eruditis
- login: jugeeem
avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=ae590d79c38ac79c91b9c5caa6887d061e865a3d&v=4
url: https://github.com/jugeeem
- login: apitally
avatarUrl: https://avatars.githubusercontent.com/u/138365043?v=4
url: https://github.com/apitally
- login: logic-automation
avatarUrl: https://avatars.githubusercontent.com/u/144732884?v=4
url: https://github.com/logic-automation
- login: ddilidili
avatarUrl: https://avatars.githubusercontent.com/u/42176885?u=c0a849dde06987434653197b5f638d3deb55fc6c&v=4
url: https://github.com/ddilidili
- login: Torqsight-Labs
avatarUrl: https://avatars.githubusercontent.com/u/169598176?v=4
url: https://github.com/Torqsight-Labs
- login: ramonalmeidam
avatarUrl: https://avatars.githubusercontent.com/u/45269580?u=3358750b3a5854d7c3ed77aaca7dd20a0f529d32&v=4
url: https://github.com/ramonalmeidam
- login: roboflow
avatarUrl: https://avatars.githubusercontent.com/u/53104118?v=4
url: https://github.com/roboflow
- login: dudikbender
avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=3a57542938ebfd57579a0111db2b297e606d9681&v=4
url: https://github.com/dudikbender
- login: prodhype
avatarUrl: https://avatars.githubusercontent.com/u/60444672?u=3f278cff25ea37ead487d7861d4a984795de819e&v=4
url: https://github.com/prodhype
- login: patsatsia
avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4
url: https://github.com/patsatsia
- login: anthonycepeda
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
url: https://github.com/anthonycepeda
- login: patricioperezv
avatarUrl: https://avatars.githubusercontent.com/u/73832292?u=5f471f156e19ee7920e62ae0f4a47b95580e61cf&v=4
url: https://github.com/patricioperezv
- login: mintuhouse
avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
url: https://github.com/mintuhouse
- login: tcsmith
avatarUrl: https://avatars.githubusercontent.com/u/989034?u=7d8d741552b3279e8f4d3878679823a705a46f8f&v=4
url: https://github.com/tcsmith
@@ -200,9 +197,6 @@ sponsors:
- login: knallgelb
avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4
url: https://github.com/knallgelb
- login: johannquerne
avatarUrl: https://avatars.githubusercontent.com/u/2736484?u=9b3381546a25679913a2b08110e4373c98840821&v=4
url: https://github.com/johannquerne
- login: Shark009
avatarUrl: https://avatars.githubusercontent.com/u/3163309?u=0c6f4091b0eda05c44c390466199826e6dc6e431&v=4
url: https://github.com/Shark009
@@ -215,15 +209,18 @@ sponsors:
- login: kennywakeland
avatarUrl: https://avatars.githubusercontent.com/u/3631417?u=7c8f743f1ae325dfadea7c62bbf1abd6a824fc55&v=4
url: https://github.com/kennywakeland
- login: simw
avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
url: https://github.com/simw
- login: koconder
avatarUrl: https://avatars.githubusercontent.com/u/25068?u=582657b23622aaa3dfe68bd028a780f272f456fa&v=4
url: https://github.com/koconder
- login: aacayaco
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
url: https://github.com/aacayaco
- login: anomaly
avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
url: https://github.com/anomaly
- login: jstanden
avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4
url: https://github.com/jstanden
- login: paulcwatts
avatarUrl: https://avatars.githubusercontent.com/u/150269?u=1819e145d573b44f0ad74b87206d21cd60331d4e&v=4
url: https://github.com/paulcwatts
- login: andreaso
avatarUrl: https://avatars.githubusercontent.com/u/285964?u=837265cc7562c0685f25b2d81cd9de0434fe107c&v=4
url: https://github.com/andreaso
@@ -239,36 +236,36 @@ sponsors:
- login: wshayes
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
- login: gaetanBloch
avatarUrl: https://avatars.githubusercontent.com/u/583199?u=50c49e83d6b4feb78a091901ea02ead1462f442b&v=4
url: https://github.com/gaetanBloch
- login: koxudaxi
avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
url: https://github.com/koxudaxi
- login: falkben
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
url: https://github.com/falkben
- login: mintuhouse
avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
url: https://github.com/mintuhouse
- login: Rehket
avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
url: https://github.com/Rehket
- login: hiancdtrsnm
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
url: https://github.com/hiancdtrsnm
- login: TrevorBenson
avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=afdd1766fdb79e04e59094cc6a54cd011ee7f686&v=4
avatarUrl: https://avatars.githubusercontent.com/u/9167887?u=dccbea3327a57750923333d8ebf1a0b3f1948949&v=4
url: https://github.com/TrevorBenson
- login: wdwinslow
avatarUrl: https://avatars.githubusercontent.com/u/11562137?u=dc01daafb354135603a263729e3d26d939c0c452&v=4
url: https://github.com/wdwinslow
- login: aacayaco
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
url: https://github.com/aacayaco
- login: anomaly
avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
url: https://github.com/anomaly
- login: jgreys
avatarUrl: https://avatars.githubusercontent.com/u/4136890?u=096820d1ef89877d57d0f68e669ead8b0fde84df&v=4
url: https://github.com/jgreys
- login: catherinenelson1
avatarUrl: https://avatars.githubusercontent.com/u/11951946?u=fe11bc35d36b6038cd46a946e4e46ef8aa5688ab&v=4
url: https://github.com/catherinenelson1
- login: jsoques
avatarUrl: https://avatars.githubusercontent.com/u/12414216?u=620921d94196546cc8b9eae2cc4cbc3f95bab42f&v=4
url: https://github.com/jsoques
- login: joeds13
avatarUrl: https://avatars.githubusercontent.com/u/13631604?u=628eb122e08bef43767b3738752b883e8e7f6259&v=4
url: https://github.com/joeds13
- login: dannywade
avatarUrl: https://avatars.githubusercontent.com/u/13680237?u=418ee985bd41577b20fde81417fb2d901e875e8a&v=4
url: https://github.com/dannywade
- login: khadrawy
avatarUrl: https://avatars.githubusercontent.com/u/13686061?u=59f25ef42ecf04c22657aac4238ce0e2d3d30304&v=4
url: https://github.com/khadrawy
- login: Ryandaydev
avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=48f68868db8886fce31a1d802c1003914c6cd7c6&v=4
url: https://github.com/Ryandaydev
@@ -290,12 +287,42 @@ sponsors:
- login: FernandoCelmer
avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=d29fff3fd862fda4ca752079f13f32e84c762ea4&v=4
url: https://github.com/FernandoCelmer
- - login: getsentry
avatarUrl: https://avatars.githubusercontent.com/u/1396951?v=4
url: https://github.com/getsentry
- login: simw
avatarUrl: https://avatars.githubusercontent.com/u/6322526?v=4
url: https://github.com/simw
- login: Rehket
avatarUrl: https://avatars.githubusercontent.com/u/7015688?u=3afb0ba200feebbc7f958950e92db34df2a3c172&v=4
url: https://github.com/Rehket
- login: hiancdtrsnm
avatarUrl: https://avatars.githubusercontent.com/u/7343177?v=4
url: https://github.com/hiancdtrsnm
- - login: pawamoy
avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
url: https://github.com/pawamoy
- login: engineerjoe440
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
url: https://github.com/engineerjoe440
- login: bnkc
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4
url: https://github.com/bnkc
- login: petercool
avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=81c525232bb35780945a68e88afd96bb2cdad9c4&v=4
url: https://github.com/petercool
- login: siavashyj
avatarUrl: https://avatars.githubusercontent.com/u/43583410?u=562005ddc7901cd27a1219a118a2363817b14977&v=4
url: https://github.com/siavashyj
- login: mobyw
avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4
url: https://github.com/mobyw
- login: ArtyomVancyan
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
url: https://github.com/ArtyomVancyan
- login: TheR1D
avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
url: https://github.com/TheR1D
- login: joshuatz
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
url: https://github.com/joshuatz
- login: SebTota
avatarUrl: https://avatars.githubusercontent.com/u/25122511?v=4
url: https://github.com/SebTota
@@ -311,87 +338,30 @@ sponsors:
- login: rlnchow
avatarUrl: https://avatars.githubusercontent.com/u/28018479?u=a93ca9cf1422b9ece155784a72d5f2fdbce7adff&v=4
url: https://github.com/rlnchow
- login: engineerjoe440
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
url: https://github.com/engineerjoe440
- login: bnkc
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=db5e6f4f87836cad26c2aa90ce390ce49041c5a9&v=4
url: https://github.com/bnkc
- login: DevOpsKev
avatarUrl: https://avatars.githubusercontent.com/u/36336550?u=6ccd5978fdaab06f37e22f2a14a7439341df7f67&v=4
url: https://github.com/DevOpsKev
- login: petercool
avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=81c525232bb35780945a68e88afd96bb2cdad9c4&v=4
url: https://github.com/petercool
- login: JimFawkes
avatarUrl: https://avatars.githubusercontent.com/u/12075115?u=dc58ecfd064d72887c34bf500ddfd52592509acd&v=4
url: https://github.com/JimFawkes
- login: artempronevskiy
avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4
url: https://github.com/artempronevskiy
- login: TheR1D
avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
url: https://github.com/TheR1D
- login: joshuatz
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
url: https://github.com/joshuatz
- login: jangia
avatarUrl: https://avatars.githubusercontent.com/u/17927101?u=9261b9bb0c3e3bb1ecba43e8915dc58d8c9a077e&v=4
url: https://github.com/jangia
- login: jackleeio
avatarUrl: https://avatars.githubusercontent.com/u/20477587?u=c5184dab6d021733d10c8f975b20e391856303d6&v=4
url: https://github.com/jackleeio
- login: shuheng-liu
avatarUrl: https://avatars.githubusercontent.com/u/22414322?u=813c45f30786c6b511b21a661def025d8f7b609e&v=4
url: https://github.com/shuheng-liu
- login: pers0n4
avatarUrl: https://avatars.githubusercontent.com/u/24864600?u=f211a13a7b572cbbd7779b9c8d8cb428cc7ba07e&v=4
url: https://github.com/pers0n4
- login: curegit
avatarUrl: https://avatars.githubusercontent.com/u/37978051?u=1733c322079118c0cdc573c03d92813f50a9faec&v=4
url: https://github.com/curegit
- login: fernandosmither
avatarUrl: https://avatars.githubusercontent.com/u/66154723?u=f79753eb207d01cca5bbb91ac62db6123e7622d1&v=4
url: https://github.com/fernandosmither
- login: PunRabbit
avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
url: https://github.com/PunRabbit
- login: PelicanQ
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
url: https://github.com/PelicanQ
- login: tahmarrrr23
avatarUrl: https://avatars.githubusercontent.com/u/138208610?u=465a46b0ff72a74252d3e3a71ac7d2f1919cda28&v=4
url: https://github.com/tahmarrrr23
- login: zk-Call
avatarUrl: https://avatars.githubusercontent.com/u/147117264?v=4
url: https://github.com/zk-Call
- login: kristiangronberg
avatarUrl: https://avatars.githubusercontent.com/u/42678548?v=4
url: https://github.com/kristiangronberg
- login: leonardo-holguin
avatarUrl: https://avatars.githubusercontent.com/u/43093055?u=b59013d52fb6c4e0954aaaabc0882bd844985b38&v=4
url: https://github.com/leonardo-holguin
- login: arrrrrmin
avatarUrl: https://avatars.githubusercontent.com/u/43553423?u=36a3880a6eb29309c19e6cadbb173bafbe91deb1&v=4
url: https://github.com/arrrrrmin
- login: mobyw
avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4
url: https://github.com/mobyw
- login: ArtyomVancyan
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
url: https://github.com/ArtyomVancyan
- login: harol97
avatarUrl: https://avatars.githubusercontent.com/u/49042862?u=2b18e115ab73f5f09a280be2850f93c58a12e3d2&v=4
url: https://github.com/harol97
- login: dvlpjrs
avatarUrl: https://avatars.githubusercontent.com/u/32254642?u=fbd6ad0324d4f1eb6231cf775be1c7bd4404e961&v=4
url: https://github.com/dvlpjrs
- login: caviri
avatarUrl: https://avatars.githubusercontent.com/u/45425937?u=4e14bd64282bad8f385eafbdb004b5a279366d6e&v=4
url: https://github.com/caviri
- login: hgalytoby
avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=62c7ff3519858423579676cd0efbd7e3f1ffe63a&v=4
url: https://github.com/hgalytoby
- login: conservative-dude
avatarUrl: https://avatars.githubusercontent.com/u/55538308?u=f250c44942ea6e73a6bd90739b381c470c192c11&v=4
url: https://github.com/conservative-dude
- login: Joaopcamposs
avatarUrl: https://avatars.githubusercontent.com/u/57376574?u=699d5ba5ee66af1d089df6b5e532b97169e73650&v=4
url: https://github.com/Joaopcamposs
- login: CR1337
avatarUrl: https://avatars.githubusercontent.com/u/62649536?u=57a6aab10d2421a497306da8bcded01b826c54ae&v=4
url: https://github.com/CR1337
- login: PunRabbit
avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
url: https://github.com/PunRabbit
- login: PelicanQ
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
url: https://github.com/PelicanQ
- login: tochikuji
avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
url: https://github.com/tochikuji
- login: browniebroke
avatarUrl: https://avatars.githubusercontent.com/u/861044?u=5abfca5588f3e906b31583d7ee62f6de4b68aa24&v=4
url: https://github.com/browniebroke
@@ -407,9 +377,12 @@ sponsors:
- login: leobiscassi
avatarUrl: https://avatars.githubusercontent.com/u/1977418?u=f9f82445a847ab479bd7223debd677fcac6c49a0&v=4
url: https://github.com/leobiscassi
- login: cbonoz
avatarUrl: https://avatars.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
url: https://github.com/cbonoz
- login: Alisa-lisa
avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
url: https://github.com/Alisa-lisa
- login: Graeme22
avatarUrl: https://avatars.githubusercontent.com/u/4185684?u=498182a42300d7bcd4de1215190cb17eb501136c&v=4
url: https://github.com/Graeme22
- login: ddanier
avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4
url: https://github.com/ddanier
@@ -419,33 +392,15 @@ sponsors:
- login: slafs
avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4
url: https://github.com/slafs
- login: adamghill
avatarUrl: https://avatars.githubusercontent.com/u/317045?u=f1349d5ffe84a19f324e204777859fbf69ddf633&v=4
url: https://github.com/adamghill
- login: ceb10n
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
- login: eteq
avatarUrl: https://avatars.githubusercontent.com/u/346587?v=4
url: https://github.com/eteq
- login: dmig
avatarUrl: https://avatars.githubusercontent.com/u/388564?v=4
url: https://github.com/dmig
- login: securancy
avatarUrl: https://avatars.githubusercontent.com/u/606673?v=4
url: https://github.com/securancy
- login: tochikuji
avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
url: https://github.com/tochikuji
- login: KentShikama
avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
url: https://github.com/KentShikama
- login: katnoria
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
url: https://github.com/katnoria
- login: harsh183
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
url: https://github.com/harsh183
- login: hcristea
avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4
url: https://github.com/hcristea
- login: moonape1226
avatarUrl: https://avatars.githubusercontent.com/u/8532038?u=d9f8b855a429fff9397c3833c2ff83849ebf989d&v=4
url: https://github.com/moonape1226
@@ -453,7 +408,7 @@ sponsors:
avatarUrl: https://avatars.githubusercontent.com/u/9369632?u=8c988f1b008a3f601385a3616f9327820f66e3a5&v=4
url: https://github.com/msehnout
- login: xncbf
avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=2ef1ede118a72c170805f50b9ad07341fd16a354&v=4
avatarUrl: https://avatars.githubusercontent.com/u/9462045?u=a80a7bb349555b277645632ed66639ff43400614&v=4
url: https://github.com/xncbf
- login: DMantis
avatarUrl: https://avatars.githubusercontent.com/u/9536869?v=4
@@ -464,9 +419,6 @@ sponsors:
- login: supdann
avatarUrl: https://avatars.githubusercontent.com/u/9986994?u=9671810f4ae9504c063227fee34fd47567ff6954&v=4
url: https://github.com/supdann
- login: satwikkansal
avatarUrl: https://avatars.githubusercontent.com/u/10217535?u=b12d6ef74ea297de9e46da6933b1a5b7ba9e6a61&v=4
url: https://github.com/satwikkansal
- login: mntolia
avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4
url: https://github.com/mntolia
@@ -479,17 +431,14 @@ sponsors:
- login: Zuzah
avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4
url: https://github.com/Zuzah
- login: Alisa-lisa
avatarUrl: https://avatars.githubusercontent.com/u/4137964?u=e7e393504f554f4ff15863a1e01a5746863ef9ce&v=4
url: https://github.com/Alisa-lisa
- login: Graeme22
avatarUrl: https://avatars.githubusercontent.com/u/4185684?u=498182a42300d7bcd4de1215190cb17eb501136c&v=4
url: https://github.com/Graeme22
- login: artempronevskiy
avatarUrl: https://avatars.githubusercontent.com/u/12235104?u=03df6e1e55c9c6fe5d230adabb8dd7d43d8bbe8f&v=4
url: https://github.com/artempronevskiy
- login: danielunderwood
avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
url: https://github.com/danielunderwood
- login: rangulvers
avatarUrl: https://avatars.githubusercontent.com/u/5235430?v=4
avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4
url: https://github.com/rangulvers
- login: sdevkota
avatarUrl: https://avatars.githubusercontent.com/u/5250987?u=4ed9a120c89805a8aefda1cbdc0cf6512e64d1b4&v=4
@@ -500,33 +449,42 @@ sponsors:
- login: Baghdady92
avatarUrl: https://avatars.githubusercontent.com/u/5708590?v=4
url: https://github.com/Baghdady92
- login: jakeecolution
avatarUrl: https://avatars.githubusercontent.com/u/5884696?u=4a7c7883fb064b593b50cb6697b54687e6f7aafe&v=4
url: https://github.com/jakeecolution
- login: stephane-rbn
avatarUrl: https://avatars.githubusercontent.com/u/5939522?u=eb7ffe768fa3bcbcd04de14fe4a47444cc00ec4c&v=4
url: https://github.com/stephane-rbn
- - login: danburonline
avatarUrl: https://avatars.githubusercontent.com/u/34251194?u=94935cccfbec58083ab1e535212d54f1bf2c978a&v=4
url: https://github.com/danburonline
- login: AliYmn
avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=0de5a262e8b4dc0a08d065f30f7a39941e246530&v=4
url: https://github.com/AliYmn
- login: sadikkuzu
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
url: https://github.com/sadikkuzu
- login: tran-hai-long
avatarUrl: https://avatars.githubusercontent.com/u/119793901?u=3b173a845dcf099b275bdc9713a69cbbc36040ce&v=4
url: https://github.com/tran-hai-long
- login: KentShikama
avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
url: https://github.com/KentShikama
- login: katnoria
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
url: https://github.com/katnoria
- login: harsh183
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
url: https://github.com/harsh183
- login: hcristea
avatarUrl: https://avatars.githubusercontent.com/u/7814406?u=61d7a4fcf846983a4606788eac25e1c6c1209ba8&v=4
url: https://github.com/hcristea
- - login: larsyngvelundin
avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4
url: https://github.com/larsyngvelundin
- login: andrecorumba
avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4
url: https://github.com/andrecorumba
- login: rwxd
avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4
url: https://github.com/rwxd
- login: sadikkuzu
avatarUrl: https://avatars.githubusercontent.com/u/23168063?u=d179c06bb9f65c4167fcab118526819f8e0dac17&v=4
url: https://github.com/sadikkuzu
- login: Olegt0rr
avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4
url: https://github.com/Olegt0rr
- login: FabulousCodingFox
avatarUrl: https://avatars.githubusercontent.com/u/78906517?u=924a27cbee3db7e0ece5cc1509921402e1445e74&v=4
url: https://github.com/FabulousCodingFox
- login: anqorithm
avatarUrl: https://avatars.githubusercontent.com/u/61029571?u=468256fa4e2d9ce2870b608299724bebb7a33f18&v=4
url: https://github.com/anqorithm
- login: ssbarnea
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c2efbf6fea2737e21dfc6b1113c4edc9644e9eaa&v=4
avatarUrl: https://avatars.githubusercontent.com/u/102495?u=c7bd9ddf127785286fc939dd18cb02db0a453bce&v=4
url: https://github.com/ssbarnea
- login: yuawn
avatarUrl: https://avatars.githubusercontent.com/u/5111198?u=5315576f3fe1a70fd2d0f02181588f4eea5d353d&v=4
url: https://github.com/yuawn
- login: dongzhenye
avatarUrl: https://avatars.githubusercontent.com/u/5765843?u=fe420c9a4c41e5b060faaf44029f5485616b470d&v=4
url: https://github.com/dongzhenye
- login: andreagrandi
avatarUrl: https://avatars.githubusercontent.com/u/636391?u=13d90cb8ec313593a5b71fbd4e33b78d6da736f5&v=4
url: https://github.com/andreagrandi

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
- tiangolo
- codecov
- github-actions
- pre-commit-ci
- dependabot

View File

@@ -29,7 +29,12 @@ logins:
- andrew-propelauth
- svix
- zuplo-oss
- zuplo
- Kong
- speakeasy-api
- jess-render
- blockbee-io
- liblaber
- render-sponsorships
- renderinc
- stainless-api

View File

@@ -0,0 +1,495 @@
- name: full-stack-fastapi-template
html_url: https://github.com/fastapi/full-stack-fastapi-template
stars: 28796
owner_login: fastapi
owner_html_url: https://github.com/fastapi
- name: Hello-Python
html_url: https://github.com/mouredev/Hello-Python
stars: 27554
owner_login: mouredev
owner_html_url: https://github.com/mouredev
- name: serve
html_url: https://github.com/jina-ai/serve
stars: 21225
owner_login: jina-ai
owner_html_url: https://github.com/jina-ai
- name: sqlmodel
html_url: https://github.com/fastapi/sqlmodel
stars: 14921
owner_login: fastapi
owner_html_url: https://github.com/fastapi
- name: HivisionIDPhotos
html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos
stars: 14025
owner_login: Zeyi-Lin
owner_html_url: https://github.com/Zeyi-Lin
- name: Douyin_TikTok_Download_API
html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API
stars: 10001
owner_login: Evil0ctal
owner_html_url: https://github.com/Evil0ctal
- name: fastapi-best-practices
html_url: https://github.com/zhanymkanov/fastapi-best-practices
stars: 9820
owner_login: zhanymkanov
owner_html_url: https://github.com/zhanymkanov
- name: awesome-fastapi
html_url: https://github.com/mjhea0/awesome-fastapi
stars: 8899
owner_login: mjhea0
owner_html_url: https://github.com/mjhea0
- name: FastUI
html_url: https://github.com/pydantic/FastUI
stars: 8400
owner_login: pydantic
owner_html_url: https://github.com/pydantic
- name: nonebot2
html_url: https://github.com/nonebot/nonebot2
stars: 6235
owner_login: nonebot
owner_html_url: https://github.com/nonebot
- name: serge
html_url: https://github.com/serge-chat/serge
stars: 5685
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: 4787
owner_login: fastapi-users
owner_html_url: https://github.com/fastapi-users
- name: FileCodeBox
html_url: https://github.com/vastsa/FileCodeBox
stars: 4479
owner_login: vastsa
owner_html_url: https://github.com/vastsa
- name: hatchet
html_url: https://github.com/hatchet-dev/hatchet
stars: 4413
owner_login: hatchet-dev
owner_html_url: https://github.com/hatchet-dev
- name: chatgpt-web-share
html_url: https://github.com/chatpire/chatgpt-web-share
stars: 4322
owner_login: chatpire
owner_html_url: https://github.com/chatpire
- name: atrilabs-engine
html_url: https://github.com/Atri-Labs/atrilabs-engine
stars: 4115
owner_login: Atri-Labs
owner_html_url: https://github.com/Atri-Labs
- name: strawberry
html_url: https://github.com/strawberry-graphql/strawberry
stars: 4084
owner_login: strawberry-graphql
owner_html_url: https://github.com/strawberry-graphql
- name: dynaconf
html_url: https://github.com/dynaconf/dynaconf
stars: 3844
owner_login: dynaconf
owner_html_url: https://github.com/dynaconf
- name: poem
html_url: https://github.com/poem-web/poem
stars: 3698
owner_login: poem-web
owner_html_url: https://github.com/poem-web
- name: polar
html_url: https://github.com/polarsource/polar
stars: 3355
owner_login: polarsource
owner_html_url: https://github.com/polarsource
- name: opyrator
html_url: https://github.com/ml-tooling/opyrator
stars: 3114
owner_login: ml-tooling
owner_html_url: https://github.com/ml-tooling
- name: farfalle
html_url: https://github.com/rashadphz/farfalle
stars: 3022
owner_login: rashadphz
owner_html_url: https://github.com/rashadphz
- name: fastapi-admin
html_url: https://github.com/fastapi-admin/fastapi-admin
stars: 3002
owner_login: fastapi-admin
owner_html_url: https://github.com/fastapi-admin
- name: docarray
html_url: https://github.com/docarray/docarray
stars: 2998
owner_login: docarray
owner_html_url: https://github.com/docarray
- name: datamodel-code-generator
html_url: https://github.com/koxudaxi/datamodel-code-generator
stars: 2845
owner_login: koxudaxi
owner_html_url: https://github.com/koxudaxi
- name: fastapi-realworld-example-app
html_url: https://github.com/nsidnev/fastapi-realworld-example-app
stars: 2832
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: 2727
owner_login: tiangolo
owner_html_url: https://github.com/tiangolo
- name: WrenAI
html_url: https://github.com/Canner/WrenAI
stars: 2699
owner_login: Canner
owner_html_url: https://github.com/Canner
- name: LitServe
html_url: https://github.com/Lightning-AI/LitServe
stars: 2664
owner_login: Lightning-AI
owner_html_url: https://github.com/Lightning-AI
- name: logfire
html_url: https://github.com/pydantic/logfire
stars: 2495
owner_login: pydantic
owner_html_url: https://github.com/pydantic
- name: huma
html_url: https://github.com/danielgtaylor/huma
stars: 2479
owner_login: danielgtaylor
owner_html_url: https://github.com/danielgtaylor
- name: tracecat
html_url: https://github.com/TracecatHQ/tracecat
stars: 2446
owner_login: TracecatHQ
owner_html_url: https://github.com/TracecatHQ
- name: RasaGPT
html_url: https://github.com/paulpierre/RasaGPT
stars: 2378
owner_login: paulpierre
owner_html_url: https://github.com/paulpierre
- name: best-of-web-python
html_url: https://github.com/ml-tooling/best-of-web-python
stars: 2374
owner_login: ml-tooling
owner_html_url: https://github.com/ml-tooling
- name: fastapi-react
html_url: https://github.com/Buuntu/fastapi-react
stars: 2274
owner_login: Buuntu
owner_html_url: https://github.com/Buuntu
- name: nextpy
html_url: https://github.com/dot-agent/nextpy
stars: 2244
owner_login: dot-agent
owner_html_url: https://github.com/dot-agent
- name: 30-Days-of-Python
html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python
stars: 2154
owner_login: codingforentrepreneurs
owner_html_url: https://github.com/codingforentrepreneurs
- name: FastAPI-template
html_url: https://github.com/s3rius/FastAPI-template
stars: 2067
owner_login: s3rius
owner_html_url: https://github.com/s3rius
- name: langserve
html_url: https://github.com/langchain-ai/langserve
stars: 1980
owner_login: langchain-ai
owner_html_url: https://github.com/langchain-ai
- name: sqladmin
html_url: https://github.com/aminalaee/sqladmin
stars: 1980
owner_login: aminalaee
owner_html_url: https://github.com/aminalaee
- name: fastapi-utils
html_url: https://github.com/fastapiutils/fastapi-utils
stars: 1970
owner_login: fastapiutils
owner_html_url: https://github.com/fastapiutils
- name: solara
html_url: https://github.com/widgetti/solara
stars: 1950
owner_login: widgetti
owner_html_url: https://github.com/widgetti
- name: python-week-2022
html_url: https://github.com/rochacbruno/python-week-2022
stars: 1836
owner_login: rochacbruno
owner_html_url: https://github.com/rochacbruno
- name: supabase-py
html_url: https://github.com/supabase/supabase-py
stars: 1803
owner_login: supabase
owner_html_url: https://github.com/supabase
- name: mangum
html_url: https://github.com/Kludex/mangum
stars: 1760
owner_login: Kludex
owner_html_url: https://github.com/Kludex
- name: manage-fastapi
html_url: https://github.com/ycd/manage-fastapi
stars: 1704
owner_login: ycd
owner_html_url: https://github.com/ycd
- name: ormar
html_url: https://github.com/collerek/ormar
stars: 1688
owner_login: collerek
owner_html_url: https://github.com/collerek
- name: agentkit
html_url: https://github.com/BCG-X-Official/agentkit
stars: 1615
owner_login: BCG-X-Official
owner_html_url: https://github.com/BCG-X-Official
- name: langchain-serve
html_url: https://github.com/jina-ai/langchain-serve
stars: 1615
owner_login: jina-ai
owner_html_url: https://github.com/jina-ai
- name: termpair
html_url: https://github.com/cs01/termpair
stars: 1613
owner_login: cs01
owner_html_url: https://github.com/cs01
- name: coronavirus-tracker-api
html_url: https://github.com/ExpDev07/coronavirus-tracker-api
stars: 1591
owner_login: ExpDev07
owner_html_url: https://github.com/ExpDev07
- name: piccolo
html_url: https://github.com/piccolo-orm/piccolo
stars: 1477
owner_login: piccolo-orm
owner_html_url: https://github.com/piccolo-orm
- name: fastapi-crudrouter
html_url: https://github.com/awtkns/fastapi-crudrouter
stars: 1435
owner_login: awtkns
owner_html_url: https://github.com/awtkns
- name: fastapi-cache
html_url: https://github.com/long2ice/fastapi-cache
stars: 1412
owner_login: long2ice
owner_html_url: https://github.com/long2ice
- name: openapi-python-client
html_url: https://github.com/openapi-generators/openapi-python-client
stars: 1398
owner_login: openapi-generators
owner_html_url: https://github.com/openapi-generators
- name: awesome-fastapi-projects
html_url: https://github.com/Kludex/awesome-fastapi-projects
stars: 1386
owner_login: Kludex
owner_html_url: https://github.com/Kludex
- name: awesome-python-resources
html_url: https://github.com/DjangoEx/awesome-python-resources
stars: 1371
owner_login: DjangoEx
owner_html_url: https://github.com/DjangoEx
- name: budgetml
html_url: https://github.com/ebhy/budgetml
stars: 1342
owner_login: ebhy
owner_html_url: https://github.com/ebhy
- name: slowapi
html_url: https://github.com/laurentS/slowapi
stars: 1289
owner_login: laurentS
owner_html_url: https://github.com/laurentS
- name: fastapi-pagination
html_url: https://github.com/uriyyo/fastapi-pagination
stars: 1240
owner_login: uriyyo
owner_html_url: https://github.com/uriyyo
- name: fastapi-boilerplate
html_url: https://github.com/teamhide/fastapi-boilerplate
stars: 1173
owner_login: teamhide
owner_html_url: https://github.com/teamhide
- name: fastapi-tutorial
html_url: https://github.com/liaogx/fastapi-tutorial
stars: 1162
owner_login: liaogx
owner_html_url: https://github.com/liaogx
- name: fastapi-amis-admin
html_url: https://github.com/amisadmin/fastapi-amis-admin
stars: 1118
owner_login: amisadmin
owner_html_url: https://github.com/amisadmin
- name: fastapi-code-generator
html_url: https://github.com/koxudaxi/fastapi-code-generator
stars: 1095
owner_login: koxudaxi
owner_html_url: https://github.com/koxudaxi
- name: bolt-python
html_url: https://github.com/slackapi/bolt-python
stars: 1086
owner_login: slackapi
owner_html_url: https://github.com/slackapi
- name: odmantic
html_url: https://github.com/art049/odmantic
stars: 1085
owner_login: art049
owner_html_url: https://github.com/art049
- name: langchain-extract
html_url: https://github.com/langchain-ai/langchain-extract
stars: 1068
owner_login: langchain-ai
owner_html_url: https://github.com/langchain-ai
- name: fastapi_production_template
html_url: https://github.com/zhanymkanov/fastapi_production_template
stars: 1059
owner_login: zhanymkanov
owner_html_url: https://github.com/zhanymkanov
- name: fastapi-alembic-sqlmodel-async
html_url: https://github.com/jonra1993/fastapi-alembic-sqlmodel-async
stars: 1031
owner_login: jonra1993
owner_html_url: https://github.com/jonra1993
- name: prometheus-fastapi-instrumentator
html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator
stars: 1013
owner_login: trallnag
owner_html_url: https://github.com/trallnag
- name: runhouse
html_url: https://github.com/run-house/runhouse
stars: 988
owner_login: run-house
owner_html_url: https://github.com/run-house
- name: lanarky
html_url: https://github.com/ajndkr/lanarky
stars: 982
owner_login: ajndkr
owner_html_url: https://github.com/ajndkr
- name: autollm
html_url: https://github.com/viddexa/autollm
stars: 981
owner_login: viddexa
owner_html_url: https://github.com/viddexa
- name: bedrock-claude-chat
html_url: https://github.com/aws-samples/bedrock-claude-chat
stars: 977
owner_login: aws-samples
owner_html_url: https://github.com/aws-samples
- name: SurfSense
html_url: https://github.com/MODSetter/SurfSense
stars: 971
owner_login: MODSetter
owner_html_url: https://github.com/MODSetter
- name: restish
html_url: https://github.com/danielgtaylor/restish
stars: 954
owner_login: danielgtaylor
owner_html_url: https://github.com/danielgtaylor
- name: secure
html_url: https://github.com/TypeError/secure
stars: 911
owner_login: TypeError
owner_html_url: https://github.com/TypeError
- name: langcorn
html_url: https://github.com/msoedov/langcorn
stars: 909
owner_login: msoedov
owner_html_url: https://github.com/msoedov
- name: energy-forecasting
html_url: https://github.com/iusztinpaul/energy-forecasting
stars: 884
owner_login: iusztinpaul
owner_html_url: https://github.com/iusztinpaul
- name: vue-fastapi-admin
html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin
stars: 863
owner_login: mizhexiaoxiao
owner_html_url: https://github.com/mizhexiaoxiao
- name: authx
html_url: https://github.com/yezz123/authx
stars: 850
owner_login: yezz123
owner_html_url: https://github.com/yezz123
- name: titiler
html_url: https://github.com/developmentseed/titiler
stars: 809
owner_login: developmentseed
owner_html_url: https://github.com/developmentseed
- name: marker-api
html_url: https://github.com/adithya-s-k/marker-api
stars: 792
owner_login: adithya-s-k
owner_html_url: https://github.com/adithya-s-k
- name: fastapi_best_architecture
html_url: https://github.com/fastapi-practices/fastapi_best_architecture
stars: 742
owner_login: fastapi-practices
owner_html_url: https://github.com/fastapi-practices
- name: fastapi-mail
html_url: https://github.com/sabuhish/fastapi-mail
stars: 728
owner_login: sabuhish
owner_html_url: https://github.com/sabuhish
- name: fastcrud
html_url: https://github.com/igorbenav/fastcrud
stars: 727
owner_login: igorbenav
owner_html_url: https://github.com/igorbenav
- name: annotated-py-projects
html_url: https://github.com/hhstore/annotated-py-projects
stars: 722
owner_login: hhstore
owner_html_url: https://github.com/hhstore
- name: FastAPI-boilerplate
html_url: https://github.com/igorbenav/FastAPI-boilerplate
stars: 716
owner_login: igorbenav
owner_html_url: https://github.com/igorbenav
- name: lccn_predictor
html_url: https://github.com/baoliay2008/lccn_predictor
stars: 707
owner_login: baoliay2008
owner_html_url: https://github.com/baoliay2008
- name: chatGPT-web
html_url: https://github.com/mic1on/chatGPT-web
stars: 706
owner_login: mic1on
owner_html_url: https://github.com/mic1on
- name: fastapi-do-zero
html_url: https://github.com/dunossauro/fastapi-do-zero
stars: 702
owner_login: dunossauro
owner_html_url: https://github.com/dunossauro
- name: linbing
html_url: https://github.com/taomujian/linbing
stars: 699
owner_login: taomujian
owner_html_url: https://github.com/taomujian
- name: fastapi-observability
html_url: https://github.com/blueswen/fastapi-observability
stars: 698
owner_login: blueswen
owner_html_url: https://github.com/blueswen
- name: FastAPI-Backend-Template
html_url: https://github.com/Aeternalis-Ingenium/FastAPI-Backend-Template
stars: 682
owner_login: Aeternalis-Ingenium
owner_html_url: https://github.com/Aeternalis-Ingenium
- name: learn-generative-ai
html_url: https://github.com/panaverse/learn-generative-ai
stars: 673
owner_login: panaverse
owner_html_url: https://github.com/panaverse
- name: fastapi-jwt-auth
html_url: https://github.com/IndominusByte/fastapi-jwt-auth
stars: 668
owner_login: IndominusByte
owner_html_url: https://github.com/IndominusByte
- name: pity
html_url: https://github.com/wuranxu/pity
stars: 660
owner_login: wuranxu
owner_html_url: https://github.com/wuranxu
- name: starlette-admin
html_url: https://github.com/jowilf/starlette-admin
stars: 653
owner_login: jowilf
owner_html_url: https://github.com/jowilf
- name: fastapi_login
html_url: https://github.com/MushroomMaula/fastapi_login
stars: 650
owner_login: MushroomMaula
owner_html_url: https://github.com/MushroomMaula

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,490 @@
nilslindemann:
login: nilslindemann
count: 120
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
jaystone776:
login: jaystone776
count: 46
avatarUrl: https://avatars.githubusercontent.com/u/11191137?u=299205a95e9b6817a43144a48b643346a5aac5cc&v=4
url: https://github.com/jaystone776
tokusumi:
login: tokusumi
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
url: https://github.com/tokusumi
SwftAlpc:
login: SwftAlpc
count: 23
avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
url: https://github.com/SwftAlpc
hasansezertasan:
login: hasansezertasan
count: 22
avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4
url: https://github.com/hasansezertasan
ceb10n:
login: ceb10n
count: 22
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
waynerv:
login: waynerv
count: 20
avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
url: https://github.com/waynerv
AlertRED:
login: AlertRED
count: 16
avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4
url: https://github.com/AlertRED
hard-coders:
login: hard-coders
count: 15
avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=95db33927bbff1ed1c07efddeb97ac2ff33068ed&v=4
url: https://github.com/hard-coders
codingjenny:
login: codingjenny
count: 14
avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4
url: https://github.com/codingjenny
Xewus:
login: Xewus
count: 13
avatarUrl: https://avatars.githubusercontent.com/u/85196001?u=f8e2dc7e5104f109cef944af79050ea8d1b8f914&v=4
url: https://github.com/Xewus
Joao-Pedro-P-Holanda:
login: Joao-Pedro-P-Holanda
count: 12
avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4
url: https://github.com/Joao-Pedro-P-Holanda
Smlep:
login: Smlep
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/16785985?u=ffe99fa954c8e774ef1117e58d34aece92051e27&v=4
url: https://github.com/Smlep
marcelomarkus:
login: marcelomarkus
count: 11
avatarUrl: https://avatars.githubusercontent.com/u/20115018?u=dda090ce9160ef0cd2ff69b1e5ea741283425cba&v=4
url: https://github.com/marcelomarkus
KaniKim:
login: KaniKim
count: 10
avatarUrl: https://avatars.githubusercontent.com/u/19832624?u=296dbdd490e0eb96e3d45a2608c065603b17dc31&v=4
url: https://github.com/KaniKim
Vincy1230:
login: Vincy1230
count: 9
avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4
url: https://github.com/Vincy1230
rjNemo:
login: rjNemo
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4
url: https://github.com/rjNemo
xzmeng:
login: xzmeng
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/40202897?v=4
url: https://github.com/xzmeng
pablocm83:
login: pablocm83
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4
url: https://github.com/pablocm83
Zhongheng-Cheng:
login: Zhongheng-Cheng
count: 8
avatarUrl: https://avatars.githubusercontent.com/u/95612344?u=a0f7730a3cc7486827965e01a119ad610bda4b0a&v=4
url: https://github.com/Zhongheng-Cheng
batlopes:
login: batlopes
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/33462923?u=0fb3d7acb316764616f11e4947faf080e49ad8d9&v=4
url: https://github.com/batlopes
lucasbalieiro:
login: lucasbalieiro
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/37416577?u=5a395a69384e7fa0f9840ea32ef963d3f1cd9da4&v=4
url: https://github.com/lucasbalieiro
Alexandrhub:
login: Alexandrhub
count: 6
avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4
url: https://github.com/Alexandrhub
Serrones:
login: Serrones
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
url: https://github.com/Serrones
RunningIkkyu:
login: RunningIkkyu
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/31848542?u=494ecc298e3f26197495bb357ad0f57cfd5f7a32&v=4
url: https://github.com/RunningIkkyu
Attsun1031:
login: Attsun1031
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/1175560?v=4
url: https://github.com/Attsun1031
NinaHwang:
login: NinaHwang
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/79563565?u=241f2cb6d38a2d379536608a8ea5a22ed4b1a3ea&v=4
url: https://github.com/NinaHwang
tiangolo:
login: tiangolo
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4
url: https://github.com/tiangolo
rostik1410:
login: rostik1410
count: 5
avatarUrl: https://avatars.githubusercontent.com/u/11443899?u=e26a635c2ba220467b308a326a579b8ccf4a8701&v=4
url: https://github.com/rostik1410
komtaki:
login: komtaki
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/39375566?u=260ad6b1a4b34c07dbfa728da5e586f16f6d1824&v=4
url: https://github.com/komtaki
JulianMaurin:
login: JulianMaurin
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/63545168?u=b7d15ac865268cbefc2d739e2f23d9aeeac1a622&v=4
url: https://github.com/JulianMaurin
stlucasgarcia:
login: stlucasgarcia
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/61513630?u=c22d8850e9dc396a8820766a59837f967e14f9a0&v=4
url: https://github.com/stlucasgarcia
ComicShrimp:
login: ComicShrimp
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/43503750?u=d2fbf412e7730183ce91686ca48d4147e1b7dc74&v=4
url: https://github.com/ComicShrimp
BilalAlpaslan:
login: BilalAlpaslan
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/47563997?u=63ed66e304fe8d765762c70587d61d9196e5c82d&v=4
url: https://github.com/BilalAlpaslan
axel584:
login: axel584
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/1334088?u=9667041f5b15dc002b6f9665fda8c0412933ac04&v=4
url: https://github.com/axel584
tamtam-fitness:
login: tamtam-fitness
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/62091034?u=8da19a6bd3d02f5d6ba30c7247d5b46c98dd1403&v=4
url: https://github.com/tamtam-fitness
Limsunoh:
login: Limsunoh
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/90311848?u=f456e0c5709fd50c8cd2898b551558eda14e5f21&v=4
url: https://github.com/Limsunoh
kwang1215:
login: kwang1215
count: 4
avatarUrl: https://avatars.githubusercontent.com/u/74170199?u=2a63ff6692119dde3f5e5693365b9fcd6f977b08&v=4
url: https://github.com/kwang1215
jfunez:
login: jfunez
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/805749?v=4
url: https://github.com/jfunez
ycd:
login: ycd
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=29682e4b6ac7d5293742ccf818188394b9a82972&v=4
url: https://github.com/ycd
mariacamilagl:
login: mariacamilagl
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
url: https://github.com/mariacamilagl
maoyibo:
login: maoyibo
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/7887703?v=4
url: https://github.com/maoyibo
blt232018:
login: blt232018
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/43393471?u=172b0e0391db1aa6c1706498d6dfcb003c8a4857&v=4
url: https://github.com/blt232018
magiskboy:
login: magiskboy
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/13352088?u=18b6d672523f9e9d98401f31dd50e28bb27d826f&v=4
url: https://github.com/magiskboy
luccasmmg:
login: luccasmmg
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/11317382?u=65099a5a0d492b89119471f8a7014637cc2e04da&v=4
url: https://github.com/luccasmmg
lbmendes:
login: lbmendes
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/80999926?u=646619e2f07ac5a7c3f65fe7834197461a4fff9f&v=4
url: https://github.com/lbmendes
Zssaer:
login: Zssaer
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/45691504?u=4c0c195f25cb5ac6af32acfb0ab35427682938d2&v=4
url: https://github.com/Zssaer
wdh99:
login: wdh99
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/108172295?u=8a8fb95d5afe3e0fa33257b2aecae88d436249eb&v=4
url: https://github.com/wdh99
ChuyuChoyeon:
login: ChuyuChoyeon
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/129537877?u=f0c76f3327817a8b86b422d62e04a34bf2827f2b&v=4
url: https://github.com/ChuyuChoyeon
ivan-abc:
login: ivan-abc
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/36765187?u=c6e0ba571c1ccb6db9d94e62e4b8b5eda811a870&v=4
url: https://github.com/ivan-abc
mojtabapaso:
login: mojtabapaso
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/121169359?u=ced1d5ad673bcd9e949ebf967a4ab50185637443&v=4
url: https://github.com/mojtabapaso
hsuanchi:
login: hsuanchi
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/24913710?u=0b094ae292292fee093818e37ceb645c114d2bff&v=4
url: https://github.com/hsuanchi
alejsdev:
login: alejsdev
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=356f39ff3f0211c720b06d3dbb060e98884085e3&v=4
url: https://github.com/alejsdev
riroan:
login: riroan
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/33053284?u=2d18e3771506ee874b66d6aa2b3b1107fd95c38f&v=4
url: https://github.com/riroan
nayeonkinn:
login: nayeonkinn
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/98254573?u=64a75ac99b320d4935eff8d1fceea9680fa07473&v=4
url: https://github.com/nayeonkinn
pe-brian:
login: pe-brian
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/1783138?u=7e6242eb9e85bcf673fa88bbac9dd6dc3f03b1b5&v=4
url: https://github.com/pe-brian
maxscheijen:
login: maxscheijen
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/47034840?u=eb98f37882528ea349ca4e5255fa64ac3fef0294&v=4
url: https://github.com/maxscheijen
ilacftemp:
login: ilacftemp
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/159066669?v=4
url: https://github.com/ilacftemp
devluisrodrigues:
login: devluisrodrigues
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/103431660?u=d9674a3249edc4601d2c712cdebf899918503c3a&v=4
url: https://github.com/devluisrodrigues
devfernandoa:
login: devfernandoa
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/28360583?u=c4308abd62e8847c9e572e1bb9fe6b9dc9ef8e50&v=4
url: https://github.com/devfernandoa
kim-sangah:
login: kim-sangah
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/173775778?v=4
url: https://github.com/kim-sangah
9zimin9:
login: 9zimin9
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/174453744?v=4
url: https://github.com/9zimin9
nahyunkeem:
login: nahyunkeem
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/174440096?u=e12401d492eee58570f8914d0872b52e421a776e&v=4
url: https://github.com/nahyunkeem
alv2017:
login: alv2017
count: 3
avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4
url: https://github.com/alv2017
izaguerreiro:
login: izaguerreiro
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/2241504?v=4
url: https://github.com/izaguerreiro
Xaraxx:
login: Xaraxx
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/29824698?u=dde2e233e22bb5ca1f8bb0c6e353ccd0d06e6066&v=4
url: https://github.com/Xaraxx
sh0nk:
login: sh0nk
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/6478810?u=af15d724875cec682ed8088a86d36b2798f981c0&v=4
url: https://github.com/sh0nk
dukkee:
login: dukkee
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/36825394?u=ccfd86e6a4f2d093dad6f7544cc875af67fa2df8&v=4
url: https://github.com/dukkee
oandersonmagalhaes:
login: oandersonmagalhaes
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/83456692?v=4
url: https://github.com/oandersonmagalhaes
leandrodesouzadev:
login: leandrodesouzadev
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/85115541?u=4eb25f43f1fe23727d61e986cf83b73b86e2a95a&v=4
url: https://github.com/leandrodesouzadev
kty4119:
login: kty4119
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/49435654?v=4
url: https://github.com/kty4119
ASpathfinder:
login: ASpathfinder
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/31813636?u=2090bd1b7abb65cfeff0c618f99f11afa82c0548&v=4
url: https://github.com/ASpathfinder
jujumilk3:
login: jujumilk3
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/41659814?u=538f7dfef03b59f25e43f10d59a31c19ef538a0c&v=4
url: https://github.com/jujumilk3
ayr-ton:
login: ayr-ton
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1090517?u=5cf70a0e0f0dbf084e074e494aa94d7c91a46ba6&v=4
url: https://github.com/ayr-ton
KdHyeon0661:
login: KdHyeon0661
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/20253352?u=5ae1aae34b091a39f22cbe60a02b79dcbdbea031&v=4
url: https://github.com/KdHyeon0661
LorhanSohaky:
login: LorhanSohaky
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/16273730?u=095b66f243a2cd6a0aadba9a095009f8aaf18393&v=4
url: https://github.com/LorhanSohaky
cfraboulet:
login: cfraboulet
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/62244267?u=ed0e286ba48fa1dafd64a08e50f3364b8e12df34&v=4
url: https://github.com/cfraboulet
dedkot01:
login: dedkot01
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/26196675?u=e2966887124e67932853df4f10f86cb526edc7b0&v=4
url: https://github.com/dedkot01
AGolicyn:
login: AGolicyn
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/86262613?u=3c21606ab8d210a061a1673decff1e7d5592b380&v=4
url: https://github.com/AGolicyn
fhabers21:
login: fhabers21
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/58401847?v=4
url: https://github.com/fhabers21
TabarakoAkula:
login: TabarakoAkula
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/113298631?u=add801e370dbc502cd94ce6d3484760d7fef5406&v=4
url: https://github.com/TabarakoAkula
AhsanSheraz:
login: AhsanSheraz
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/51913596?u=08e31cacb3048be30722c94010ddd028f3fdbec4&v=4
url: https://github.com/AhsanSheraz
ArtemKhymenko:
login: ArtemKhymenko
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/14346625?u=f2fa553d9e5ec5e0f05d66bd649f7be347169631&v=4
url: https://github.com/ArtemKhymenko
hasnatsajid:
login: hasnatsajid
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/86589885?u=49958789e6385be624f2c6a55a860c599eb05e2c&v=4
url: https://github.com/hasnatsajid
alperiox:
login: alperiox
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/34214152?u=2c5acad3461d4dbc2d48371ba86cac56ae9b25cc&v=4
url: https://github.com/alperiox
emrhnsyts:
login: emrhnsyts
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/42899027?u=ad26798e3f8feed2041c5dd5f87e58933d6c3283&v=4
url: https://github.com/emrhnsyts
vusallyv:
login: vusallyv
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/85983771?u=53a7b755cb338d9313966dbf2e4e68b512565186&v=4
url: https://github.com/vusallyv
jackleeio:
login: jackleeio
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/20477587?u=c5184dab6d021733d10c8f975b20e391856303d6&v=4
url: https://github.com/jackleeio
choi-haram:
login: choi-haram
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/62204475?v=4
url: https://github.com/choi-haram
imtiaz101325:
login: imtiaz101325
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/54007087?u=7a210ee38a0a30b7536226419b3b799620ad57d9&v=4
url: https://github.com/imtiaz101325
waketzheng:
login: waketzheng
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/35413830?u=df19e4fd5bb928e7d086e053ef26a46aad23bf84&v=4
url: https://github.com/waketzheng
billzhong:
login: billzhong
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1644011?v=4
url: https://github.com/billzhong
chaoless:
login: chaoless
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/64477804?v=4
url: https://github.com/chaoless
logan2d5:
login: logan2d5
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/146642263?u=dbd6621f8b0330d6919f6a7131277b92e26fbe87&v=4
url: https://github.com/logan2d5
andersonrocha0:
login: andersonrocha0
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/22346169?u=93a1359c8c5461d894802c0cc65bcd09217e7a02&v=4
url: https://github.com/andersonrocha0
saeye:
login: saeye
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/62229734?v=4
url: https://github.com/saeye
timothy-jeong:
login: timothy-jeong
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/53824764?u=659311b6f6aeb0fbb8b527723fd4c83642f04327&v=4
url: https://github.com/timothy-jeong
gerry-sabar:
login: gerry-sabar
count: 2
avatarUrl: https://avatars.githubusercontent.com/u/1120123?v=4
url: https://github.com/gerry-sabar

View File

@@ -7,45 +7,33 @@ In short, use `fastapi run` to serve your FastAPI application:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<font color="#4E9A06">╭─────────── FastAPI CLI - Production mode ───────────╮</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Serving at: http://0.0.0.0:8000 │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ API docs: http://0.0.0.0:8000/docs │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Running in production mode, for development use: │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ </font><font color="#8AE234"><b>fastapi dev</b></font><font color="#4E9A06"> │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">╰─────────────────────────────────────────────────────╯</font>
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2306215</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8000</b> (Press CTRL+C to quit)
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
```
</div>

View File

@@ -36,56 +36,43 @@ If you use the `fastapi` command:
<div class="termy">
```console
$ <pre> <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u>
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
Searching for package file structure from directories with
<font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
following code:
<font color="#4E9A06">╭─────────── FastAPI CLI - Production mode ───────────╮</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Serving at: http://0.0.0.0:8000 │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ API docs: http://0.0.0.0:8000/docs │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Running in production mode, for development use: │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ </font><font color="#8AE234"><b>fastapi dev</b></font><font color="#4E9A06"> │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">╰─────────────────────────────────────────────────────╯</font>
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8000</b> (Press CTRL+C to quit)
<font color="#4E9A06">INFO</font>: Started parent process [<font color="#34E2E2"><b>27365</b></font>]
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27368</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27369</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27370</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27367</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
</pre>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to
quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>

View File

@@ -28,9 +28,12 @@ If you have an article, project, tool, or anything related to **FastAPI** that i
{% endfor %}
{% endfor %}
## Projects
## GitHub Repositories
Latest GitHub projects with the topic `fastapi`:
Most starred GitHub repositories with the topic `fastapi`:
<div class="github-topic-projects">
</div>
{% for repo in topic_repos %}
<a href={{repo.html_url}} target="_blank">★ {{repo.stars}} - {{repo.name}}</a> by <a href={{repo.owner_html_url}} target="_blank">@{{repo.owner_login}}</a>.
{% endfor %}

View File

@@ -13,15 +13,13 @@ Hey! 👋
This is me:
{% if people %}
<div class="user-list user-list-center">
{% for user in people.maintainers %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Answers: {{ user.answers }}</div><div class="count">Pull Requests: {{ user.prs }}</div></div>
<div class="user"><a href="{{ contributors.tiangolo.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ contributors.tiangolo.avatarUrl }}"/></div><div class="title">@{{ contributors.tiangolo.login }}</div></a> <div class="count">Answers: {{ user.answers }}</div><div class="count">Pull Requests: {{ contributors.tiangolo.count }}</div></div>
{% endfor %}
</div>
{% endif %}
I'm the creator of **FastAPI**. You can read more about that in [Help FastAPI - Get Help - Connect with the author](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}.
@@ -49,9 +47,11 @@ This is the current list of team members. 😎
They have different levels of involvement and permissions, they can perform [repository management tasks](./management-tasks.md){.internal-link target=_blank} and together we [manage the FastAPI repository](./management.md){.internal-link target=_blank}.
<div class="user-list user-list-center">
{% for user in members["members"] %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatar_url }}"/></div><div class="title">@{{ user.login }}</div></a></div>
{% endfor %}
</div>
@@ -84,57 +84,73 @@ You can see the **FastAPI Experts** for:
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last month. 🤓
{% if people %}
<div class="user-list user-list-center">
{% for user in people.last_month_experts[:10] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
{% endif %}
### FastAPI Experts - 3 Months
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 3 months. 😎
{% if people %}
<div class="user-list user-list-center">
{% for user in people.three_months_experts[:10] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
{% endif %}
### FastAPI Experts - 6 Months
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last 6 months. 🧐
{% if people %}
<div class="user-list user-list-center">
{% for user in people.six_months_experts[:10] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
{% endif %}
### FastAPI Experts - 1 Year
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} during the last year. 🧑‍🔬
{% if people %}
<div class="user-list user-list-center">
{% for user in people.one_year_experts[:20] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
{% endif %}
### FastAPI Experts - All Time
@@ -142,15 +158,19 @@ Here are the all time **FastAPI Experts**. 🤓🤯
These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github){.internal-link target=_blank} through *all time*. 🧙
{% if people %}
<div class="user-list user-list-center">
{% for user in people.experts[:50] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
{% endif %}
## Top Contributors
@@ -158,19 +178,43 @@ Here are the **Top Contributors**. 👷
These users have [created the most Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} that have been *merged*.
They have contributed source code, documentation, translations, etc. 📦
They have contributed source code, documentation, etc. 📦
{% if people %}
<div class="user-list user-list-center">
{% for user in people.top_contributors[:50] %}
{% for user in (contributors.values() | list)[:50] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Pull Requests: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
There are hundreds of other contributors, you can see them all in the <a href="https://github.com/fastapi/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷
## Top Translators
These are the **Top Translators**. 🌐
These users have created the most Pull Requests with [translations to other languages](contributing.md#translations){.internal-link target=_blank} that have been *merged*.
<div class="user-list user-list-center">
{% for user in (translators.values() | list)[:50] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Translations: {{ user.count }}</div></div>
{% endif %}
There are many other contributors (more than a hundred), you can see them all in the <a href="https://github.com/fastapi/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷
{% endfor %}
</div>
## Top Translation Reviewers
@@ -178,15 +222,18 @@ These users are the **Top Translation Reviewers**. 🕵️
I only speak a few languages (and not very well 😅). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
{% if people %}
<div class="user-list user-list-center">
{% for user in people.top_translations_reviewers[:50] %}
{% for user in (translation_reviewers.values() | list)[:50] %}
{% if user.login not in skip_users %}
<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Reviews: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
{% endif %}
## Sponsors
@@ -251,7 +298,7 @@ The main intention of this page is to highlight the effort of the community to h
Especially including efforts that are normally less visible, and in many cases more arduous, like helping others with questions and reviewing Pull Requests with translations.
The data is calculated each month, you can read the <a href="https://github.com/fastapi/fastapi/blob/master/.github/actions/people/app/main.py" class="external-link" target="_blank">source code here</a>.
The data is calculated each month, you can read the <a href="https://github.com/fastapi/fastapi/blob/master/scripts/" class="external-link" target="_blank">source code here</a>.
Here I'm also highlighting contributions from sponsors.

View File

@@ -35,7 +35,7 @@ Depending on your use case, you might prefer to use a different library, but if
Here's a small preview of how you could integrate Strawberry with FastAPI:
{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *}
{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *}
You can learn more about Strawberry in the <a href="https://strawberry.rocks/" class="external-link" target="_blank">Strawberry documentation</a>.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,25 +1,3 @@
const div = document.querySelector('.github-topic-projects')
async function getDataBatch(page) {
const response = await fetch(`https://api.github.com/search/repositories?q=topic:fastapi&per_page=100&page=${page}`, { headers: { Accept: 'application/vnd.github.mercy-preview+json' } })
const data = await response.json()
return data
}
async function getData() {
let page = 1
let data = []
let dataBatch = await getDataBatch(page)
data = data.concat(dataBatch.items)
const totalCount = dataBatch.total_count
while (data.length < totalCount) {
page += 1
dataBatch = await getDataBatch(page)
data = data.concat(dataBatch.items)
}
return data
}
function setupTermynal() {
document.querySelectorAll(".use-termynal").forEach(node => {
node.style.display = "block";
@@ -158,20 +136,6 @@ async function showRandomAnnouncement(groupId, timeInterval) {
}
async function main() {
if (div) {
data = await getData()
div.innerHTML = '<ul></ul>'
const ul = document.querySelector('.github-topic-projects ul')
data.forEach(v => {
if (v.full_name === 'fastapi/fastapi') {
return
}
const li = document.createElement('li')
li.innerHTML = `<a href="${v.html_url}" target="_blank">★ ${v.stargazers_count} - ${v.full_name}</a> by <a href="${v.owner.html_url}" target="_blank">@${v.owner.login}</a>`
ul.append(li)
})
}
setupTermynal();
showRandomAnnouncement('announce-left', 5000)
showRandomAnnouncement('announce-right', 10000)

View File

@@ -7,8 +7,82 @@ hide:
## Latest Changes
## 0.115.8
### Fixes
* 🐛 Fix `OAuth2PasswordRequestForm` and `OAuth2PasswordRequestFormStrict` fixed `grant_type` "password" RegEx. PR [#9783](https://github.com/fastapi/fastapi/pull/9783) by [@skarfie123](https://github.com/skarfie123).
### Refactors
* ✅ Simplify tests for body_multiple_params . PR [#13237](https://github.com/fastapi/fastapi/pull/13237) by [@alejsdev](https://github.com/alejsdev).
* ♻️ Move duplicated code portion to a static method in the `APIKeyBase` super class. PR [#3142](https://github.com/fastapi/fastapi/pull/3142) by [@ShahriyarR](https://github.com/ShahriyarR).
* ✅ Simplify tests for request_files. PR [#13182](https://github.com/fastapi/fastapi/pull/13182) by [@alejsdev](https://github.com/alejsdev).
### Docs
* 📝 Change the word "unwrap" to "unpack" in `docs/en/docs/tutorial/extra-models.md`. PR [#13061](https://github.com/fastapi/fastapi/pull/13061) by [@timothy-jeong](https://github.com/timothy-jeong).
* 📝 Update Request Body's `tutorial002` to deal with `tax=0` case. PR [#13230](https://github.com/fastapi/fastapi/pull/13230) by [@togogh](https://github.com/togogh).
* 👥 Update FastAPI People - Experts. PR [#13269](https://github.com/fastapi/fastapi/pull/13269) by [@tiangolo](https://github.com/tiangolo).
### Translations
* 🌐 Add Japanese translation for `docs/ja/docs/environment-variables.md`. PR [#13226](https://github.com/fastapi/fastapi/pull/13226) by [@k94-ishi](https://github.com/k94-ishi).
* 🌐 Add Russian translation for `docs/ru/docs/advanced/async-tests.md`. PR [#13227](https://github.com/fastapi/fastapi/pull/13227) by [@Rishat-F](https://github.com/Rishat-F).
* 🌐 Update Russian translation for `docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md`. PR [#13252](https://github.com/fastapi/fastapi/pull/13252) by [@Rishat-F](https://github.com/Rishat-F).
* 🌐 Add Russian translation for `docs/ru/docs/tutorial/bigger-applications.md`. PR [#13154](https://github.com/fastapi/fastapi/pull/13154) by [@alv2017](https://github.com/alv2017).
### Internal
* ⬆️ Add support for Python 3.13. PR [#13274](https://github.com/fastapi/fastapi/pull/13274) by [@tiangolo](https://github.com/tiangolo).
* ⬆️ Upgrade AnyIO max version for tests, new range: `>=3.2.1,<5.0.0`. PR [#13273](https://github.com/fastapi/fastapi/pull/13273) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update Sponsors badges. PR [#13271](https://github.com/fastapi/fastapi/pull/13271) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Fix `notify_translations.py` empty env var handling for PR label events vs workflow_dispatch. PR [#13272](https://github.com/fastapi/fastapi/pull/13272) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Refactor and move `scripts/notify_translations.py`, no need for a custom GitHub Action. PR [#13270](https://github.com/fastapi/fastapi/pull/13270) by [@tiangolo](https://github.com/tiangolo).
* 🔨 Update FastAPI People Experts script, refactor and optimize data fetching to handle rate limits. PR [#13267](https://github.com/fastapi/fastapi/pull/13267) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4. PR [#13251](https://github.com/fastapi/fastapi/pull/13251) by [@dependabot[bot]](https://github.com/apps/dependabot).
## 0.115.7
### Upgrades
* ⬆️ Upgrade `python-multipart` to >=0.0.18. PR [#13219](https://github.com/fastapi/fastapi/pull/13219) by [@DanielKusyDev](https://github.com/DanielKusyDev).
* ⬆️ Bump Starlette to allow up to 0.45.0: `>=0.40.0,<0.46.0`. PR [#13117](https://github.com/fastapi/fastapi/pull/13117) by [@Kludex](https://github.com/Kludex).
* ⬆️ Upgrade `jinja2` to >=3.1.5. PR [#13194](https://github.com/fastapi/fastapi/pull/13194) by [@DanielKusyDev](https://github.com/DanielKusyDev).
### Refactors
* ✅ Simplify tests for websockets. PR [#13202](https://github.com/fastapi/fastapi/pull/13202) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for request_form_models . PR [#13183](https://github.com/fastapi/fastapi/pull/13183) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for separate_openapi_schemas. PR [#13201](https://github.com/fastapi/fastapi/pull/13201) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for security. PR [#13200](https://github.com/fastapi/fastapi/pull/13200) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for schema_extra_example. PR [#13197](https://github.com/fastapi/fastapi/pull/13197) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for request_model. PR [#13195](https://github.com/fastapi/fastapi/pull/13195) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for request_forms_and_files. PR [#13185](https://github.com/fastapi/fastapi/pull/13185) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for request_forms. PR [#13184](https://github.com/fastapi/fastapi/pull/13184) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for path_query_params. PR [#13181](https://github.com/fastapi/fastapi/pull/13181) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for path_operation_configurations. PR [#13180](https://github.com/fastapi/fastapi/pull/13180) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for header_params. PR [#13179](https://github.com/fastapi/fastapi/pull/13179) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for extra_models. PR [#13178](https://github.com/fastapi/fastapi/pull/13178) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for extra_data_types. PR [#13177](https://github.com/fastapi/fastapi/pull/13177) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for cookie_params. PR [#13176](https://github.com/fastapi/fastapi/pull/13176) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for dependencies. PR [#13174](https://github.com/fastapi/fastapi/pull/13174) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_updates. PR [#13172](https://github.com/fastapi/fastapi/pull/13172) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_nested_models. PR [#13171](https://github.com/fastapi/fastapi/pull/13171) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_multiple_params. PR [#13170](https://github.com/fastapi/fastapi/pull/13170) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body_fields. PR [#13169](https://github.com/fastapi/fastapi/pull/13169) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for body. PR [#13168](https://github.com/fastapi/fastapi/pull/13168) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for bigger_applications. PR [#13167](https://github.com/fastapi/fastapi/pull/13167) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for background_tasks. PR [#13166](https://github.com/fastapi/fastapi/pull/13166) by [@alejsdev](https://github.com/alejsdev).
* ✅ Simplify tests for additional_status_codes. PR [#13149](https://github.com/fastapi/fastapi/pull/13149) by [@tiangolo](https://github.com/tiangolo).
### Docs
* ✏️ Update Strawberry integration docs. PR [#13155](https://github.com/fastapi/fastapi/pull/13155) by [@kinuax](https://github.com/kinuax).
* 🔥 Remove unused Peewee tutorial files. PR [#13158](https://github.com/fastapi/fastapi/pull/13158) by [@alejsdev](https://github.com/alejsdev).
* 📝 Update image in body-nested-model docs. PR [#11063](https://github.com/fastapi/fastapi/pull/11063) by [@untilhamza](https://github.com/untilhamza).
* 📝 Update `fastapi-cli` UI examples in docs. PR [#13107](https://github.com/fastapi/fastapi/pull/13107) by [@Zhongheng-Cheng](https://github.com/Zhongheng-Cheng).
* 👷 Add new GitHub Action to update contributors, translators, and translation reviewers. PR [#13136](https://github.com/fastapi/fastapi/pull/13136) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix typo in `docs/en/docs/virtual-environments.md`. PR [#13124](https://github.com/fastapi/fastapi/pull/13124) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix error in `docs/en/docs/contributing.md`. PR [#12899](https://github.com/fastapi/fastapi/pull/12899) by [@kingsubin](https://github.com/kingsubin).
* 📝 Minor corrections in `docs/en/docs/tutorial/sql-databases.md`. PR [#13081](https://github.com/fastapi/fastapi/pull/13081) by [@alv2017](https://github.com/alv2017).
@@ -18,6 +92,17 @@ hide:
### Translations
* 🌐 Update Portuguese Translation for `docs/pt/docs/tutorial/request-forms.md`. PR [#13216](https://github.com/fastapi/fastapi/pull/13216) by [@Joao-Pedro-P-Holanda](https://github.com/Joao-Pedro-P-Holanda).
* 🌐 Update Portuguese translation for `docs/pt/docs/advanced/settings.md`. PR [#13209](https://github.com/fastapi/fastapi/pull/13209) by [@ceb10n](https://github.com/ceb10n).
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/security/oauth2-jwt.md`. PR [#13205](https://github.com/fastapi/fastapi/pull/13205) by [@ceb10n](https://github.com/ceb10n).
* 🌐 Add Indonesian translation for `docs/id/docs/index.md`. PR [#13191](https://github.com/fastapi/fastapi/pull/13191) by [@gerry-sabar](https://github.com/gerry-sabar).
* 🌐 Add Indonesian translation for `docs/id/docs/tutorial/static-files.md`. PR [#13092](https://github.com/fastapi/fastapi/pull/13092) by [@guspan-tanadi](https://github.com/guspan-tanadi).
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/security/get-current-user.md`. PR [#13188](https://github.com/fastapi/fastapi/pull/13188) by [@ceb10n](https://github.com/ceb10n).
* 🌐 Remove Wrong Portuguese translations location for `docs/pt/docs/advanced/benchmarks.md`. PR [#13187](https://github.com/fastapi/fastapi/pull/13187) by [@ceb10n](https://github.com/ceb10n).
* 🌐 Update Portuguese translations. PR [#13156](https://github.com/fastapi/fastapi/pull/13156) by [@nillvitor](https://github.com/nillvitor).
* 🌐 Update Russian translation for `docs/ru/docs/tutorial/security/first-steps.md`. PR [#13159](https://github.com/fastapi/fastapi/pull/13159) by [@Yarous](https://github.com/Yarous).
* ✏️ Delete unnecessary backspace in `docs/ja/docs/tutorial/path-params-numeric-validations.md`. PR [#12238](https://github.com/fastapi/fastapi/pull/12238) by [@FakeDocument](https://github.com/FakeDocument).
* 🌐 Update Chinese translation for `docs/zh/docs/fastapi-cli.md`. PR [#13102](https://github.com/fastapi/fastapi/pull/13102) by [@Zhongheng-Cheng](https://github.com/Zhongheng-Cheng).
* 🌐 Add new Spanish translations for all docs with new LLM-assisted system using PydanticAI. PR [#13122](https://github.com/fastapi/fastapi/pull/13122) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update existing Spanish translations using the new LLM-assisted system using PydanticAI. PR [#13118](https://github.com/fastapi/fastapi/pull/13118) by [@tiangolo](https://github.com/tiangolo).
* 🌐 Update Chinese translation for `docs/zh/docs/advanced/security/oauth2-scopes.md`. PR [#13110](https://github.com/fastapi/fastapi/pull/13110) by [@ChenPu2002](https://github.com/ChenPu2002).
@@ -62,6 +147,16 @@ hide:
### Internal
* 🔧 Add Pydantic 2 trove classifier. PR [#13199](https://github.com/fastapi/fastapi/pull/13199) by [@johnthagen](https://github.com/johnthagen).
* 👥 Update FastAPI People - Sponsors. PR [#13231](https://github.com/fastapi/fastapi/pull/13231) by [@tiangolo](https://github.com/tiangolo).
* 👷 Refactor FastAPI People Sponsors to use 2 tokens. PR [#13228](https://github.com/fastapi/fastapi/pull/13228) by [@tiangolo](https://github.com/tiangolo).
* 👷 Update token for FastAPI People - Sponsors. PR [#13225](https://github.com/fastapi/fastapi/pull/13225) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add independent CI automation for FastAPI People - Sponsors. PR [#13221](https://github.com/fastapi/fastapi/pull/13221) by [@tiangolo](https://github.com/tiangolo).
* 👷 Add retries to Smokeshow. PR [#13151](https://github.com/fastapi/fastapi/pull/13151) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update Speakeasy sponsor graphic. PR [#13147](https://github.com/fastapi/fastapi/pull/13147) by [@chailandau](https://github.com/chailandau).
* 👥 Update FastAPI GitHub topic repositories. PR [#13146](https://github.com/fastapi/fastapi/pull/13146) by [@tiangolo](https://github.com/tiangolo).
* 👷‍♀️ Add script for GitHub Topic Repositories and update External Links. PR [#13135](https://github.com/fastapi/fastapi/pull/13135) by [@alejsdev](https://github.com/alejsdev).
* 👥 Update FastAPI People - Contributors and Translators. PR [#13145](https://github.com/fastapi/fastapi/pull/13145) by [@tiangolo](https://github.com/tiangolo).
* ⬆ Bump markdown-include-variants from 0.0.3 to 0.0.4. PR [#13129](https://github.com/fastapi/fastapi/pull/13129) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump inline-snapshot from 0.14.0 to 0.18.1. PR [#13132](https://github.com/fastapi/fastapi/pull/13132) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Bump mkdocs-macros-plugin from 1.0.5 to 1.3.7. PR [#13133](https://github.com/fastapi/fastapi/pull/13133) by [@dependabot[bot]](https://github.com/apps/dependabot).

View File

@@ -70,9 +70,9 @@ we would get a Python `dict` with:
}
```
#### Unwrapping a `dict`
#### Unpacking a `dict`
If we take a `dict` like `user_dict` and pass it to a function (or class) with `**user_dict`, Python will "unwrap" it. It will pass the keys and values of the `user_dict` directly as key-value arguments.
If we take a `dict` like `user_dict` and pass it to a function (or class) with `**user_dict`, Python will "unpack" it. It will pass the keys and values of the `user_dict` directly as key-value arguments.
So, continuing with the `user_dict` from above, writing:
@@ -117,11 +117,11 @@ would be equivalent to:
UserInDB(**user_in.dict())
```
...because `user_in.dict()` is a `dict`, and then we make Python "unwrap" it by passing it to `UserInDB` prefixed with `**`.
...because `user_in.dict()` is a `dict`, and then we make Python "unpack" it by passing it to `UserInDB` prefixed with `**`.
So, we get a Pydantic model from the data in another Pydantic model.
#### Unwrapping a `dict` and extra keywords
#### Unpacking a `dict` and extra keywords
And then adding the extra keyword argument `hashed_password=hashed_password`, like in:

View File

@@ -11,47 +11,39 @@ Run the live server:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: [&apos;/home/user/code/awesomeapp&apos;]
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>

View File

@@ -15,48 +15,39 @@ To run any of the examples, copy the code to a file `main.py`, and start `fastap
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: [&apos;/home/user/code/awesomeapp&apos;]
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
</pre>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>

View File

@@ -65,9 +65,14 @@ plugins:
- external_links: ../en/data/external_links.yml
- github_sponsors: ../en/data/github_sponsors.yml
- people: ../en/data/people.yml
- contributors: ../en/data/contributors.yml
- translators: ../en/data/translators.yml
- translation_reviewers: ../en/data/translation_reviewers.yml
- skip_users: ../en/data/skip_users.yml
- members: ../en/data/members.yml
- sponsors_badge: ../en/data/sponsors_badge.yml
- sponsors: ../en/data/sponsors.yml
- topic_repos: ../en/data/topic_repos.yml
redirects:
redirect_maps:
deployment/deta.md: deployment/cloud.md

495
docs/id/docs/index.md Normal file
View File

@@ -0,0 +1,495 @@
# FastAPI
<style>
.md-content .md-typeset h1 { display: none; }
</style>
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>FastAPI, framework performa tinggi, mudah dipelajari, cepat untuk coding, siap untuk pengembangan</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
<img src="https://github.com/fastapi/fastapi/workflows/Test/badge.svg?event=push&branch=master" alt="Test">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/fastapi" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions">
</a>
</p>
---
**Dokumentasi**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
**Kode Sumber**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
---
FastAPI adalah *framework* *web* moderen, cepat (performa-tinggi) untuk membangun API dengan Python berdasarkan tipe petunjuk Python.
Fitur utama FastAPI:
* **Cepat**: Performa sangat tinggi, setara **NodeJS** dan **Go** (berkat Starlette dan Pydantic). [Salah satu *framework* Python tercepat yang ada](#performa).
* **Cepat untuk coding**: Meningkatkan kecepatan pengembangan fitur dari 200% sampai 300%. *
* **Sedikit bug**: Mengurangi hingga 40% kesalahan dari manusia (pemrogram). *
* **Intuitif**: Dukungan editor hebat. <abbr title="juga dikenal otomatis-lengkap, pelengkapan otomatis, kecerdasan">Penyelesaian</abbr> di mana pun. Lebih sedikit *debugging*.
* **Mudah**: Dibuat mudah digunakan dan dipelajari. Sedikit waktu membaca dokumentasi.
* **Ringkas**: Mengurasi duplikasi kode. Beragam fitur dari setiap deklarasi parameter. Lebih sedikit *bug*.
* **Handal**: Dapatkan kode siap-digunakan. Dengan dokumentasi otomatis interaktif.
* **Standar-resmi**: Berdasarkan (kompatibel dengan ) standar umum untuk API: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (sebelumnya disebut Swagger) dan <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
<small>* estimasi berdasarkan pengujian tim internal pengembangan applikasi siap pakai.</small>
## Sponsor
<!-- sponsors -->
{% if sponsors %}
{% for sponsor in sponsors.gold -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor -%}
{%- for sponsor in sponsors.silver -%}
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
{% endfor %}
{% endif %}
<!-- /sponsors -->
<a href="https://fastapi.tiangolo.com/fastapi-people/#sponsors" class="external-link" target="_blank">Sponsor lainnya</a>
## Opini
"_[...] Saya banyak menggunakan **FastAPI** sekarang ini. [...] Saya berencana menggunakannya di semua tim servis ML Microsoft. Beberapa dari mereka sudah mengintegrasikan dengan produk inti *Windows** dan sebagian produk **Office**._"
<div style="text-align: right; margin-right: 10%;">Kabir Khan - <strong>Microsoft</strong> <a href="https://github.com/fastapi/fastapi/pull/26" target="_blank"><small>(ref)</small></a></div>
---
"_Kami adopsi library **FastAPI** untuk membuat server **REST** yang melakukan kueri untuk menghasilkan **prediksi**. [untuk Ludwig]_"
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/" target="_blank"><small>(ref)</small></a></div>
---
"_**Netflix** dengan bangga mengumumkan rilis open-source orkestrasi framework **manajemen krisis** : **Dispatch**! [dibuat dengan **FastAPI**]_"
<div style="text-align: right; margin-right: 10%;">Kevin Glisson, Marc Vilanova, Forest Monsen - <strong>Netflix</strong> <a href="https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072" target="_blank"><small>(ref)</small></a></div>
---
"_Saya sangat senang dengan **FastAPI**. Sangat menyenangkan!_"
<div style="text-align: right; margin-right: 10%;">Brian Okken - <strong><a href="https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855" target="_blank">Python Bytes</a> podcast host</strong> <a href="https://twitter.com/brianokken/status/1112220079972728832" target="_blank"><small>(ref)</small></a></div>
---
"_Jujur, apa yang anda buat sangat solid dan berkualitas. Ini adalah yang saya inginkan di **Hug** - sangat menginspirasi melihat seseorang membuat ini._"
<div style="text-align: right; margin-right: 10%;">Timothy Crosley - <strong><a href="https://github.com/hugapi/hug" target="_blank">Hug</a> creator</strong> <a href="https://news.ycombinator.com/item?id=19455465" target="_blank"><small>(ref)</small></a></div>
---
"_Jika anda ingin mempelajari **framework moderen** untuk membangun REST API, coba **FastAPI** [...] cepat, mudah digunakan dan dipelajari [...]_"
"_Kami sudah pindah ke **FastAPI** untuk **API** kami [...] Saya pikir kamu juga akan suka [...]_"
<div style="text-align: right; margin-right: 10%;">Ines Montani - Matthew Honnibal - <strong><a href="https://explosion.ai" target="_blank">Explosion AI</a> founders - <a href="https://spacy.io" target="_blank">spaCy</a> creators</strong> <a href="https://twitter.com/_inesmontani/status/1144173225322143744" target="_blank"><small>(ref)</small></a> - <a href="https://twitter.com/honnibal/status/1144031421859655680" target="_blank"><small>(ref)</small></a></div>
---
"_Jika anda ingin membuat API Python siap pakai, saya merekomendasikan **FastAPI**. FastAPI **didesain indah**, **mudah digunakan** dan **sangat scalable**, FastAPI adalah **komponen kunci** di strategi pengembangan API pertama kami dan mengatur banyak otomatisasi dan service seperti TAC Engineer kami._"
<div style="text-align: right; margin-right: 10%;">Deon Pillsbury - <strong>Cisco</strong> <a href="https://www.linkedin.com/posts/deonpillsbury_cisco-cx-python-activity-6963242628536487936-trAp/" target="_blank"><small>(ref)</small></a></div>
---
## **Typer**, CLI FastAPI
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
Jika anda mengembangkan app <abbr title="Command Line Interface">CLI</abbr> yang digunakan di terminal bukan sebagai API web, kunjungi <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** adalah saudara kecil FastAPI. Dan ditujukan sebagai **CLI FastAPI**. ⌨️ 🚀
## Prayarat
FastAPI berdiri di pundak raksasa:
* <a href="https://www.starlette.io/" class="external-link" target="_blank">Starlette</a> untuk bagian web.
* <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> untuk bagian data.
## Instalasi
Buat dan aktifkan <a href="https://fastapi.tiangolo.com/virtual-environments/" class="external-link" target="_blank">virtual environment</a> kemudian *install* FastAPI:
<div class="termy">
```console
$ pip install "fastapi[standard]"
---> 100%
```
</div>
**Catatan**: Pastikan anda menulis `"fastapi[standard]"` dengan tanda petik untuk memastikan bisa digunakan di semua *terminal*.
## Contoh
### Buat app
* Buat file `main.py` dengan:
```Python
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
<details markdown="1">
<summary>Atau gunakan <code>async def</code>...</summary>
Jika kode anda menggunakan `async` / `await`, gunakan `async def`:
```Python hl_lines="9 14"
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
```
**Catatan**:
Jika anda tidak paham, kunjungi _"Panduan cepat"_ bagian <a href="https://fastapi.tiangolo.com/async/#in-a-hurry" target="_blank">`async` dan `await` di dokumentasi</a>.
</details>
### Jalankan
Jalankan *server* dengan:
<div class="termy">
```console
$ fastapi dev main.py
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
│ Serving at: http://127.0.0.1:8000 │
│ │
│ API docs: http://127.0.0.1:8000/docs │
│ │
│ Running in development mode, for production use: │
│ │
│ fastapi run │
│ │
╰─────────────────────────────────────────────────────╯
INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [2248755] using WatchFiles
INFO: Started server process [2248757]
INFO: Waiting for application startup.
INFO: Application startup complete.
```
</div>
<details markdown="1">
<summary>Mengenai perintah <code>fastapi dev main.py</code>...</summary>
Perintah `fastapi dev` membaca file `main.py`, memeriksa app **FastAPI** di dalamnya, dan menjalan server dengan <a href="https://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a>.
Secara otomatis, `fastapi dev` akan mengaktifkan *auto-reload* untuk pengembangan lokal.
Informasi lebih lanjut kunjungi <a href="https://fastapi.tiangolo.com/fastapi-cli/" target="_blank">Dokumen FastAPI CLI</a>.
</details>
### Periksa
Buka *browser* di <a href="http://127.0.0.1:8000/items/5?q=somequery" class="external-link" target="_blank">http://127.0.0.1:8000/items/5?q=somequery</a>.
Anda akan melihat respon JSON berikut:
```JSON
{"item_id": 5, "q": "somequery"}
```
Anda telah membuat API:
* Menerima permintaan HTTP di _path_ `/` dan `/items/{item_id}`.
* Kedua _paths_ menerima <em>operasi</em> `GET` (juga disebut _metode_ HTTP).
* _path_ `/items/{item_id}` memiliki _parameter path_ `item_id` yang harus berjenis `int`.
* _path_ `/items/{item_id}` memiliki _query parameter_ `q` berjenis `str`.
### Dokumentasi API interaktif
Sekarang kunjungi <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Anda akan melihat dokumentasi API interaktif otomatis (dibuat oleh <a href="https://github.com/swagger-api/swagger-ui" class="external-link" target="_blank">Swagger UI</a>):
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png)
### Dokumentasi API alternatif
Kemudian kunjungi <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
Anda akan melihat dokumentasi alternatif otomatis (dibuat oleh <a href="https://github.com/Rebilly/ReDoc" class="external-link" target="_blank">ReDoc</a>):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
## Contoh upgrade
Sekarang ubah `main.py` untuk menerima struktur permintaan `PUT`.
Deklarasikan struktur menggunakan tipe standar Python, berkat Pydantic.
```Python hl_lines="4 9-12 25-27"
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Union[bool, None] = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
```
Server `fastapi dev` akan otomatis memuat kembali.
### Upgrade dokumentasi API interaktif
Kunjungi <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
* Dokumentasi API interaktif akan otomatis diperbarui, termasuk kode yang baru:
![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* Klik tombol "Try it out", anda dapat mengisi parameter dan langsung berinteraksi dengan API:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png)
* Kemudian klik tombol "Execute", tampilan pengguna akan berkomunikasi dengan API, mengirim parameter, mendapatkan dan menampilkan hasil ke layar:
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png)
### Upgrade dokumentasi API alternatif
Kunjungi <a href="http://127.0.0.1:8000/redoc" class="external-link" target="_blank">http://127.0.0.1:8000/redoc</a>.
* Dokumentasi alternatif akan menampilkan parameter *query* dan struktur *request*:
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
### Ringkasan
Singkatnya, anda mendeklarasikan **sekali** jenis parameter, struktur, dll. sebagai parameter fungsi.
Anda melakukannya dengan tipe standar moderen Python.
Anda tidak perlu belajar sintaksis, metode, *classs* baru dari *library* tertentu, dll.
Cukup **Python** standar.
Sebagai contoh untuk `int`:
```Python
item_id: int
```
atau untuk model lebih rumit `Item`:
```Python
item: Item
```
...dengan sekali deklarasi anda mendapatkan:
* Dukungan editor, termasuk:
* Pelengkapan kode.
* Pengecekan tipe.
* Validasi data:
* Kesalahan otomatis dan jelas ketika data tidak sesuai.
* Validasi hingga untuk object JSON bercabang mendalam.
* <abbr title="juga disebut: serialization, parsing, marshalling">Konversi</abbr> input data: berasal dari jaringan ke data dan tipe Python. Membaca dari:
* JSON.
* Parameter path.
* Parameter query.
* Cookie.
* Header.
* Form.
* File.
* <abbr title="juga disebut: serialization, parsing, marshalling">Konversi</abbr> output data: konversi data Python ke tipe jaringan data (seperti JSON):
* Konversi tipe Python (`str`, `int`, `float`, `bool`, `list`, dll).
* Objek `datetime`.
* Objek `UUID`.
* Model database.
* ...dan banyak lagi.
* Dokumentasi interaktif otomatis, termasuk 2 alternatif tampilan pengguna:
* Swagger UI.
* ReDoc.
---
Kembali ke kode contoh sebelumnya, **FastAPI** akan:
* Validasi apakah terdapat `item_id` di *path* untuk permintaan `GET` dan `PUT` requests.
* Validasi apakah `item_id` berjenit `int` untuk permintaan `GET` dan `PUT`.
* Jika tidak, klien akan melihat pesan kesalahan jelas.
* Periksa jika ada parameter *query* opsional bernama `q` (seperti `http://127.0.0.1:8000/items/foo?q=somequery`) untuk permintaan `GET`.
* Karena parameter `q` dideklarasikan dengan `= None`, maka bersifat opsional.
* Tanpa `None` maka akan menjadi wajib ada (seperti struktur di kondisi dengan `PUT`).
* Untuk permintaan `PUT` `/items/{item_id}`, membaca struktur sebagai JSON:
* Memeriksa terdapat atribut wajib `name` harus berjenis `str`.
* Memeriksa terdapat atribut wajib`price` harus berjenis `float`.
* Memeriksa atribut opsional `is_offer`, harus berjenis `bool`, jika ada.
* Semua ini juga sama untuk objek json yang bersarang mendalam.
* Konversi dari dan ke JSON secara otomatis.
* Dokumentasi segalanya dengan OpenAPI, dengan menggunakan:
* Sistem dokumentasi interaktif.
* Sistem otomatis penghasil kode, untuk banyak bahasa.
* Menyediakan 2 tampilan dokumentasi web interaktif dengan langsung.
---
Kita baru menyentuh permukaannya saja, tetapi anda sudah mulai paham gambaran besar cara kerjanya.
Coba ubah baris:
```Python
return {"item_name": item.name, "item_id": item_id}
```
...dari:
```Python
... "item_name": item.name ...
```
...menjadi:
```Python
... "item_price": item.price ...
```
...anda akan melihat kode editor secara otomatis melengkapi atributnya dan tahu tipe nya:
![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png)
Untuk contoh lengkap termasuk fitur lainnya, kunjungi <a href="https://fastapi.tiangolo.com/tutorial/">Tutorial - Panduan Pengguna</a>.
**Peringatan spoiler**: tutorial - panduan pengguna termasuk:
* Deklarasi **parameter** dari tempat berbeda seperti: **header**, **cookie**, **form field** and **file**.
* Bagaimana mengatur **batasan validasi** seperti `maximum_length`atau `regex`.
* Sistem **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** yang hebat dan mudah digunakan.
* Keamanan dan autentikasi, termasuk dukungan ke **OAuth2** dengan **JWT token** dan autentikasi **HTTP Basic**.
* Teknik lebih aju (tetapi mudah dipakai untuk deklarasi **model JSON bersarang ke dalam** (berkat Pydantic).
* Integrasi **GraphQL** dengan <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> dan library lainnya.
* Fitur lainnya (berkat Starlette) seperti:
* **WebSocket**
* Test yang sangat mudah berdasarkan HTTPX dan `pytest`
* **CORS**
* **Cookie Session**
* ...dan lainnya.
## Performa
Tolok ukur Independent TechEmpower mendapati aplikasi **FastAPI** berjalan menggunakan Uvicorn sebagai <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">salah satu framework Python tercepat yang ada</a>, hanya di bawah Starlette dan Uvicorn itu sendiri (digunakan di internal FastAPI). (*)
Penjelasan lebih lanjut, lihat bagian <a href="https://fastapi.tiangolo.com/benchmarks/" class="internal-link" target="_blank">Tolok ukur</a>.
## Dependensi
FastAPI bergantung pada Pydantic dan Starlette.
### Dependensi `standar`
Ketika anda meng-*install* FastAPI dengan `pip install "fastapi[standard]"`, maka FastAPI akan menggunakan sekumpulan dependensi opsional `standar`:
Digunakan oleh Pydantic:
* <a href="https://github.com/JoshData/python-email-validator" target="_blank"><code>email-validator</code></a> - untuk validasi email.
Digunakan oleh Starlette:
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Dibutuhkan jika anda menggunakan `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Dibutuhkan jika anda menggunakan konfigurasi template bawaan.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Dibutuhkan jika anda menggunakan form dukungan <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>, dengan `request.form()`.
Digunakan oleh FastAPI / Starlette:
* <a href="https://www.uvicorn.org" target="_blank"><code>uvicorn</code></a> - untuk server yang memuat dan melayani aplikasi anda. Termasuk `uvicorn[standard]`, yang memasukan sejumlah dependensi (misal `uvloop`) untuk needed melayani dengan performa tinggi.
* `fastapi-cli` - untuk menyediakan perintah `fastapi`.
### Tanpda dependensi `standard`
Jika anda tidak ingin menambahkan dependensi opsional `standard`, anda dapat menggunakan `pip install fastapi` daripada `pip install "fastapi[standard]"`.
### Dependensi Opsional Tambahan
Ada beberapa dependensi opsional yang bisa anda install.
Dependensi opsional tambahan Pydantic:
* <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" target="_blank"><code>pydantic-settings</code></a> - untuk manajemen setting.
* <a href="https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/" target="_blank"><code>pydantic-extra-types</code></a> - untuk tipe tambahan yang digunakan dengan Pydantic.
Dependensi tambahan opsional FastAPI:
* <a href="https://github.com/ijl/orjson" target="_blank"><code>orjson</code></a> - Diperlukan jika anda akan menggunakan`ORJSONResponse`.
* <a href="https://github.com/esnme/ultrajson" target="_blank"><code>ujson</code></a> - Diperlukan jika anda akan menggunakan `UJSONResponse`.
## Lisensi
Project terlisensi dengan lisensi MIT.

View File

@@ -0,0 +1,40 @@
# Berkas Statis
Anda dapat menyajikan berkas statis secara otomatis dari sebuah direktori menggunakan `StaticFiles`.
## Penggunaan `StaticFiles`
* Mengimpor `StaticFiles`.
* "Mount" representatif `StaticFiles()` di jalur spesifik.
{* ../../docs_src/static_files/tutorial001.py hl[2,6] *}
/// note | Detail Teknis
Anda dapat pula menggunakan `from starlette.staticfiles import StaticFiles`.
**FastAPI** menyediakan `starlette.staticfiles` sama seperti `fastapi.staticfiles` sebagai kemudahan pada Anda, yaitu para pengembang. Tetapi ini asli berasal langsung dari Starlette.
///
### Apa itu "Mounting"
"Mounting" dimaksud menambah aplikasi "independen" secara lengkap di jalur spesifik, kemudian menangani seluruh sub-jalur.
Hal ini berbeda dari menggunakan `APIRouter` karena aplikasi yang dimount benar-benar independen. OpenAPI dan dokumentasi dari aplikasi utama Anda tak akan menyertakan apa pun dari aplikasi yang dimount, dst.
Anda dapat mempelajari mengenai ini dalam [Panduan Pengguna Lanjutan](../advanced/index.md){.internal-link target=_blank}.
## Detail
Terhadap `"/static"` pertama mengacu pada sub-jalur yang akan menjadi tempat "sub-aplikasi" ini akan "dimount". Maka, jalur apa pun yang dimulai dengan `"/static"` akan ditangani oleh sub-jalur tersebut.
Terhadap `directory="static"` mengacu pada nama direktori yang berisi berkas statis Anda.
Terhadap `name="static"` ialah nama yang dapat digunakan secara internal oleh **FastAPI**.
Seluruh parameter ini dapat berbeda dari sekadar "`static`", sesuaikan parameter dengan keperluan dan detail spesifik akan aplikasi Anda.
## Info lanjutan
Sebagai detail dan opsi tambahan lihat <a href="https://www.starlette.io/staticfiles/" class="external-link" target="_blank">dokumentasi Starlette perihal Berkas Statis</a>.

View File

@@ -0,0 +1,301 @@
# 環境変数
/// tip
もし、「環境変数」とは何か、それをどう使うかを既に知っている場合は、このセクションをスキップして構いません。
///
環境変数(**env var**とも呼ばれるはPythonコードの**外側**、つまり**OS**に存在する変数で、Pythonから読み取ることができます。他のプログラムでも同様に読み取れます。
環境変数は、アプリケーションの**設定**の管理や、Pythonの**インストール**などに役立ちます。
## 環境変数の作成と使用
環境変数は**シェル(ターミナル)**内で**作成**して使用でき、それらにPythonは不要です。
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// You could create an env var MY_NAME with
$ export MY_NAME="Wade Wilson"
// Then you could use it with other programs, like
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Create an env var MY_NAME
$ $Env:MY_NAME = "Wade Wilson"
// Use it with other programs, like
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
## Pythonで環境変数を読み取る
環境変数をPythonの**外側**、ターミナル(や他の方法)で作成し、**Python内で読み取る**こともできます。
例えば、以下のような`main.py`ファイルを用意します:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip
<a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> の第2引数は、デフォルトで返される値を指定します。
この引数を省略するとデフォルト値として`None`が返されますが、ここではデフォルト値として`"World"`を指定しています。
///
次に、このPythonプログラムを呼び出します。
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ export MY_NAME="Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ $Env:MY_NAME = "Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
環境変数はコードの外側で設定し、内側から読み取ることができるので、他のファイルと一緒に(`git`に)保存する必要がありません。そのため、環境変数をコンフィグレーションや**設定**に使用することが一般的です。
また、**特定のプログラムの呼び出し**のための環境変数を、そのプログラムのみ、その実行中に限定して利用できるよう作成できます。
そのためには、プログラム起動コマンドと同じコマンドライン上の、起動コマンド直前で環境変数を作成してください。
<div class="termy">
```console
// Create an env var MY_NAME in line for this program call
$ MY_NAME="Wade Wilson" python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
// The env var no longer exists afterwards
$ python main.py
Hello World from Python
```
</div>
/// tip
詳しくは <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a> を参照してください。
///
## 型とバリデーション
環境変数は**テキスト文字列**のみを扱うことができます。これは、環境変数がPython外部に存在し、他のプログラムやシステム全体Linux、Windows、macOS間の互換性を含むと連携する必要があるためです。
つまり、Pythonが環境変数から読み取る**あらゆる値**は **`str`型となり**、他の型への変換やバリデーションはコード内で行う必要があります。
環境変数を使用して**アプリケーション設定**を管理する方法については、[高度なユーザーガイド - Settings and Environment Variables](./advanced/settings.md){.internal-link target=_blank}で詳しく学べます。
## `PATH`環境変数
**`PATH`**という**特別な**環境変数があります。この環境変数は、OSLinux、macOS、Windowsが実行するプログラムを発見するために使用されます。
`PATH`変数は、複数のディレクトリのパスから成る長い文字列です。このパスはLinuxやMacOSの場合は`:`で、Windowsの場合は`;`で区切られています。
例えば、`PATH`環境変数は次のような文字列かもしれません:
//// tab | Linux, macOS
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
これは、OSはプログラムを見つけるために以下のディレクトリを探す、ということを意味します:
* `/usr/local/bin`
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
```
これは、OSはプログラムを見つけるために以下のディレクトリを探す、ということを意味します:
* `C:\Program Files\Python312\Scripts`
* `C:\Program Files\Python312`
* `C:\Windows\System32`
////
ターミナル上で**コマンド**を入力すると、 OSはそのプログラムを見つけるために、`PATH`環境変数のリストに記載された**それぞれのディレクトリを探し**ます。
例えば、ターミナル上で`python`を入力すると、OSは`python`によって呼ばれるプログラムを見つけるために、そのリストの**先頭のディレクトリ**を最初に探します。
OSは、もしそのプログラムをそこで発見すれば**実行し**ますが、そうでなければリストの**他のディレクトリ**を探していきます。
### PythonのインストールとPATH環境変数の更新
Pythonのインストール時に`PATH`環境変数を更新したいか聞かれるかもしれません。
/// tab | Linux, macOS
Pythonをインストールして、そのプログラムが`/opt/custompython/bin`というディレクトリに配置されたとします。
もし、`PATH`環境変数を更新するように答えると、`PATH`環境変数に`/opt/custompython/bin`が追加されます。
`PATH`環境変数は以下のように更新されるでしょう:
``` plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
```
このようにして、ターミナルで`python`と入力したときに、OSは`/opt/custompython/bin`リストの末尾のディレクトリにあるPythonプログラムを見つけ、使用します。
///
/// tab | Windows
Pythonをインストールして、そのプログラムが`C:\opt\custompython\bin`というディレクトリに配置されたとします。
もし、`PATH`環境変数を更新するように答えると、`PATH`環境変数に`C:\opt\custompython\bin`が追加されます。
`PATH`環境変数は以下のように更新されるでしょう:
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
```
このようにして、ターミナルで`python`と入力したときに、OSは`C:\opt\custompython\bin\python`リストの末尾のディレクトリにあるPythonプログラムを見つけ、使用します。
///
つまり、ターミナルで以下のコマンドを入力すると:
<div class="termy">
``` console
$ python
```
</div>
/// tab | Linux, macOS
OSは`/opt/custompython/bin`にある`python`プログラムを**見つけ**て実行します。
これは、次のコマンドを入力した場合とほとんど同等です:
<div class="termy">
```console
$ /opt/custompython/bin/python
```
</div>
///
/// tab | Windows
OSは`C:\opt\custompython\bin\python`にある`python`プログラムを**見つけ**て実行します。
これは、次のコマンドを入力した場合とほとんど同等です:
<div class="termy">
```console
$ C:\opt\custompython\bin\python
```
</div>
///
この情報は、[Virtual Environments](virtual-environments.md) について学ぶ際にも役立ちます。
## まとめ
これで、**環境変数**とは何か、Pythonでどのように使用するかについて、基本的な理解が得られたはずです。
環境変数についての詳細は、<a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia: Environment Variable</a> を参照してください。
環境変数の用途や適用方法が最初は直感的ではないかもしれませんが、開発中のさまざまなシナリオで繰り返し登場します。そのため、基本を知っておくことが重要です。
たとえば、この情報は次のセクションで扱う[Virtual Environments](virtual-environments.md)にも関連します。

View File

@@ -58,7 +58,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降
`Query``Path`(、そして後述する他のもの)を用いて、文字列の制約を宣言することができますが、数値の制約も同様に宣言できます。
ここで、`ge=1`の場合、`item_id``1`「より大きい`g`か、同じ`e`」整数でなれけばなりません。
ここで、`ge=1`の場合、`item_id``1`「より大きい`g`か、同じ`e`」整数でなれけばなりません。
{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *}
@@ -104,7 +104,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降
/// note | 技術詳細
`fastapi`から`Query``Path`などをインポートすると、これらは実際には関数です。
`fastapi`から`Query``Path`などをインポートすると、これらは実際には関数です。
呼び出されると、同じ名前のクラスのインスタンスを返します。

View File

@@ -1,34 +0,0 @@
# Benchmarks
Benchmarks independentes da TechEmpower mostram que aplicações **FastAPI** rodando com o Uvicorn como <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">um dos frameworks Python mais rápidos disponíveis</a>, ficando atrás apenas do Starlette e Uvicorn (utilizado internamente pelo FastAPI).
Porém, ao verificar benchmarks e comparações você deve prestar atenção ao seguinte:
## Benchmarks e velocidade
Quando você verifica os benchmarks, é comum ver diversas ferramentas de diferentes tipos comparados como se fossem equivalentes.
Especificamente, para ver o Uvicorn, Starlette e FastAPI comparados entre si (entre diversas outras ferramentas).
Quanto mais simples o problema resolvido pela ferramenta, melhor será a performance. E a maioria das análises não testa funcionalidades adicionais que são oferecidas pela ferramenta.
A hierarquia é:
* **Uvicorn**: um servidor ASGI
* **Starlette**: (utiliza Uvicorn) um microframework web
* **FastAPI**: (utiliza Starlette) um microframework para APIs com diversas funcionalidades adicionais para a construção de APIs, com validação de dados, etc.
* **Uvicorn**:
* Terá a melhor performance, pois não possui muito código além do próprio servidor.
* Você não escreveria uma aplicação utilizando o Uvicorn diretamente. Isso significaria que o seu código teria que incluir pelo menos todo o código fornecido pelo Starlette (ou o **FastAPI**). E caso você fizesse isso, a sua aplicação final teria a mesma sobrecarga que teria se utilizasse um framework, minimizando o código e os bugs.
* Se você está comparando o Uvicorn, compare com os servidores de aplicação Daphne, Hypercorn, uWSGI, etc.
* **Starlette**:
* Terá o melhor desempenho, depois do Uvicorn. Na verdade, o Starlette utiliza o Uvicorn para rodar. Portanto, ele pode ficar mais "devagar" que o Uvicorn apenas por ter que executar mais código.
* Mas ele fornece as ferramentas para construir aplicações web simples, com roteamento baseado em caminhos, etc.
* Se você está comparando o Starlette, compare-o com o Sanic, Flask, Django, etc. Frameworks web (ou microframeworks).
* **FastAPI**:
* Da mesma forma que o Starlette utiliza o Uvicorn e não consegue ser mais rápido que ele, o **FastAPI** utiliza o Starlette, portanto, ele não consegue ser mais rápido que ele.
* O FastAPI provê mais funcionalidades em cima do Starlette. Funcionalidades que você quase sempre precisará quando estiver construindo APIs, como validação de dados e serialização. E ao utilizá-lo, você obtém documentação automática sem custo nenhum (a documentação automática sequer adiciona sobrecarga nas aplicações rodando, pois ela é gerada na inicialização).
* Caso você não utilize o FastAPI e faz uso do Starlette diretamente (ou outra ferramenta, como o Sanic, Flask, Responder, etc) você mesmo teria que implementar toda a validação de dados e serialização. Então, a sua aplicação final ainda teria a mesma sobrecarga caso estivesse usando o FastAPI. E em muitos casos, validação de dados e serialização é a maior parte do código escrito em aplicações.
* Então, ao utilizar o FastAPI, você está economizando tempo de programação, evitando bugs, linhas de código, e provavelmente terá a mesma performance (ou até melhor) do que teria caso você não o utilizasse (já que você teria que implementar tudo no seu código).
* Se você está comparando o FastAPI, compare-o com frameworks de aplicações web (ou conjunto de ferramentas) que oferecem validação de dados, serialização e documentação, como por exemplo o Flask-apispec, NestJS, Molten, etc. Frameworks que possuem validação integrada de dados, serialização e documentação.

View File

@@ -8,7 +8,7 @@ Por isso é comum prover essas configurações como variáveis de ambiente que s
## Variáveis de Ambiente
/// dica
/// tip | Dica
Se você já sabe o que são variáveis de ambiente e como utilizá-las, sinta-se livre para avançar para o próximo tópico.
@@ -67,7 +67,7 @@ name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// dica
/// tip | Dica
O segundo parâmetro em <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> é o valor padrão para o retorno.
@@ -124,7 +124,7 @@ Hello World from Python
</div>
/// dica
/// tip | Dica
Você pode ler mais sobre isso em: <a href="https://12factor.net/pt_br/config" class="external-link" target="_blank">The Twelve-Factor App: Configurações</a>.
@@ -196,7 +196,7 @@ Na versão 1 do Pydantic você importaria `BaseSettings` diretamente do módulo
////
/// dica
/// tip | Dica
Se você quiser algo pronto para copiar e colar na sua aplicação, não use esse exemplo, mas sim o exemplo abaixo.
@@ -226,7 +226,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p
</div>
/// dica
/// tip | Dica
Para definir múltiplas variáveis de ambiente para um único comando basta separá-las utilizando espaços, e incluir todas elas antes do comando.
@@ -250,7 +250,7 @@ E utilizar essa configuração em `main.py`:
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *}
/// dica
/// tip | Dica
Você também precisa incluir um arquivo `__init__.py` como visto em [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=\_blank}.
@@ -276,7 +276,7 @@ Agora criamos a dependência que retorna um novo objeto `config.Settings()`.
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *}
/// dica
/// tip | Dica
Vamos discutir sobre `@lru_cache` logo mais.
@@ -304,7 +304,7 @@ Se você tiver muitas configurações que variem bastante, talvez em ambientes d
Essa prática é tão comum que possui um nome, essas variáveis de ambiente normalmente são colocadas em um arquivo `.env`, e esse arquivo é chamado de "dotenv".
/// dica
/// tip | Dica
Um arquivo iniciando com um ponto final (`.`) é um arquivo oculto em sistemas baseados em Unix, como Linux e MacOS.
@@ -314,7 +314,7 @@ Mas um arquivo dotenv não precisa ter esse nome exato.
Pydantic suporta a leitura desses tipos de arquivos utilizando uma biblioteca externa. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>.
/// dica
/// tip | Dica
Para que isso funcione você precisa executar `pip install python-dotenv`.
@@ -337,7 +337,7 @@ E então adicionar o seguinte código em `config.py`:
{* ../../docs_src/settings/app03_an/config.py hl[9] *}
/// dica
/// tip | Dica
O atributo `model_config` é usado apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.
@@ -349,7 +349,7 @@ O atributo `model_config` é usado apenas para configuração do Pydantic. Você
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *}
/// dica
/// tip | Dica
A classe `Config` é usada apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>.

View File

@@ -7,45 +7,33 @@ Em resumo, utilize o comando `fastapi run` para inicializar sua aplicação Fast
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
$ <font color="#4E9A06">fastapi</font> run <u style="text-decoration-style:solid">main.py</u>
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<font color="#4E9A06">╭─────────── FastAPI CLI - Production mode ───────────╮</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Serving at: http://0.0.0.0:8000 │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ API docs: http://0.0.0.0:8000/docs │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Running in production mode, for development use: │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ </font><font color="#8AE234"><b>fastapi dev</b></font><font color="#4E9A06"> │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">╰─────────────────────────────────────────────────────╯</font>
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2306215</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8000</b> (Press CTRL+C to quit)
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>2306215</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
```
</div>

View File

@@ -36,56 +36,43 @@ Se você usar o comando `fastapi`:
<div class="termy">
```console
$ <pre> <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
$ <font color="#4E9A06">fastapi</font> run --workers 4 <u style="text-decoration-style:solid">main.py</u>
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting production server 🚀
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
Searching for package file structure from directories with
<font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
following code:
<font color="#4E9A06">╭─────────── FastAPI CLI - Production mode ───────────╮</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Serving at: http://0.0.0.0:8000 │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ API docs: http://0.0.0.0:8000/docs │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ Running in production mode, for development use: │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">│ </font><font color="#8AE234"><b>fastapi dev</b></font><font color="#4E9A06"> │</font>
<font color="#4E9A06">│ │</font>
<font color="#4E9A06">╰─────────────────────────────────────────────────────╯</font>
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8000</b> (Press CTRL+C to quit)
<font color="#4E9A06">INFO</font>: Started parent process [<font color="#34E2E2"><b>27365</b></font>]
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27368</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27369</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27370</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">27367</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
</pre>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000/docs</u></font>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://0.0.0.0:8000</u></font> <b>(</b>Press CTRL+C to
quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started parent process <b>[</b><font color="#34E2E2"><b>27365</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27368</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27369</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27370</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>27367</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>

View File

@@ -11,27 +11,43 @@ Execute o servidor:
<div class="termy">
```console
$ uvicorn main:app --reload
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
/// note | Nota
O comando `uvicorn main:app` se refere a:
* `main`: o arquivo `main.py` (o "módulo" Python).
* `app`: o objeto criado no arquivo `main.py` com a linha `app = FastAPI()`.
* `--reload`: faz o servidor reiniciar após mudanças de código. Use apenas para desenvolvimento.
///
Na saída, temos:
```hl_lines="4"
@@ -151,34 +167,6 @@ Aqui, a variável `app` será uma "instância" da classe `FastAPI`.
Este será o principal ponto de interação para criar toda a sua API.
Este `app` é o mesmo referenciado por `uvicorn` no comando:
<div class="termy">
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Se você criar a sua aplicação como:
{* ../../docs_src/first_steps/tutorial002.py hl[3] *}
E colocar em um arquivo `main.py`, você iria chamar o `uvicorn` assim:
<div class="termy">
```console
$ uvicorn main:my_awesome_api --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
### Passo 3: crie uma *rota*
#### Rota

View File

@@ -4,9 +4,7 @@ Esse tutorial mostra como usar o **FastAPI** com a maior parte de seus recursos,
Cada seção constrói, gradualmente, sobre as anteriores, mas sua estrutura são tópicos separados, para que você possa ir a qualquer um específico e resolver suas necessidades específicas de API.
Ele também foi feito como referência futura.
Então você poderá voltar e ver exatamente o que precisar.
Ele também foi construído para servir como uma referência futura, então você pode voltar e ver exatamente o que você precisa.
## Rode o código
@@ -17,13 +15,39 @@ Para rodar qualquer um dos exemplos, copie o codigo para um arquivo `main.py`, e
<div class="termy">
```console
$ uvicorn main:app --reload
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
Searching for package file structure from directories
with <font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with
the following code:
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C
to quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>
@@ -43,32 +67,18 @@ Para o tutorial, você deve querer instalá-lo com todas as dependências e recu
<div class="termy">
```console
$ pip install "fastapi[all]"
$ pip install "fastapi[standard]"
---> 100%
```
</div>
...isso também inclui o `uvicorn`, que você pode usar como o servidor que rodará seu código.
/// note | Nota
Você também pode instalar parte por parte.
Quando você instala com pip install "fastapi[standard]", ele vem com algumas dependências opcionais padrão.
Isso é provavelmente o que você faria quando você quisesse lançar sua aplicação em produção:
```
pip install fastapi
```
Também instale o `uvicorn` para funcionar como servidor:
```
pip install "uvicorn[standard]"
```
E o mesmo para cada dependência opcional que você quiser usar.
Se você não quiser ter essas dependências opcionais, pode instalar pip install fastapi em vez disso.
///

View File

@@ -6,7 +6,11 @@ Quando você precisar receber campos de formulário ao invés de JSON, você pod
Para usar formulários, primeiro instale <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>.
Ex: `pip install python-multipart`.
Lembre-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar a dependência, por exemplo:
```console
$ pip install python-multipart
```
///

View File

@@ -0,0 +1,105 @@
# Obter Usuário Atual
No capítulo anterior, o sistema de segurança (que é baseado no sistema de injeção de dependências) estava fornecendo à *função de operação de rota* um `token` como uma `str`:
{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *}
Mas isso ainda não é tão útil.
Vamos fazer com que ele nos forneça o usuário atual.
## Criar um modelo de usuário
Primeiro, vamos criar um modelo de usuário com Pydantic.
Da mesma forma que usamos o Pydantic para declarar corpos, podemos usá-lo em qualquer outro lugar:
{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *}
## Criar uma dependência `get_current_user`
Vamos criar uma dependência chamada `get_current_user`.
Lembra que as dependências podem ter subdependências?
`get_current_user` terá uma dependência com o mesmo `oauth2_scheme` que criamos antes.
Da mesma forma que estávamos fazendo antes diretamente na *operação de rota*, a nossa nova dependência `get_current_user` receberá um `token` como uma `str` da subdependência `oauth2_scheme`:
{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *}
## Obter o usuário
`get_current_user` usará uma função utilitária (falsa) que criamos, que recebe um token como uma `str` e retorna nosso modelo Pydantic `User`:
{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *}
## Injetar o usuário atual
Então agora nós podemos usar o mesmo `Depends` com nosso `get_current_user` na *operação de rota*:
{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *}
Observe que nós declaramos o tipo de `current_user` como o modelo Pydantic `User`.
Isso nos ajudará dentro da função com todo o preenchimento automático e verificações de tipo.
/// tip | Dica
Você pode se lembrar que corpos de requisição também são declarados com modelos Pydantic.
Aqui, o **FastAPI** não ficará confuso porque você está usando `Depends`.
///
/// check | Verifique
A forma como esse sistema de dependências foi projetado nos permite ter diferentes dependências (diferentes "dependables") que retornam um modelo `User`.
Não estamos restritos a ter apenas uma dependência que possa retornar esse tipo de dado.
///
## Outros modelos
Agora você pode obter o usuário atual diretamente nas *funções de operação de rota* e lidar com os mecanismos de segurança no nível da **Injeção de Dependências**, usando `Depends`.
E você pode usar qualquer modelo ou dado para os requisitos de segurança (neste caso, um modelo Pydantic `User`).
Mas você não está restrito a usar um modelo de dados, classe ou tipo específico.
Você quer ter apenas um `id` e `email`, sem incluir nenhum `username` no modelo? Claro. Você pode usar essas mesmas ferramentas.
Você quer ter apenas uma `str`? Ou apenas um `dict`? Ou uma instância de modelo de classe de banco de dados diretamente? Tudo funciona da mesma forma.
Na verdade, você não tem usuários que fazem login no seu aplicativo, mas sim robôs, bots ou outros sistemas, que possuem apenas um token de acesso? Novamente, tudo funciona da mesma forma.
Apenas use qualquer tipo de modelo, qualquer tipo de classe, qualquer tipo de banco de dados que você precise para a sua aplicação. O **FastAPI** cobre tudo com o sistema de injeção de dependências.
## Tamanho do código
Este exemplo pode parecer verboso. Lembre-se de que estamos misturando segurança, modelos de dados, funções utilitárias e *operações de rota* no mesmo arquivo.
Mas aqui está o ponto principal.
O código relacionado à segurança e à injeção de dependências é escrito apenas uma vez.
E você pode torná-lo tão complexo quanto quiser. E ainda assim, tê-lo escrito apenas uma vez, em um único lugar. Com toda a flexibilidade.
Mas você pode ter milhares de endpoints (*operações de rota*) usando o mesmo sistema de segurança.
E todos eles (ou qualquer parte deles que você desejar) podem aproveitar o reuso dessas dependências ou de quaisquer outras dependências que você criar.
E todos esses milhares de *operações de rota* podem ter apenas 3 linhas:
{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *}
## Recapitulação
Agora você pode obter o usuário atual diretamente na sua *função de operação de rota*.
Já estamos na metade do caminho.
Só precisamos adicionar uma *operação de rota* para que o usuário/cliente realmente envie o `username` e `password`.
Isso vem a seguir.

View File

@@ -0,0 +1,274 @@
# OAuth2 com Senha (e hashing), Bearer com tokens JWT
Agora que temos todo o fluxo de segurança, vamos tornar a aplicação realmente segura, usando tokens <abbr title="JSON Web Tokens">JWT</abbr> e hashing de senhas seguras.
Este código é algo que você pode realmente usar na sua aplicação, salvar os hashes das senhas no seu banco de dados, etc.
Vamos começar de onde paramos no capítulo anterior e incrementá-lo.
## Sobre o JWT
JWT significa "JSON Web Tokens".
É um padrão para codificar um objeto JSON em uma string longa e densa sem espaços. Ele se parece com isso:
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```
Ele não é criptografado, então qualquer pessoa pode recuperar as informações do seu conteúdo.
Mas ele é assinado. Assim, quando você recebe um token que você emitiu, você pode verificar que foi realmente você quem o emitiu.
Dessa forma, você pode criar um token com um prazo de expiração, digamos, de 1 semana. E então, quando o usuário voltar no dia seguinte com o token, você sabe que ele ainda está logado no seu sistema.
Depois de uma semana, o token expirará e o usuário não estará autorizado, precisando fazer login novamente para obter um novo token. E se o usuário (ou uma terceira parte) tentar modificar o token para alterar a expiração, você seria capaz de descobrir isso, pois as assinaturas não iriam corresponder.
Se você quiser brincar com tokens JWT e ver como eles funcionam, visite <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>.
## Instalar `PyJWT`
Nós precisamos instalar o `PyJWT` para criar e verificar os tokens JWT em Python.
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o `pyjwt`:
<div class="termy">
```console
$ pip install pyjwt
---> 100%
```
</div>
/// info | Informação
Se você pretente utilizar algoritmos de assinatura digital como o RSA ou o ECDSA, você deve instalar a dependência da biblioteca de criptografia `pyjwt[crypto]`.
Você pode ler mais sobre isso na <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">documentação de instalação do PyJWT</a>.
///
## Hashing de senhas
"Hashing" significa converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece um monte de caracteres sem sentido.
Sempre que você passar exatamente o mesmo conteúdo (exatamente a mesma senha), você obterá exatamente o mesmo resultado.
Mas não é possível converter os caracteres sem sentido de volta para a senha original.
### Por que usar hashing de senhas
Se o seu banco de dados for roubado, o invasor não terá as senhas em texto puro dos seus usuários, apenas os hashes.
Então, o invasor não poderá tentar usar essas senhas em outro sistema (como muitos usuários utilizam a mesma senha em vários lugares, isso seria perigoso).
## Instalar o `passlib`
O PassLib é uma excelente biblioteca Python para lidar com hashes de senhas.
Ele suporta muitos algoritmos de hashing seguros e utilitários para trabalhar com eles.
O algoritmo recomendado é o "Bcrypt".
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o PassLib com Bcrypt:
<div class="termy">
```console
$ pip install "passlib[bcrypt]"
---> 100%
```
</div>
/// tip | Dica
Com o `passlib`, você poderia até configurá-lo para ser capaz de ler senhas criadas pelo **Django**, um plug-in de segurança do **Flask** ou muitos outros.
Assim, você poderia, por exemplo, compartilhar os mesmos dados de um aplicativo Django em um banco de dados com um aplicativo FastAPI. Ou migrar gradualmente uma aplicação Django usando o mesmo banco de dados.
E seus usuários poderiam fazer login tanto pela sua aplicação Django quanto pela sua aplicação **FastAPI**, ao mesmo tempo.
///
## Criar o hash e verificar as senhas
Importe as ferramentas que nós precisamos de `passlib`.
Crie um "contexto" do PassLib. Este será usado para criar o hash e verificar as senhas.
/// tip | Dica
O contexto do PassLib também possui funcionalidades para usar diferentes algoritmos de hashing, incluindo algoritmos antigos que estão obsoletos, apenas para permitir verificá-los, etc.
Por exemplo, você poderia usá-lo para ler e verificar senhas geradas por outro sistema (como Django), mas criar o hash de novas senhas com um algoritmo diferente, como o Bcrypt.
E ser compatível com todos eles ao mesmo tempo.
///
Crie uma função utilitária para criar o hash de uma senha fornecida pelo usuário.
E outra função utilitária para verificar se uma senha recebida corresponde ao hash armazenado.
E outra para autenticar e retornar um usuário.
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *}
/// note | Nota
Se você verificar o novo banco de dados (falso) `fake_users_db`, você verá como o hash da senha se parece agora: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`.
///
## Manipular tokens JWT
Importe os módulos instalados.
Crie uma chave secreta aleatória que será usada para assinar os tokens JWT.
Para gerar uma chave secreta aleatória e segura, use o comando:
<div class="termy">
```console
$ openssl rand -hex 32
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
```
</div>
E copie a saída para a variável `SECRET_KEY` (não use a do exemplo).
Crie uma variável `ALGORITHM` com o algoritmo usado para assinar o token JWT e defina como `"HS256"`.
Crie uma variável para a expiração do token.
Defina um modelo Pydantic que será usado no endpoint de token para a resposta.
Crie uma função utilitária para gerar um novo token de acesso.
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *}
## Atualize as dependências
Atualize `get_current_user` para receber o mesmo token de antes, mas desta vez, usando tokens JWT.
Decodifique o token recebido, verifique-o e retorne o usuário atual.
Se o token for inválido, retorne um erro HTTP imediatamente.
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *}
## Atualize a *operação de rota* `/token`
Crie um `timedelta` com o tempo de expiração do token.
Crie um token de acesso JWT real e o retorne.
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *}
### Detalhes técnicos sobre o "sujeito" `sub` do JWT
A especificação JWT diz que existe uma chave `sub`, com o sujeito do token.
É opcional usá-la, mas é onde você colocaria a identificação do usuário, então nós estamos usando aqui.
O JWT pode ser usado para outras coisas além de identificar um usuário e permitir que ele execute operações diretamente na sua API.
Por exemplo, você poderia identificar um "carro" ou uma "postagem de blog".
Depois, você poderia adicionar permissões sobre essa entidade, como "dirigir" (para o carro) ou "editar" (para o blog).
E então, poderia dar esse token JWT para um usuário (ou bot), e ele poderia usá-lo para realizar essas ações (dirigir o carro ou editar o blog) sem sequer precisar ter uma conta, apenas com o token JWT que sua API gerou para isso.
Usando essas ideias, o JWT pode ser usado para cenários muito mais sofisticados.
Nesses casos, várias dessas entidades poderiam ter o mesmo ID, digamos `foo` (um usuário `foo`, um carro `foo` e uma postagem de blog `foo`).
Então, para evitar colisões de ID, ao criar o token JWT para o usuário, você poderia prefixar o valor da chave `sub`, por exemplo, com `username:`. Assim, neste exemplo, o valor de `sub` poderia ser: `username:johndoe`.
O importante a se lembrar é que a chave `sub` deve ter um identificador único em toda a aplicação e deve ser uma string.
## Testando
Execute o servidor e vá para a documentação: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Você verá a interface de usuário assim:
<img src="/img/tutorial/security/image07.png">
Autorize a aplicação da mesma maneira que antes.
Usando as credenciais:
Username: `johndoe`
Password: `secret`
/// check | Verifique
Observe que em nenhuma parte do código está a senha em texto puro "`secret`", nós temos apenas o hash.
///
<img src="/img/tutorial/security/image08.png">
Chame o endpoint `/users/me/`, você receberá o retorno como:
```JSON
{
"username": "johndoe",
"email": "johndoe@example.com",
"full_name": "John Doe",
"disabled": false
}
```
<img src="/img/tutorial/security/image09.png">
Se você abrir as ferramentas de desenvolvedor, poderá ver que os dados enviados incluem apenas o token. A senha é enviada apenas na primeira requisição para autenticar o usuário e obter o token de acesso, mas não é enviada nas próximas requisições:
<img src="/img/tutorial/security/image10.png">
/// note | Nota
Perceba que o cabeçalho `Authorization`, com o valor que começa com `Bearer `.
///
## Uso avançado com `scopes`
O OAuth2 tem a noção de "scopes" (escopos).
Você pode usá-los para adicionar um conjunto específico de permissões a um token JWT.
Então, você pode dar este token diretamente a um usuário ou a uma terceira parte para interagir com sua API com um conjunto de restrições.
Você pode aprender como usá-los e como eles são integrados ao **FastAPI** mais adiante no **Guia Avançado do Usuário**.
## Recapitulação
Com o que você viu até agora, você pode configurar uma aplicação **FastAPI** segura usando padrões como OAuth2 e JWT.
Em quase qualquer framework, lidar com a segurança se torna rapidamente um assunto bastante complexo.
Muitos pacotes que simplificam bastante isso precisam fazer muitas concessões com o modelo de dados, o banco de dados e os recursos disponíveis. E alguns desses pacotes que simplificam demais na verdade têm falhas de segurança subjacentes.
---
O **FastAPI** não faz nenhuma concessão com nenhum banco de dados, modelo de dados ou ferramenta.
Ele oferece toda a flexibilidade para você escolher as opções que melhor se ajustam ao seu projeto.
E você pode usar diretamente muitos pacotes bem mantidos e amplamente utilizados, como `passlib` e `PyJWT`, porque o **FastAPI** não exige mecanismos complexos para integrar pacotes externos.
Mas ele fornece as ferramentas para simplificar o processo o máximo possível, sem comprometer a flexibilidade, robustez ou segurança.
E você pode usar e implementar protocolos padrão seguros, como o OAuth2, de uma maneira relativamente simples.
Você pode aprender mais no **Guia Avançado do Usuário** sobre como usar os "scopes" do OAuth2 para um sistema de permissões mais refinado, seguindo esses mesmos padrões. O OAuth2 com scopes é o mecanismo usado por muitos provedores grandes de autenticação, como o Facebook, Google, GitHub, Microsoft, Twitter, etc. para autorizar aplicativos de terceiros a interagir com suas APIs em nome de seus usuários.

View File

@@ -0,0 +1,99 @@
# Асинхронное тестирование
Вы уже видели как тестировать **FastAPI** приложение, используя имеющийся класс `TestClient`. К этому моменту вы видели только как писать тесты в синхронном стиле без использования `async` функций.
Возможность использования асинхронных функций в ваших тестах может быть полезнa, когда, например, вы асинхронно обращаетесь к вашей базе данных. Представьте, что вы хотите отправить запросы в ваше FastAPI приложение, а затем при помощи асинхронной библиотеки для работы с базой данных удостовериться, что ваш бекэнд корректно записал данные в базу данных.
Давайте рассмотрим, как мы можем это реализовать.
## pytest.mark.anyio
Если мы хотим вызывать асинхронные функции в наших тестах, то наши тестовые функции должны быть асинхронными. AnyIO предоставляет для этого отличный плагин, который позволяет нам указывать, какие тестовые функции должны вызываться асинхронно.
## HTTPX
Даже если **FastAPI** приложение использует обычные функции `def` вместо `async def`, это все равно `async` приложение 'под капотом'.
Чтобы работать с асинхронным FastAPI приложением в ваших обычных тестовых функциях `def`, используя стандартный pytest, `TestClient` внутри себя делает некоторую магию. Но эта магия перестает работать, когда мы используем его внутри асинхронных функций. Запуская наши тесты асинхронно, мы больше не можем использовать `TestClient` внутри наших тестовых функций.
`TestClient` основан на <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, и, к счастью, мы можем использовать его (`HTTPX`) напрямую для тестирования API.
## Пример
В качестве простого примера, давайте рассмотрим файловую структуру, схожую с описанной в [Большие приложения](../tutorial/bigger-applications.md){.internal-link target=_blank} и [Тестирование](../tutorial/testing.md){.internal-link target=_blank}:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
Файл `main.py`:
{* ../../docs_src/async_tests/main.py *}
Файл `test_main.py` содержит тесты для `main.py`, теперь он может выглядеть так:
{* ../../docs_src/async_tests/test_main.py *}
## Запуск тестов
Вы можете запустить свои тесты как обычно:
<div class="termy">
```console
$ pytest
---> 100%
```
</div>
## Подробнее
Маркер `@pytest.mark.anyio` говорит pytest, что тестовая функция должна быть вызвана асинхронно:
{* ../../docs_src/async_tests/test_main.py hl[7] *}
/// tip | Подсказка
Обратите внимание, что тестовая функция теперь `async def` вместо простого `def`, как это было при использовании `TestClient`.
///
Затем мы можем создать `AsyncClient` со ссылкой на приложение и посылать асинхронные запросы, используя `await`.
{* ../../docs_src/async_tests/test_main.py hl[9:12] *}
Это эквивалентно следующему:
```Python
response = client.get('/')
```
...которое мы использовали для отправки наших запросов с `TestClient`.
/// tip | Подсказка
Обратите внимание, что мы используем async/await с `AsyncClient` - запрос асинхронный.
///
/// warning | Внимание
Если ваше приложение полагается на lifespan события, то `AsyncClient` не запустит эти события. Чтобы обеспечить их срабатывание используйте `LifespanManager` из <a href="https://github.com/florimondmanca/asgi-lifespan#usage" class="external-link" target="_blank">florimondmanca/asgi-lifespan</a>.
///
## Вызов других асинхронных функций
Теперь тестовая функция стала асинхронной, поэтому внутри нее вы можете вызывать также и другие `async` функции, не связанные с отправлением запросов в ваше FastAPI приложение. Как если бы вы вызывали их в любом другом месте вашего кода.
/// tip | Подсказка
Если вы столкнулись с `RuntimeError: Task attached to a different loop` при вызове асинхронных функций в ваших тестах (например, при использовании <a href="https://stackoverflow.com/questions/41584243/runtimeerror-task-attached-to-a-different-loop" class="external-link" target="_blank">MongoDB's MotorClient</a>), то не забывайте инициализировать объекты, которым нужен цикл событий (event loop), только внутри асинхронных функций, например, в `'@app.on_event("startup")` callback.
///

View File

@@ -0,0 +1,556 @@
# Большие приложения, в которых много файлов
При построении приложения или веб-API нам редко удается поместить всё в один файл.
**FastAPI** предоставляет удобный инструментарий, который позволяет нам структурировать приложение, сохраняя при этом всю необходимую гибкость.
/// info | Примечание
Если вы раньше использовали Flask, то это аналог шаблонов Flask (Flask's Blueprints).
///
## Пример структуры приложения
Давайте предположим, что наше приложение имеет следующую структуру:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │ ├── __init__.py
│   │ ├── items.py
│   │ └── users.py
│   └── internal
│   ├── __init__.py
│   └── admin.py
```
/// tip | Подсказка
Обратите внимание, что в каждом каталоге и подкаталоге имеется файл `__init__.py`
Это как раз то, что позволяет импортировать код из одного файла в другой.
Например, в файле `app/main.py` может быть следующая строка:
```
from app.routers import items
```
///
* Всё помещается в каталоге `app`. В нём также находится пустой файл `app/__init__.py`. Таким образом, `app` является "Python-пакетом" (коллекцией модулей Python).
* Он содержит файл `app/main.py`. Данный файл является частью пакета (т.е. находится внутри каталога, содержащего файл `__init__.py`), и, соответственно, он является модулем пакета: `app.main`.
* Он также содержит файл `app/dependencies.py`, который также, как и `app/main.py`, является модулем: `app.dependencies`.
* Здесь также находится подкаталог `app/routers/`, содержащий `__init__.py`. Он является суб-пакетом: `app.routers`.
* Файл `app/routers/items.py` находится внутри пакета `app/routers/`. Таким образом, он является суб-модулем: `app.routers.items`.
* Точно также `app/routers/users.py` является ещё одним суб-модулем: `app.routers.users`.
* Подкаталог `app/internal/`, содержащий файл `__init__.py`, является ещё одним суб-пакетом: `app.internal`.
* А файл `app/internal/admin.py` является ещё одним суб-модулем: `app.internal.admin`.
<img src="/img/tutorial/bigger-applications/package.svg">
Та же самая файловая структура приложения, но с комментариями:
```
.
├── app # "app" пакет
│   ├── __init__.py # этот файл превращает "app" в "Python-пакет"
│   ├── main.py # модуль "main", напр.: import app.main
│   ├── dependencies.py # модуль "dependencies", напр.: import app.dependencies
│   └── routers # суб-пакет "routers"
│   │ ├── __init__.py # превращает "routers" в суб-пакет
│   │ ├── items.py # суб-модуль "items", напр.: import app.routers.items
│   │ └── users.py # суб-модуль "users", напр.: import app.routers.users
│   └── internal # суб-пакет "internal"
│   ├── __init__.py # превращает "internal" в суб-пакет
│   └── admin.py # суб-модуль "admin", напр.: import app.internal.admin
```
## `APIRouter`
Давайте предположим, что для работы с пользователями используется отдельный файл (суб-модуль) `/app/routers/users.py`.
Для лучшей организации приложения, вы хотите отделить операции пути, связанные с пользователями, от остального кода.
Но так, чтобы эти операции по-прежнему оставались частью **FastAPI** приложения/веб-API (частью одного пакета)
С помощью `APIRouter` вы можете создать *операции пути* (*эндпоинты*) для данного модуля.
### Импорт `APIRouter`
Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`.
```Python hl_lines="1 3" title="app/routers/users.py"
{!../../docs_src/bigger_applications/app/routers/users.py!}
```
### Создание *эндпоинтов* с помощью `APIRouter`
В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`:
```Python hl_lines="6 11 16" title="app/routers/users.py"
{!../../docs_src/bigger_applications/app/routers/users.py!}
```
Вы можете думать об `APIRouter` как об "уменьшенной версии" класса FastAPI`.
`APIRouter` поддерживает все те же самые опции.
`APIRouter` поддерживает все те же самые параметры, такие как `parameters`, `responses`, `dependencies`, `tags`, и т. д.
/// tip | Подсказка
В данном примере, в качестве названия переменной используется `router`, но вы можете использовать любое другое имя.
///
Мы собираемся подключить данный `APIRouter` к нашему основному приложению на `FastAPI`, но сначала давайте проверим зависимости и создадим ещё один модуль с `APIRouter`.
## Зависимости
Нам понадобятся некоторые зависимости, которые мы будем использовать в разных местах нашего приложения.
Мы поместим их в отдельный модуль `dependencies` (`app/dependencies.py`).
Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомизированный `X-Token` из заголовка:
//// tab | Python 3.9+
```Python hl_lines="3 6-8" title="app/dependencies.py"
{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!}
```
////
//// tab | Python 3.8+
```Python hl_lines="1 5-7" title="app/dependencies.py"
{!> ../../docs_src/bigger_applications/app_an/dependencies.py!}
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | Подсказка
Мы рекомендуем использовать версию `Annotated`, когда это возможно.
///
```Python hl_lines="1 4-6" title="app/dependencies.py"
{!> ../../docs_src/bigger_applications/app/dependencies.py!}
```
////
/// tip | Подсказка
Для простоты мы воспользовались неким воображаемым заголовоком.
В реальных случаях для получения наилучших результатов используйте интегрированные утилиты обеспечения безопасности [Security utilities](security/index.md){.internal-link target=_blank}.
///
## Ещё один модуль с `APIRouter`
Давайте также предположим, что у вас есть *эндпоинты*, отвечающие за обработку "items", и они находятся в модуле `app/routers/items.py`.
У вас определены следующие *операции пути* (*эндпоинты*):
* `/items/`
* `/items/{item_id}`
Тут всё точно также, как и в ситуации с `app/routers/users.py`.
Но теперь мы хотим поступить немного умнее и слегка упростить код.
Мы знаем, что все *эндпоинты* данного модуля имеют некоторые общие свойства:
* Префикс пути: `/items`.
* Теги: (один единственный тег: `items`).
* Дополнительные ответы (responses)
* Зависимости: использование созданной нами зависимости `X-token`
Таким образом, вместо того чтобы добавлять все эти свойства в функцию каждого отдельного *эндпоинта*,
мы добавим их в `APIRouter`.
```Python hl_lines="5-10 16 21" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
Так как каждый *эндпоинт* начинается с символа `/`:
```Python hl_lines="1"
@router.get("/{item_id}")
async def read_item(item_id: str):
...
```
...то префикс не должен заканчиваться символом `/`.
В нашем случае префиксом является `/items`.
Мы также можем добавить в наш маршрутизатор (router) список `тегов` (`tags`) и дополнительных `ответов` (`responses`), которые являются общими для каждого *эндпоинта*.
И ещё мы можем добавить в наш маршрутизатор список `зависимостей`, которые должны вызываться при каждом обращении к *эндпоинтам*.
/// tip | Подсказка
Обратите внимание, что также, как и в случае с зависимостями в декораторах *эндпоинтов* ([dependencies in *path operation decorators*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), никакого значения в *функцию эндпоинта* передано не будет.
///
В результате мы получим следующие эндпоинты:
* `/items/`
* `/items/{item_id}`
...как мы и планировали.
* Они будут помечены тегами из заданного списка, в нашем случае это `"items"`.
* Эти теги особенно полезны для системы автоматической интерактивной документации (с использованием OpenAPI).
* Каждый из них будет включать предопределенные ответы `responses`.
* Каждый *эндпоинт* будет иметь список зависимостей (`dependencies`), исполняемых перед вызовом *эндпоинта*.
* Если вы определили зависимости в самой операции пути, **то она также будет выполнена**.
* Сначала выполняются зависимости маршрутизатора, затем вызываются зависимости, определенные в декораторе *эндпоинта* ([`dependencies` in the decorator](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), и, наконец, обычные параметрические зависимости.
* Вы также можете добавить зависимости безопасности с областями видимости (`scopes`) [`Security` dependencies with `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}.
/// tip | Подсказка
Например, с помощью зависимостей в `APIRouter` мы можем потребовать аутентификации для доступа ко всей группе *эндпоинтов*, не указывая зависимости для каждой отдельной функции *эндпоинта*.
///
/// check | Заметка
Параметры `prefix`, `tags`, `responses` и `dependencies` относятся к функционалу **FastAPI**, помогающему избежать дублирования кода.
///
### Импорт зависимостей
Наш код находится в модуле `app.routers.items` (файл `app/routers/items.py`).
И нам нужно вызвать функцию зависимости из модуля `app.dependencies` (файл `app/dependencies.py`).
Мы используем операцию относительного импорта `..` для импорта зависимости:
```Python hl_lines="3" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
#### Как работает относительный импорт?
/// tip | Подсказка
Если вы прекрасно знаете, как работает импорт в Python, то переходите к следующему разделу.
///
Одна точка `.`, как в данном примере:
```Python
from .dependencies import get_token_header
```
означает:
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` расположен в каталоге `app/routers/`)...
* ... найдите модуль `dependencies` (файл `app/routers/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
К сожалению, такого файла не существует, и наши зависимости находятся в файле `app/dependencies.py`.
Вспомните, как выглядит файловая структура нашего приложения:
<img src="/img/tutorial/bigger-applications/package.svg">
---
Две точки `..`, как в данном примере:
```Python
from ..dependencies import get_token_header
```
означают:
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` находится в каталоге `app/routers/`)...
* ... перейдите в родительский пакет (каталог `app/`)...
* ... найдите в нём модуль `dependencies` (файл `app/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
Это работает верно! 🎉
---
Аналогично, если бы мы использовали три точки `...`, как здесь:
```Python
from ...dependencies import get_token_header
```
то это бы означало:
* Начните с пакета, в котором находится данный модуль (файл `app/routers/items.py` находится в каталоге `app/routers/`)...
* ... перейдите в родительский пакет (каталог `app/`)...
* ... затем перейдите в родительский пакет текущего пакета (такого пакета не существует, `app` находится на самом верхнем уровне 😱)...
* ... найдите в нём модуль `dependencies` (файл `app/dependencies.py`)...
* ... и импортируйте из него функцию `get_token_header`.
Это будет относиться к некоторому пакету, находящемуся на один уровень выше чем `app/` и содержащему свой собственный файл `__init__.py`. Но ничего такого у нас нет. Поэтому это приведет к ошибке в нашем примере. 🚨
Теперь вы знаете, как работает импорт в Python, и сможете использовать относительное импортирование в своих собственных приложениях любого уровня сложности. 🤓
### Добавление пользовательских тегов (`tags`), ответов (`responses`) и зависимостей (`dependencies`)
Мы не будем добавлять префикс `/items` и список тегов `tags=["items"]` для каждого *эндпоинта*, т.к. мы уже их добавили с помощью `APIRouter`.
Но помимо этого мы можем добавить новые теги для каждого отдельного *эндпоинта*, а также некоторые дополнительные ответы (`responses`), характерные для данного *эндпоинта*:
```Python hl_lines="30-31" title="app/routers/items.py"
{!../../docs_src/bigger_applications/app/routers/items.py!}
```
/// tip | Подсказка
Последний *эндпоинт* будет иметь следующую комбинацию тегов: `["items", "custom"]`.
А также в его документации будут содержаться оба ответа: один для `404` и другой для `403`.
///
## Модуль main в `FastAPI`
Теперь давайте посмотрим на модуль `app/main.py`.
Именно сюда вы импортируете и именно здесь вы используете класс `FastAPI`.
Это основной файл вашего приложения, который объединяет всё в одно целое.
И теперь, когда большая часть логики приложения разделена на отдельные модули, основной файл `app/main.py` будет достаточно простым.
### Импорт `FastAPI`
Вы импортируете и создаете класс `FastAPI` как обычно.
Мы даже можем объявить глобальные зависимости [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора:
```Python hl_lines="1 3 7" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
### Импорт `APIRouter`
Теперь мы импортируем другие суб-модули, содержащие `APIRouter`:
```Python hl_lines="4-5" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`.
### Как работает импорт?
Данная строка кода:
```Python
from .routers import items, users
```
означает:
* Начните с пакета, в котором содержится данный модуль (файл `app/main.py` содержится в каталоге `app/`)...
* ... найдите суб-пакет `routers` (каталог `app/routers/`)...
* ... и из него импортируйте суб-модули `items` (файл `app/routers/items.py`) и `users` (файл `app/routers/users.py`)...
В модуле `items` содержится переменная `router` (`items.router`), та самая, которую мы создали в файле `app/routers/items.py`, она является объектом класса `APIRouter`.
И затем мы сделаем то же самое для модуля `users`.
Мы также могли бы импортировать и другим методом:
```Python
from app.routers import items, users
```
/// info | Примечание
Первая версия является примером относительного импорта:
```Python
from .routers import items, users
```
Вторая версия является примером абсолютного импорта:
```Python
from app.routers import items, users
```
Узнать больше о пакетах и модулях в Python вы можете из <a href="https://docs.python.org/3/tutorial/modules.html" class="external-link" target="_blank">официальной документации Python о модулях</a>
///
### Избегайте конфликтов имен
Вместо того чтобы импортировать только переменную `router`, мы импортируем непосредственно суб-модуль `items`.
Мы делаем это потому, что у нас есть ещё одна переменная `router` в суб-модуле `users`.
Если бы мы импортировали их одну за другой, как показано в примере:
```Python
from .routers.items import router
from .routers.users import router
```
то переменная `router` из `users` переписал бы переменную `router` из `items`, и у нас не было бы возможности использовать их одновременно.
Поэтому, для того чтобы использовать обе эти переменные в одном файле, мы импортировали соответствующие суб-модули:
```Python hl_lines="5" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items`
Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`:
```Python hl_lines="10-11" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
/// info | Примечание
`users.router` содержит `APIRouter` из файла `app/routers/users.py`.
А `items.router` содержит `APIRouter` из файла `app/routers/items.py`.
///
С помощью `app.include_router()` мы можем добавить каждый из маршрутизаторов (`APIRouter`) в основное приложение `FastAPI`.
Он подключит все маршруты заданного маршрутизатора к нашему приложению.
/// note | Технические детали
Фактически, внутри он создаст все *операции пути* для каждой операции пути объявленной в `APIRouter`.
И под капотом всё будет работать так, как будто бы мы имеем дело с одним файлом приложения.
///
/// check | Заметка
При подключении маршрутизаторов не стоит беспокоиться о производительности.
Операция подключения займёт микросекунды и понадобится только при запуске приложения.
Таким образом, это не повлияет на производительность. ⚡
///
### Подключение `APIRouter` с пользовательскими префиксом (`prefix`), тегами (`tags`), ответами (`responses`), и зависимостями (`dependencies`)
Теперь давайте представим, что ваша организация передала вам файл `app/internal/admin.py`.
Он содержит `APIRouter` с некоторыми *эндпоитами* администрирования, которые ваша организация использует для нескольких проектов.
В данном примере это сделать очень просто. Но давайте предположим, что поскольку файл используется для нескольких проектов,
то мы не можем модифицировать его, добавляя префиксы (`prefix`), зависимости (`dependencies`), теги (`tags`), и т.д. непосредственно в `APIRouter`:
```Python hl_lines="3" title="app/internal/admin.py"
{!../../docs_src/bigger_applications/app/internal/admin.py!}
```
Но, несмотря на это, мы хотим использовать кастомный префикс (`prefix`) для подключенного маршрутизатора (`APIRouter`), в результате чего, каждая *операция пути* будет начинаться с `/admin`. Также мы хотим защитить наш маршрутизатор с помощью зависимостей, созданных для нашего проекта. И ещё мы хотим включить теги (`tags`) и ответы (`responses`).
Мы можем применить все вышеперечисленные настройки, не изменяя начальный `APIRouter`. Нам всего лишь нужно передать нужные параметры в `app.include_router()`.
```Python hl_lines="14-17" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
Таким образом, оригинальный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации.
В результате, в нашем приложении каждый *эндпоинт* модуля `admin` будет иметь:
* Префикс `/admin`.
* Тег `admin`.
* Зависимость `get_token_header`.
* Ответ `418`. 🍵
Это будет иметь место исключительно для `APIRouter` в нашем приложении, и не затронет любой другой код, использующий его.
Например, другие проекты, могут использовать тот же самый `APIRouter` с другими методами аутентификации.
### Подключение отдельного *эндпоинта*
Мы также можем добавить *эндпоинт* непосредственно в основное приложение `FastAPI`.
Здесь мы это делаем ... просто, чтобы показать, что это возможно 🤷:
```Python hl_lines="21-23" title="app/main.py"
{!../../docs_src/bigger_applications/app/main.py!}
```
и это будет работать корректно вместе с другими *эндпоинтами*, добавленными с помощью `app.include_router()`.
/// info | Сложные технические детали
**Примечание**: это сложная техническая деталь, которую, скорее всего, **вы можете пропустить**.
---
Маршрутизаторы (`APIRouter`) не "монтируются" по-отдельности и не изолируются от остального приложения.
Это происходит потому, что нужно включить их *эндпоинты* в OpenAPI схему и в интерфейс пользователя.
В силу того, что мы не можем их изолировать и "примонтировать" независимо от остальных, *эндпоинты* клонируются (пересоздаются) и не подключаются напрямую.
///
## Проверка автоматической документации API
Теперь запустите приложение:
<div class="termy">
```console
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
Откройте документацию по адресу <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
Вы увидите автоматическую API документацию. Она включает в себя маршруты из суб-модулей, используя верные маршруты, префиксы и теги:
<img src="/img/tutorial/bigger-applications/image01.png">
## Подключение существующего маршрута через новый префикс (`prefix`)
Вы можете использовать `.include_router()` несколько раз с одним и тем же маршрутом, применив различные префиксы.
Это может быть полезным, если нужно предоставить доступ к одному и тому же API через различные префиксы, например, `/api/v1` и `/api/latest`.
Это продвинутый способ, который вам может и не пригодится. Мы приводим его на случай, если вдруг вам это понадобится.
## Включение одного маршрутизатора (`APIRouter`) в другой
Точно так же, как вы включаете `APIRouter` в приложение `FastAPI`, вы можете включить `APIRouter` в другой `APIRouter`:
```Python
router.include_router(other_router)
```
Удостоверьтесь, что вы сделали это до того, как подключить маршрутизатор (`router`) к вашему `FastAPI` приложению, и *эндпоинты* маршрутизатора `other_router` были также подключены.

View File

@@ -18,7 +18,7 @@
Зависимости из dependencies выполнятся так же, как и обычные зависимости. Но их значения (если они были) не будут переданы в *функцию операции пути*.
/// подсказка
/// tip | Подсказка
Некоторые редакторы кода определяют неиспользуемые параметры функций и подсвечивают их как ошибку.
@@ -28,7 +28,7 @@
///
/// дополнительная | информация
/// info | Примечание
В этом примере мы используем выдуманные пользовательские заголовки `X-Key` и `X-Token`.

View File

@@ -182,7 +182,7 @@ oauth2_scheme(some, parameters)
Если он не видит заголовка `Authorization` или значение не имеет токена `Bearer`, то в ответ будет выдана ошибка с кодом состояния 401 (`UNAUTHORIZED`).
Для возврата ошибки даже не нужно проверять, существует ли токен. Вы можете быть уверены, что если ваша функция будет выполнилась, то в этом токене есть `строка`.
Для возврата ошибки даже не нужно проверять, существует ли токен. Вы можете быть уверены, что если ваша функция была выполнена, то в этом токене есть `строка`.
Проверить это можно уже сейчас в интерактивной документации:

View File

@@ -9,47 +9,39 @@
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:single">main.py</u>
<font color="#3465A4">INFO </font> Using path <font color="#3465A4">main.py</font>
<font color="#3465A4">INFO </font> Resolved absolute path <font color="#75507B">/home/user/code/awesomeapp/</font><font color="#AD7FA8">main.py</font>
<font color="#3465A4">INFO </font> Searching for package file structure from directories with <font color="#3465A4">__init__.py</font> files
<font color="#3465A4">INFO </font> Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
$ <font color="#4E9A06">fastapi</font> dev <u style="text-decoration-style:solid">main.py</u>
╭─ <font color="#8AE234"><b>Python module file</b></font> ─╮
│ │
│ 🐍 main.py │
│ │
╰──────────────────────╯
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
<font color="#3465A4">INFO </font> Importing module <font color="#4E9A06">main</font>
<font color="#3465A4">INFO </font> Found importable FastAPI app
Searching for package file structure from directories with
<font color="#3465A4">__init__.py</font> files
Importing from <font color="#75507B">/home/user/code/</font><font color="#AD7FA8">awesomeapp</font>
╭─ <font color="#8AE234"><b>Importable FastAPI app</b></font> ─╮
│ │
│ <span style="background-color:#272822"><font color="#FF4689">from</font></span><span style="background-color:#272822"><font color="#F8F8F2"> main </font></span><span style="background-color:#272822"><font color="#FF4689">import</font></span><span style="background-color:#272822"><font color="#F8F8F2"> app</font></span><span style="background-color:#272822"> </span> │
│ │
╰──────────────────────────╯
<span style="background-color:#007166"><font color="#D3D7CF"> module </font></span> 🐍 main.py
<font color="#3465A4">INFO </font> Using import string <font color="#8AE234"><b>main:app</b></font>
<span style="background-color:#007166"><font color="#D3D7CF"> code </font></span> Importing the FastAPI app object from the module with the
following code:
<span style="background-color:#C4A000"><font color="#2E3436">╭────────── FastAPI CLI - Development mode ───────────╮</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Serving at: http://127.0.0.1:8000 │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ API docs: http://127.0.0.1:8000/docs │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ Running in development mode, for production use: │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ </font></span><span style="background-color:#C4A000"><font color="#555753"><b>fastapi run</b></font></span><span style="background-color:#C4A000"><font color="#2E3436"> │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">│ │</font></span>
<span style="background-color:#C4A000"><font color="#2E3436">╰─────────────────────────────────────────────────────╯</font></span>
<u style="text-decoration-style:solid">from </u><u style="text-decoration-style:solid"><b>main</b></u><u style="text-decoration-style:solid"> import </u><u style="text-decoration-style:solid"><b>app</b></u>
<font color="#4E9A06">INFO</font>: Will watch for changes in these directories: [&apos;/home/user/code/awesomeapp&apos;]
<font color="#4E9A06">INFO</font>: Uvicorn running on <b>http://127.0.0.1:8000</b> (Press CTRL+C to quit)
<font color="#4E9A06">INFO</font>: Started reloader process [<font color="#34E2E2"><b>2265862</b></font>] using <font color="#34E2E2"><b>WatchFiles</b></font>
<font color="#4E9A06">INFO</font>: Started server process [<font color="#06989A">2265873</font>]
<font color="#4E9A06">INFO</font>: Waiting for application startup.
<font color="#4E9A06">INFO</font>: Application startup complete.
<span style="background-color:#007166"><font color="#D3D7CF"> app </font></span> Using import string: <font color="#3465A4">main:app</font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Server started at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> server </font></span> Documentation at <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000/docs</u></font>
<span style="background-color:#007166"><font color="#D3D7CF"> tip </font></span> Running in development mode, for production use:
<b>fastapi run</b>
Logs:
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Will watch for changes in these directories:
<b>[</b><font color="#4E9A06">&apos;/home/user/code/awesomeapp&apos;</font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Uvicorn running on <font color="#729FCF"><u style="text-decoration-style:solid">http://127.0.0.1:8000</u></font> <b>(</b>Press CTRL+C to
quit<b>)</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started reloader process <b>[</b><font color="#34E2E2"><b>383138</b></font><b>]</b> using WatchFiles
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Started server process <b>[</b><font color="#34E2E2"><b>383153</b></font><b>]</b>
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Waiting for application startup.
<span style="background-color:#007166"><font color="#D3D7CF"> INFO </font></span> Application startup complete.
```
</div>

View File

@@ -17,7 +17,7 @@ app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
if item.tax is not None:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict

View File

@@ -15,7 +15,7 @@ app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
if item.tax is not None:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict

View File

@@ -1,6 +1,6 @@
import strawberry
from fastapi import FastAPI
from strawberry.asgi import GraphQL
from strawberry.fastapi import GraphQLRouter
@strawberry.type
@@ -19,8 +19,7 @@ class Query:
schema = strawberry.Schema(query=Query)
graphql_app = GraphQL(schema)
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.add_route("/graphql", graphql_app)
app.add_websocket_route("/graphql", graphql_app)
app.include_router(graphql_app, prefix="/graphql")

View File

@@ -1,30 +0,0 @@
from . import models, schemas
def get_user(user_id: int):
return models.User.filter(models.User.id == user_id).first()
def get_user_by_email(email: str):
return models.User.filter(models.User.email == email).first()
def get_users(skip: int = 0, limit: int = 100):
return list(models.User.select().offset(skip).limit(limit))
def create_user(user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db_user.save()
return db_user
def get_items(skip: int = 0, limit: int = 100):
return list(models.Item.select().offset(skip).limit(limit))
def create_user_item(item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db_item.save()
return db_item

View File

@@ -1,24 +0,0 @@
from contextvars import ContextVar
import peewee
DATABASE_NAME = "test.db"
db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None}
db_state = ContextVar("db_state", default=db_state_default.copy())
class PeeweeConnectionState(peewee._ConnectionState):
def __init__(self, **kwargs):
super().__setattr__("_state", db_state)
super().__init__(**kwargs)
def __setattr__(self, name, value):
self._state.get()[name] = value
def __getattr__(self, name):
return self._state.get()[name]
db = peewee.SqliteDatabase(DATABASE_NAME, check_same_thread=False)
db._state = PeeweeConnectionState()

View File

@@ -1,79 +0,0 @@
import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from . import crud, database, models, schemas
from .database import db_state_default
database.db.connect()
database.db.create_tables([models.User, models.Item])
database.db.close()
app = FastAPI()
sleep_time = 10
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(user=user)
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users(skip: int = 0, limit: int = 100):
users = crud.get_users(skip=skip, limit=limit)
return users
@app.get(
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
)
def read_user(user_id: int):
db_user = crud.get_user(user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post(
"/users/{user_id}/items/",
response_model=schemas.Item,
dependencies=[Depends(get_db)],
)
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
return crud.create_user_item(item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
def read_items(skip: int = 0, limit: int = 100):
items = crud.get_items(skip=skip, limit=limit)
return items
@app.get(
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
)
def read_slow_users(skip: int = 0, limit: int = 100):
global sleep_time
sleep_time = max(0, sleep_time - 1)
time.sleep(sleep_time) # Fake long processing request
users = crud.get_users(skip=skip, limit=limit)
return users

View File

@@ -1,21 +0,0 @@
import peewee
from .database import db
class User(peewee.Model):
email = peewee.CharField(unique=True, index=True)
hashed_password = peewee.CharField()
is_active = peewee.BooleanField(default=True)
class Meta:
database = db
class Item(peewee.Model):
title = peewee.CharField(index=True)
description = peewee.CharField(index=True)
owner = peewee.ForeignKeyField(User, backref="items")
class Meta:
database = db

View File

@@ -1,49 +0,0 @@
from typing import Any, List, Union
import peewee
from pydantic import BaseModel
from pydantic.utils import GetterDict
class PeeweeGetterDict(GetterDict):
def get(self, key: Any, default: Any = None):
res = getattr(self._obj, key, default)
if isinstance(res, peewee.ModelSelect):
return list(res)
return res
class ItemBase(BaseModel):
title: str
description: Union[str, None] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.115.6"
__version__ = "0.115.8"
from starlette import status as status

View File

@@ -9,7 +9,15 @@ from typing_extensions import Annotated, Doc
class APIKeyBase(SecurityBase):
pass
@staticmethod
def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:
if not api_key:
if auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
return None
return api_key
class APIKeyQuery(APIKeyBase):
@@ -101,14 +109,7 @@ class APIKeyQuery(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.query_params.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
return self.check_api_key(api_key, self.auto_error)
class APIKeyHeader(APIKeyBase):
@@ -196,14 +197,7 @@ class APIKeyHeader(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.headers.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
return self.check_api_key(api_key, self.auto_error)
class APIKeyCookie(APIKeyBase):
@@ -291,11 +285,4 @@ class APIKeyCookie(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.cookies.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
return self.check_api_key(api_key, self.auto_error)

View File

@@ -63,7 +63,7 @@ class OAuth2PasswordRequestForm:
*,
grant_type: Annotated[
Union[str, None],
Form(pattern="password"),
Form(pattern="^password$"),
Doc(
"""
The OAuth2 spec says it is required and MUST be the fixed string
@@ -217,7 +217,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
self,
grant_type: Annotated[
str,
Form(pattern="password"),
Form(pattern="^password$"),
Doc(
"""
The OAuth2 spec says it is required and MUST be the fixed string

View File

@@ -29,6 +29,7 @@ classifiers = [
"Framework :: FastAPI",
"Framework :: Pydantic",
"Framework :: Pydantic :: 1",
"Framework :: Pydantic :: 2",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
@@ -37,11 +38,12 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Internet :: WWW/HTTP :: HTTP Servers",
"Topic :: Internet :: WWW/HTTP",
]
dependencies = [
"starlette>=0.40.0,<0.42.0",
"starlette>=0.40.0,<0.46.0",
"pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0",
"typing-extensions>=4.8.0",
]
@@ -60,9 +62,9 @@ standard = [
# For the test client
"httpx >=0.23.0",
# For templates
"jinja2 >=2.11.2",
"jinja2 >=3.1.5",
# For forms and file uploads
"python-multipart >=0.0.7",
"python-multipart >=0.0.18",
# To validate email fields
"email-validator >=2.0.0",
# Uvicorn with uvloop
@@ -79,9 +81,9 @@ all = [
# # For the test client
"httpx >=0.23.0",
# For templates
"jinja2 >=2.11.2",
"jinja2 >=3.1.5",
# For forms and file uploads
"python-multipart >=0.0.7",
"python-multipart >=0.0.18",
# For Starlette's SessionMiddleware, not commonly used with FastAPI
"itsdangerous >=1.1.0",
# For Starlette's schema generation, would not be used with FastAPI
@@ -161,6 +163,8 @@ filterwarnings = [
# Ref: https://github.com/python-trio/trio/pull/3054
# Remove once there's a new version of Trio
'ignore:The `hash` argument is deprecated*:DeprecationWarning:trio',
# Ignore flaky coverage / pytest warning about SQLite connection, only applies to Python 3.13 and Pydantic v1
'ignore:Exception ignored in. <sqlite3\.Connection object.*:pytest.PytestUnraisableExceptionWarning',
]
[tool.coverage.run]

View File

@@ -2,4 +2,5 @@ PyGithub>=2.3.0,<3.0.0
pydantic>=2.5.3,<3.0.0
pydantic-settings>=2.1.0,<3.0.0
httpx>=0.27.0,<0.28.0
pyyaml >=5.3.1,<7.0.0
smokeshow

View File

@@ -6,7 +6,7 @@ mypy ==1.8.0
dirty-equals ==0.8.0
sqlmodel==0.0.22
flask >=1.1.2,<4.0.0
anyio[trio] >=3.2.1,<4.0.0
anyio[trio] >=3.2.1,<5.0.0
PyJWT==2.8.0
pyyaml >=5.3.1,<7.0.0
passlib[bcrypt] >=1.7.2,<2.0.0

315
scripts/contributors.py Normal file
View File

@@ -0,0 +1,315 @@
import logging
import secrets
import subprocess
from collections import Counter
from datetime import datetime
from pathlib import Path
from typing import Any
import httpx
import yaml
from github import Github
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql"
prs_query = """
query Q($after: String) {
repository(name: "fastapi", owner: "fastapi") {
pullRequests(first: 100, after: $after) {
edges {
cursor
node {
number
labels(first: 100) {
nodes {
name
}
}
author {
login
avatarUrl
url
}
title
createdAt
lastEditedAt
updatedAt
state
reviews(first:100) {
nodes {
author {
login
avatarUrl
url
}
state
}
}
}
}
}
}
}
"""
class Author(BaseModel):
login: str
avatarUrl: str
url: str
class LabelNode(BaseModel):
name: str
class Labels(BaseModel):
nodes: list[LabelNode]
class ReviewNode(BaseModel):
author: Author | None = None
state: str
class Reviews(BaseModel):
nodes: list[ReviewNode]
class PullRequestNode(BaseModel):
number: int
labels: Labels
author: Author | None = None
title: str
createdAt: datetime
lastEditedAt: datetime | None = None
updatedAt: datetime | None = None
state: str
reviews: Reviews
class PullRequestEdge(BaseModel):
cursor: str
node: PullRequestNode
class PullRequests(BaseModel):
edges: list[PullRequestEdge]
class PRsRepository(BaseModel):
pullRequests: PullRequests
class PRsResponseData(BaseModel):
repository: PRsRepository
class PRsResponse(BaseModel):
data: PRsResponseData
class Settings(BaseSettings):
github_token: SecretStr
github_repository: str
httpx_timeout: int = 30
def get_graphql_response(
*,
settings: Settings,
query: str,
after: str | None = None,
) -> dict[str, Any]:
headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
variables = {"after": after}
response = httpx.post(
github_graphql_url,
headers=headers,
timeout=settings.httpx_timeout,
json={"query": query, "variables": variables, "operationName": "Q"},
)
if response.status_code != 200:
logging.error(f"Response was not 200, after: {after}")
logging.error(response.text)
raise RuntimeError(response.text)
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
def get_graphql_pr_edges(
*, settings: Settings, after: str | None = None
) -> list[PullRequestEdge]:
data = get_graphql_response(settings=settings, query=prs_query, after=after)
graphql_response = PRsResponse.model_validate(data)
return graphql_response.data.repository.pullRequests.edges
def get_pr_nodes(settings: Settings) -> list[PullRequestNode]:
pr_nodes: list[PullRequestNode] = []
pr_edges = get_graphql_pr_edges(settings=settings)
while pr_edges:
for edge in pr_edges:
pr_nodes.append(edge.node)
last_edge = pr_edges[-1]
pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor)
return pr_nodes
class ContributorsResults(BaseModel):
contributors: Counter[str]
translation_reviewers: Counter[str]
translators: Counter[str]
authors: dict[str, Author]
def get_contributors(pr_nodes: list[PullRequestNode]) -> ContributorsResults:
contributors = Counter[str]()
translation_reviewers = Counter[str]()
translators = Counter[str]()
authors: dict[str, Author] = {}
for pr in pr_nodes:
if pr.author:
authors[pr.author.login] = pr.author
is_lang = False
for label in pr.labels.nodes:
if label.name == "lang-all":
is_lang = True
break
for review in pr.reviews.nodes:
if review.author:
authors[review.author.login] = review.author
if is_lang:
translation_reviewers[review.author.login] += 1
if pr.state == "MERGED" and pr.author:
if is_lang:
translators[pr.author.login] += 1
else:
contributors[pr.author.login] += 1
return ContributorsResults(
contributors=contributors,
translation_reviewers=translation_reviewers,
translators=translators,
authors=authors,
)
def get_users_to_write(
*,
counter: Counter[str],
authors: dict[str, Author],
min_count: int = 2,
) -> dict[str, Any]:
users: dict[str, Any] = {}
for user, count in counter.most_common():
if count >= min_count:
author = authors[user]
users[user] = {
"login": user,
"count": count,
"avatarUrl": author.avatarUrl,
"url": author.url,
}
return users
def update_content(*, content_path: Path, new_content: Any) -> bool:
old_content = content_path.read_text(encoding="utf-8")
new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
if old_content == new_content:
logging.info(f"The content hasn't changed for {content_path}")
return False
content_path.write_text(new_content, encoding="utf-8")
logging.info(f"Updated {content_path}")
return True
def main() -> None:
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
pr_nodes = get_pr_nodes(settings=settings)
contributors_results = get_contributors(pr_nodes=pr_nodes)
authors = contributors_results.authors
top_contributors = get_users_to_write(
counter=contributors_results.contributors,
authors=authors,
)
top_translators = get_users_to_write(
counter=contributors_results.translators,
authors=authors,
)
top_translations_reviewers = get_users_to_write(
counter=contributors_results.translation_reviewers,
authors=authors,
)
# For local development
# contributors_path = Path("../docs/en/data/contributors.yml")
contributors_path = Path("./docs/en/data/contributors.yml")
# translators_path = Path("../docs/en/data/translators.yml")
translators_path = Path("./docs/en/data/translators.yml")
# translation_reviewers_path = Path("../docs/en/data/translation_reviewers.yml")
translation_reviewers_path = Path("./docs/en/data/translation_reviewers.yml")
updated = [
update_content(content_path=contributors_path, new_content=top_contributors),
update_content(content_path=translators_path, new_content=top_translators),
update_content(
content_path=translation_reviewers_path,
new_content=top_translations_reviewers,
),
]
if not any(updated):
logging.info("The data hasn't changed, finishing.")
return
logging.info("Setting up GitHub Actions git user")
subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
subprocess.run(
["git", "config", "user.email", "github-actions@github.com"], check=True
)
branch_name = f"fastapi-people-contributors-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
logging.info("Adding updated file")
subprocess.run(
[
"git",
"add",
str(contributors_path),
str(translators_path),
str(translation_reviewers_path),
],
check=True,
)
logging.info("Committing updated file")
message = "👥 Update FastAPI People - Contributors and Translators"
subprocess.run(["git", "commit", "-m", message], check=True)
logging.info("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
logging.info("Creating PR")
pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
logging.info(f"Created PR: {pr.number}")
logging.info("Finished")
if __name__ == "__main__":
main()

View File

@@ -7,12 +7,13 @@ from typing import Any, Dict, List, Union, cast
import httpx
from github import Github
from pydantic import BaseModel, BaseSettings, SecretStr
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
awaiting_label = "awaiting-review"
lang_all_label = "lang-all"
approved_label = "approved-1"
translations_path = Path(__file__).parent / "translations.yml"
github_graphql_url = "https://api.github.com/graphql"
questions_translations_category_id = "DIC_kwDOCZduT84CT5P9"
@@ -175,20 +176,23 @@ class AllDiscussionsResponse(BaseModel):
class Settings(BaseSettings):
model_config = {"env_ignore_empty": True}
github_repository: str
input_token: SecretStr
github_token: SecretStr
github_event_path: Path
github_event_name: Union[str, None] = None
httpx_timeout: int = 30
input_debug: Union[bool, None] = False
debug: Union[bool, None] = False
number: int | None = None
class PartialGitHubEventIssue(BaseModel):
number: int
number: int | None = None
class PartialGitHubEvent(BaseModel):
pull_request: PartialGitHubEventIssue
pull_request: PartialGitHubEventIssue | None = None
def get_graphql_response(
@@ -202,9 +206,7 @@ def get_graphql_response(
comment_id: Union[str, None] = None,
body: Union[str, None] = None,
) -> Dict[str, Any]:
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
# some fields are only used by one query, but GraphQL allows unused variables, so
# keep them here for simplicity
headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
variables = {
"after": after,
"category_id": category_id,
@@ -228,37 +230,40 @@ def get_graphql_response(
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}, category_id: {category_id}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return cast(Dict[str, Any], data)
def get_graphql_translation_discussions(*, settings: Settings):
def get_graphql_translation_discussions(
*, settings: Settings
) -> List[AllDiscussionsDiscussionNode]:
data = get_graphql_response(
settings=settings,
query=all_discussions_query,
category_id=questions_translations_category_id,
)
graphql_response = AllDiscussionsResponse.parse_obj(data)
graphql_response = AllDiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.nodes
def get_graphql_translation_discussion_comments_edges(
*, settings: Settings, discussion_number: int, after: Union[str, None] = None
):
) -> List[CommentsEdge]:
data = get_graphql_response(
settings=settings,
query=translation_discussion_query,
discussion_number=discussion_number,
after=after,
)
graphql_response = CommentsResponse.parse_obj(data)
graphql_response = CommentsResponse.model_validate(data)
return graphql_response.data.repository.discussion.comments.edges
def get_graphql_translation_discussion_comments(
*, settings: Settings, discussion_number: int
):
) -> list[Comment]:
comment_nodes: List[Comment] = []
discussion_edges = get_graphql_translation_discussion_comments_edges(
settings=settings, discussion_number=discussion_number
@@ -276,43 +281,49 @@ def get_graphql_translation_discussion_comments(
return comment_nodes
def create_comment(*, settings: Settings, discussion_id: str, body: str):
def create_comment(*, settings: Settings, discussion_id: str, body: str) -> Comment:
data = get_graphql_response(
settings=settings,
query=add_comment_mutation,
discussion_id=discussion_id,
body=body,
)
response = AddCommentResponse.parse_obj(data)
response = AddCommentResponse.model_validate(data)
return response.data.addDiscussionComment.comment
def update_comment(*, settings: Settings, comment_id: str, body: str):
def update_comment(*, settings: Settings, comment_id: str, body: str) -> Comment:
data = get_graphql_response(
settings=settings,
query=update_comment_mutation,
comment_id=comment_id,
body=body,
)
response = UpdateCommentResponse.parse_obj(data)
response = UpdateCommentResponse.model_validate(data)
return response.data.updateDiscussionComment.comment
if __name__ == "__main__":
def main() -> None:
settings = Settings()
if settings.input_debug:
if settings.debug:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
logging.debug(f"Using config: {settings.json()}")
g = Github(settings.input_token.get_secret_value())
logging.debug(f"Using config: {settings.model_dump_json()}")
g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
if not settings.github_event_path.is_file():
raise RuntimeError(
f"No github event file available at: {settings.github_event_path}"
)
contents = settings.github_event_path.read_text()
github_event = PartialGitHubEvent.parse_raw(contents)
github_event = PartialGitHubEvent.model_validate_json(contents)
logging.info(f"Using GitHub event: {github_event}")
number = (
github_event.pull_request and github_event.pull_request.number
) or settings.number
if number is None:
raise RuntimeError("No PR number available")
# Avoid race conditions with multiple labels
sleep_time = random.random() * 10 # random number between 0 and 10 seconds
@@ -323,8 +334,8 @@ if __name__ == "__main__":
time.sleep(sleep_time)
# Get PR
logging.debug(f"Processing PR: #{github_event.pull_request.number}")
pr = repo.get_pull(github_event.pull_request.number)
logging.debug(f"Processing PR: #{number}")
pr = repo.get_pull(number)
label_strs = {label.name for label in pr.get_labels()}
langs = []
for label in label_strs:
@@ -415,3 +426,7 @@ if __name__ == "__main__":
f"There doesn't seem to be anything to be done about PR #{pr.number}"
)
logging.info("Finished")
if __name__ == "__main__":
main()

401
scripts/people.py Normal file
View File

@@ -0,0 +1,401 @@
import logging
import secrets
import subprocess
import time
from collections import Counter
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Container, Union
import httpx
import yaml
from github import Github
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql"
questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0"
discussions_query = """
query Q($after: String, $category_id: ID) {
repository(name: "fastapi", owner: "fastapi") {
discussions(first: 100, after: $after, categoryId: $category_id) {
edges {
cursor
node {
number
author {
login
avatarUrl
url
}
createdAt
comments(first: 50) {
totalCount
nodes {
createdAt
author {
login
avatarUrl
url
}
isAnswer
replies(first: 10) {
totalCount
nodes {
createdAt
author {
login
avatarUrl
url
}
}
}
}
}
}
}
}
}
}
"""
class Author(BaseModel):
login: str
avatarUrl: str | None = None
url: str | None = None
class CommentsNode(BaseModel):
createdAt: datetime
author: Union[Author, None] = None
class Replies(BaseModel):
totalCount: int
nodes: list[CommentsNode]
class DiscussionsCommentsNode(CommentsNode):
replies: Replies
class DiscussionsComments(BaseModel):
totalCount: int
nodes: list[DiscussionsCommentsNode]
class DiscussionsNode(BaseModel):
number: int
author: Union[Author, None] = None
title: str | None = None
createdAt: datetime
comments: DiscussionsComments
class DiscussionsEdge(BaseModel):
cursor: str
node: DiscussionsNode
class Discussions(BaseModel):
edges: list[DiscussionsEdge]
class DiscussionsRepository(BaseModel):
discussions: Discussions
class DiscussionsResponseData(BaseModel):
repository: DiscussionsRepository
class DiscussionsResponse(BaseModel):
data: DiscussionsResponseData
class Settings(BaseSettings):
github_token: SecretStr
github_repository: str
httpx_timeout: int = 30
def get_graphql_response(
*,
settings: Settings,
query: str,
after: Union[str, None] = None,
category_id: Union[str, None] = None,
) -> dict[str, Any]:
headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
variables = {"after": after, "category_id": category_id}
response = httpx.post(
github_graphql_url,
headers=headers,
timeout=settings.httpx_timeout,
json={"query": query, "variables": variables, "operationName": "Q"},
)
if response.status_code != 200:
logging.error(
f"Response was not 200, after: {after}, category_id: {category_id}"
)
logging.error(response.text)
raise RuntimeError(response.text)
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}, category_id: {category_id}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
def get_graphql_question_discussion_edges(
*,
settings: Settings,
after: Union[str, None] = None,
) -> list[DiscussionsEdge]:
data = get_graphql_response(
settings=settings,
query=discussions_query,
after=after,
category_id=questions_category_id,
)
graphql_response = DiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.edges
class DiscussionExpertsResults(BaseModel):
commenters: Counter[str]
last_month_commenters: Counter[str]
three_months_commenters: Counter[str]
six_months_commenters: Counter[str]
one_year_commenters: Counter[str]
authors: dict[str, Author]
def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]:
discussion_nodes: list[DiscussionsNode] = []
discussion_edges = get_graphql_question_discussion_edges(settings=settings)
while discussion_edges:
for discussion_edge in discussion_edges:
discussion_nodes.append(discussion_edge.node)
last_edge = discussion_edges[-1]
# Handle GitHub secondary rate limits, requests per minute
time.sleep(5)
discussion_edges = get_graphql_question_discussion_edges(
settings=settings, after=last_edge.cursor
)
return discussion_nodes
def get_discussions_experts(
discussion_nodes: list[DiscussionsNode],
) -> DiscussionExpertsResults:
commenters = Counter[str]()
last_month_commenters = Counter[str]()
three_months_commenters = Counter[str]()
six_months_commenters = Counter[str]()
one_year_commenters = Counter[str]()
authors: dict[str, Author] = {}
now = datetime.now(tz=timezone.utc)
one_month_ago = now - timedelta(days=30)
three_months_ago = now - timedelta(days=90)
six_months_ago = now - timedelta(days=180)
one_year_ago = now - timedelta(days=365)
for discussion in discussion_nodes:
discussion_author_name = None
if discussion.author:
authors[discussion.author.login] = discussion.author
discussion_author_name = discussion.author.login
discussion_commentors: dict[str, datetime] = {}
for comment in discussion.comments.nodes:
if comment.author:
authors[comment.author.login] = comment.author
if comment.author.login != discussion_author_name:
author_time = discussion_commentors.get(
comment.author.login, comment.createdAt
)
discussion_commentors[comment.author.login] = max(
author_time, comment.createdAt
)
for reply in comment.replies.nodes:
if reply.author:
authors[reply.author.login] = reply.author
if reply.author.login != discussion_author_name:
author_time = discussion_commentors.get(
reply.author.login, reply.createdAt
)
discussion_commentors[reply.author.login] = max(
author_time, reply.createdAt
)
for author_name, author_time in discussion_commentors.items():
commenters[author_name] += 1
if author_time > one_month_ago:
last_month_commenters[author_name] += 1
if author_time > three_months_ago:
three_months_commenters[author_name] += 1
if author_time > six_months_ago:
six_months_commenters[author_name] += 1
if author_time > one_year_ago:
one_year_commenters[author_name] += 1
discussion_experts_results = DiscussionExpertsResults(
authors=authors,
commenters=commenters,
last_month_commenters=last_month_commenters,
three_months_commenters=three_months_commenters,
six_months_commenters=six_months_commenters,
one_year_commenters=one_year_commenters,
)
return discussion_experts_results
def get_top_users(
*,
counter: Counter[str],
authors: dict[str, Author],
skip_users: Container[str],
min_count: int = 2,
) -> list[dict[str, Any]]:
users: list[dict[str, Any]] = []
for commenter, count in counter.most_common(50):
if commenter in skip_users:
continue
if count >= min_count:
author = authors[commenter]
users.append(
{
"login": commenter,
"count": count,
"avatarUrl": author.avatarUrl,
"url": author.url,
}
)
return users
def get_users_to_write(
*,
counter: Counter[str],
authors: dict[str, Author],
min_count: int = 2,
) -> list[dict[str, Any]]:
users: dict[str, Any] = {}
users_list: list[dict[str, Any]] = []
for user, count in counter.most_common(60):
if count >= min_count:
author = authors[user]
user_data = {
"login": user,
"count": count,
"avatarUrl": author.avatarUrl,
"url": author.url,
}
users[user] = user_data
users_list.append(user_data)
return users_list
def update_content(*, content_path: Path, new_content: Any) -> bool:
old_content = content_path.read_text(encoding="utf-8")
new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
if old_content == new_content:
logging.info(f"The content hasn't changed for {content_path}")
return False
content_path.write_text(new_content, encoding="utf-8")
logging.info(f"Updated {content_path}")
return True
def main() -> None:
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
discussion_nodes = get_discussion_nodes(settings=settings)
experts_results = get_discussions_experts(discussion_nodes=discussion_nodes)
authors = experts_results.authors
maintainers_logins = {"tiangolo"}
maintainers = []
for login in maintainers_logins:
user = authors[login]
maintainers.append(
{
"login": login,
"answers": experts_results.commenters[login],
"avatarUrl": user.avatarUrl,
"url": user.url,
}
)
experts = get_users_to_write(
counter=experts_results.commenters,
authors=authors,
)
last_month_experts = get_users_to_write(
counter=experts_results.last_month_commenters,
authors=authors,
)
three_months_experts = get_users_to_write(
counter=experts_results.three_months_commenters,
authors=authors,
)
six_months_experts = get_users_to_write(
counter=experts_results.six_months_commenters,
authors=authors,
)
one_year_experts = get_users_to_write(
counter=experts_results.one_year_commenters,
authors=authors,
)
people = {
"maintainers": maintainers,
"experts": experts,
"last_month_experts": last_month_experts,
"three_months_experts": three_months_experts,
"six_months_experts": six_months_experts,
"one_year_experts": one_year_experts,
}
# For local development
# people_path = Path("../docs/en/data/people.yml")
people_path = Path("./docs/en/data/people.yml")
updated = update_content(content_path=people_path, new_content=people)
if not updated:
logging.info("The data hasn't changed, finishing.")
return
logging.info("Setting up GitHub Actions git user")
subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
subprocess.run(
["git", "config", "user.email", "github-actions@github.com"], check=True
)
branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
logging.info("Adding updated file")
subprocess.run(["git", "add", str(people_path)], check=True)
logging.info("Committing updated file")
message = "👥 Update FastAPI People - Experts"
subprocess.run(["git", "commit", "-m", message], check=True)
logging.info("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
logging.info("Creating PR")
pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
logging.info(f"Created PR: {pr.number}")
logging.info("Finished")
if __name__ == "__main__":
main()

221
scripts/sponsors.py Normal file
View File

@@ -0,0 +1,221 @@
import logging
import secrets
import subprocess
from collections import defaultdict
from pathlib import Path
from typing import Any
import httpx
import yaml
from github import Github
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql"
sponsors_query = """
query Q($after: String) {
user(login: "tiangolo") {
sponsorshipsAsMaintainer(first: 100, after: $after) {
edges {
cursor
node {
sponsorEntity {
... on Organization {
login
avatarUrl
url
}
... on User {
login
avatarUrl
url
}
}
tier {
name
monthlyPriceInDollars
}
}
}
}
}
}
"""
class SponsorEntity(BaseModel):
login: str
avatarUrl: str
url: str
class Tier(BaseModel):
name: str
monthlyPriceInDollars: float
class SponsorshipAsMaintainerNode(BaseModel):
sponsorEntity: SponsorEntity
tier: Tier
class SponsorshipAsMaintainerEdge(BaseModel):
cursor: str
node: SponsorshipAsMaintainerNode
class SponsorshipAsMaintainer(BaseModel):
edges: list[SponsorshipAsMaintainerEdge]
class SponsorsUser(BaseModel):
sponsorshipsAsMaintainer: SponsorshipAsMaintainer
class SponsorsResponseData(BaseModel):
user: SponsorsUser
class SponsorsResponse(BaseModel):
data: SponsorsResponseData
class Settings(BaseSettings):
sponsors_token: SecretStr
pr_token: SecretStr
github_repository: str
httpx_timeout: int = 30
def get_graphql_response(
*,
settings: Settings,
query: str,
after: str | None = None,
) -> dict[str, Any]:
headers = {"Authorization": f"token {settings.sponsors_token.get_secret_value()}"}
variables = {"after": after}
response = httpx.post(
github_graphql_url,
headers=headers,
timeout=settings.httpx_timeout,
json={"query": query, "variables": variables, "operationName": "Q"},
)
if response.status_code != 200:
logging.error(f"Response was not 200, after: {after}")
logging.error(response.text)
raise RuntimeError(response.text)
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
def get_graphql_sponsor_edges(
*, settings: Settings, after: str | None = None
) -> list[SponsorshipAsMaintainerEdge]:
data = get_graphql_response(settings=settings, query=sponsors_query, after=after)
graphql_response = SponsorsResponse.model_validate(data)
return graphql_response.data.user.sponsorshipsAsMaintainer.edges
def get_individual_sponsors(
settings: Settings,
) -> defaultdict[float, dict[str, SponsorEntity]]:
nodes: list[SponsorshipAsMaintainerNode] = []
edges = get_graphql_sponsor_edges(settings=settings)
while edges:
for edge in edges:
nodes.append(edge.node)
last_edge = edges[-1]
edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor)
tiers: defaultdict[float, dict[str, SponsorEntity]] = defaultdict(dict)
for node in nodes:
tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = (
node.sponsorEntity
)
return tiers
def update_content(*, content_path: Path, new_content: Any) -> bool:
old_content = content_path.read_text(encoding="utf-8")
new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
if old_content == new_content:
logging.info(f"The content hasn't changed for {content_path}")
return False
content_path.write_text(new_content, encoding="utf-8")
logging.info(f"Updated {content_path}")
return True
def main() -> None:
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.pr_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
tiers = get_individual_sponsors(settings=settings)
keys = list(tiers.keys())
keys.sort(reverse=True)
sponsors = []
for key in keys:
sponsor_group = []
for login, sponsor in tiers[key].items():
sponsor_group.append(
{"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url}
)
sponsors.append(sponsor_group)
github_sponsors = {
"sponsors": sponsors,
}
# For local development
# github_sponsors_path = Path("../docs/en/data/github_sponsors.yml")
github_sponsors_path = Path("./docs/en/data/github_sponsors.yml")
updated = update_content(
content_path=github_sponsors_path, new_content=github_sponsors
)
if not updated:
logging.info("The data hasn't changed, finishing.")
return
logging.info("Setting up GitHub Actions git user")
subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
subprocess.run(
["git", "config", "user.email", "github-actions@github.com"], check=True
)
branch_name = f"fastapi-people-sponsors-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
logging.info("Adding updated file")
subprocess.run(
[
"git",
"add",
str(github_sponsors_path),
],
check=True,
)
logging.info("Committing updated file")
message = "👥 Update FastAPI People - Sponsors"
subprocess.run(["git", "commit", "-m", message], check=True)
logging.info("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
logging.info("Creating PR")
pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
logging.info(f"Created PR: {pr.number}")
logging.info("Finished")
if __name__ == "__main__":
main()

80
scripts/topic_repos.py Normal file
View File

@@ -0,0 +1,80 @@
import logging
import secrets
import subprocess
from pathlib import Path
import yaml
from github import Github
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
github_repository: str
github_token: SecretStr
class Repo(BaseModel):
name: str
html_url: str
stars: int
owner_login: str
owner_html_url: str
def main() -> None:
logging.basicConfig(level=logging.INFO)
settings = Settings()
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.github_token.get_secret_value(), per_page=100)
r = g.get_repo(settings.github_repository)
repos = g.search_repositories(query="topic:fastapi")
repos_list = list(repos)
final_repos: list[Repo] = []
for repo in repos_list[:100]:
if repo.full_name == settings.github_repository:
continue
final_repos.append(
Repo(
name=repo.name,
html_url=repo.html_url,
stars=repo.stargazers_count,
owner_login=repo.owner.login,
owner_html_url=repo.owner.html_url,
)
)
data = [repo.model_dump() for repo in final_repos]
# Local development
# repos_path = Path("../docs/en/data/topic_repos.yml")
repos_path = Path("./docs/en/data/topic_repos.yml")
repos_old_content = repos_path.read_text(encoding="utf-8")
new_repos_content = yaml.dump(data, sort_keys=False, width=200, allow_unicode=True)
if repos_old_content == new_repos_content:
logging.info("The data hasn't changed. Finishing.")
return
repos_path.write_text(new_repos_content, encoding="utf-8")
logging.info("Setting up GitHub Actions git user")
subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
subprocess.run(
["git", "config", "user.email", "github-actions@github.com"], check=True
)
branch_name = f"fastapi-topic-repos-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
logging.info("Adding updated file")
subprocess.run(["git", "add", str(repos_path)], check=True)
logging.info("Committing updated file")
message = "👥 Update FastAPI GitHub topic repositories"
subprocess.run(["git", "commit", "-m", message], check=True)
logging.info("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
logging.info("Creating PR")
pr = r.create_pull(title=message, body=message, base="master", head=branch_name)
logging.info(f"Created PR: {pr.number}")
logging.info("Finished")
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,4 @@
import pytest
from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
@@ -137,10 +138,18 @@ def test_strict_login_no_grant_type():
)
def test_strict_login_incorrect_grant_type():
@pytest.mark.parametrize(
argnames=["grant_type"],
argvalues=[
pytest.param("incorrect", id="incorrect value"),
pytest.param("passwordblah", id="password with suffix"),
pytest.param("blahpassword", id="password with prefix"),
],
)
def test_strict_login_incorrect_grant_type(grant_type: str):
response = client.post(
"/login",
data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
assert response.json() == IsDict(
@@ -149,9 +158,9 @@ def test_strict_login_incorrect_grant_type():
{
"type": "string_pattern_mismatch",
"loc": ["body", "grant_type"],
"msg": "String should match pattern 'password'",
"input": "incorrect",
"ctx": {"pattern": "password"},
"msg": "String should match pattern '^password$'",
"input": grant_type,
"ctx": {"pattern": "^password$"},
}
]
}
@@ -161,9 +170,9 @@ def test_strict_login_incorrect_grant_type():
"detail": [
{
"loc": ["body", "grant_type"],
"msg": 'string does not match regex "password"',
"msg": 'string does not match regex "^password$"',
"type": "value_error.str.regex",
"ctx": {"pattern": "password"},
"ctx": {"pattern": "^password$"},
}
]
}
@@ -248,7 +257,7 @@ def test_openapi_schema():
"properties": {
"grant_type": {
"title": "Grant Type",
"pattern": "password",
"pattern": "^password$",
"type": "string",
},
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,6 @@
from typing import Optional
import pytest
from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
@@ -141,10 +142,18 @@ def test_strict_login_no_grant_type():
)
def test_strict_login_incorrect_grant_type():
@pytest.mark.parametrize(
argnames=["grant_type"],
argvalues=[
pytest.param("incorrect", id="incorrect value"),
pytest.param("passwordblah", id="password with suffix"),
pytest.param("blahpassword", id="password with prefix"),
],
)
def test_strict_login_incorrect_grant_type(grant_type: str):
response = client.post(
"/login",
data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
assert response.json() == IsDict(
@@ -153,9 +162,9 @@ def test_strict_login_incorrect_grant_type():
{
"type": "string_pattern_mismatch",
"loc": ["body", "grant_type"],
"msg": "String should match pattern 'password'",
"input": "incorrect",
"ctx": {"pattern": "password"},
"msg": "String should match pattern '^password$'",
"input": grant_type,
"ctx": {"pattern": "^password$"},
}
]
}
@@ -165,9 +174,9 @@ def test_strict_login_incorrect_grant_type():
"detail": [
{
"loc": ["body", "grant_type"],
"msg": 'string does not match regex "password"',
"msg": 'string does not match regex "^password$"',
"type": "value_error.str.regex",
"ctx": {"pattern": "password"},
"ctx": {"pattern": "^password$"},
}
]
}
@@ -252,7 +261,7 @@ def test_openapi_schema():
"properties": {
"grant_type": {
"title": "Grant Type",
"pattern": "password",
"pattern": "^password$",
"type": "string",
},
"username": {"title": "Username", "type": "string"},

View File

@@ -1,5 +1,6 @@
from typing import Optional
import pytest
from dirty_equals import IsDict
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict
@@ -142,10 +143,18 @@ def test_strict_login_no_grant_type():
)
def test_strict_login_incorrect_grant_type():
@pytest.mark.parametrize(
argnames=["grant_type"],
argvalues=[
pytest.param("incorrect", id="incorrect value"),
pytest.param("passwordblah", id="password with suffix"),
pytest.param("blahpassword", id="password with prefix"),
],
)
def test_strict_login_incorrect_grant_type(grant_type: str):
response = client.post(
"/login",
data={"username": "johndoe", "password": "secret", "grant_type": "incorrect"},
data={"username": "johndoe", "password": "secret", "grant_type": grant_type},
)
assert response.status_code == 422
assert response.json() == IsDict(
@@ -154,9 +163,9 @@ def test_strict_login_incorrect_grant_type():
{
"type": "string_pattern_mismatch",
"loc": ["body", "grant_type"],
"msg": "String should match pattern 'password'",
"input": "incorrect",
"ctx": {"pattern": "password"},
"msg": "String should match pattern '^password$'",
"input": grant_type,
"ctx": {"pattern": "^password$"},
}
]
}
@@ -166,9 +175,9 @@ def test_strict_login_incorrect_grant_type():
"detail": [
{
"loc": ["body", "grant_type"],
"msg": 'string does not match regex "password"',
"msg": 'string does not match regex "^password$"',
"type": "value_error.str.regex",
"ctx": {"pattern": "password"},
"ctx": {"pattern": "^password$"},
}
]
}
@@ -253,7 +262,7 @@ def test_openapi_schema():
"properties": {
"grant_type": {
"title": "Grant Type",
"pattern": "password",
"pattern": "^password$",
"type": "string",
},
"username": {"title": "Username", "type": "string"},

View File

@@ -1,17 +1,35 @@
import importlib
import pytest
from fastapi.testclient import TestClient
from docs_src.additional_status_codes.tutorial001 import app
client = TestClient(app)
from ...utils import needs_py39, needs_py310
def test_update():
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py39", marks=needs_py39),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.additional_status_codes.{request.param}")
client = TestClient(mod.app)
return client
def test_update(client: TestClient):
response = client.put("/items/foo", json={"name": "Wrestlers"})
assert response.status_code == 200, response.text
assert response.json() == {"name": "Wrestlers", "size": None}
def test_create():
def test_create(client: TestClient):
response = client.put("/items/red", json={"name": "Chillies"})
assert response.status_code == 201, response.text
assert response.json() == {"name": "Chillies", "size": None}

View File

@@ -1,17 +0,0 @@
from fastapi.testclient import TestClient
from docs_src.additional_status_codes.tutorial001_an import app
client = TestClient(app)
def test_update():
response = client.put("/items/foo", json={"name": "Wrestlers"})
assert response.status_code == 200, response.text
assert response.json() == {"name": "Wrestlers", "size": None}
def test_create():
response = client.put("/items/red", json={"name": "Chillies"})
assert response.status_code == 201, response.text
assert response.json() == {"name": "Chillies", "size": None}

View File

@@ -1,26 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.additional_status_codes.tutorial001_an_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_update(client: TestClient):
response = client.put("/items/foo", json={"name": "Wrestlers"})
assert response.status_code == 200, response.text
assert response.json() == {"name": "Wrestlers", "size": None}
@needs_py310
def test_create(client: TestClient):
response = client.put("/items/red", json={"name": "Chillies"})
assert response.status_code == 201, response.text
assert response.json() == {"name": "Chillies", "size": None}

View File

@@ -1,26 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.additional_status_codes.tutorial001_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_update(client: TestClient):
response = client.put("/items/foo", json={"name": "Wrestlers"})
assert response.status_code == 200, response.text
assert response.json() == {"name": "Wrestlers", "size": None}
@needs_py39
def test_create(client: TestClient):
response = client.put("/items/red", json={"name": "Chillies"})
assert response.status_code == 201, response.text
assert response.json() == {"name": "Chillies", "size": None}

View File

@@ -1,26 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.additional_status_codes.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_update(client: TestClient):
response = client.put("/items/foo", json={"name": "Wrestlers"})
assert response.status_code == 200, response.text
assert response.json() == {"name": "Wrestlers", "size": None}
@needs_py310
def test_create(client: TestClient):
response = client.put("/items/red", json={"name": "Chillies"})
assert response.status_code == 201, response.text
assert response.json() == {"name": "Chillies", "size": None}

View File

@@ -1,14 +1,31 @@
import importlib
import os
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from docs_src.background_tasks.tutorial002 import app
client = TestClient(app)
from ...utils import needs_py39, needs_py310
def test():
@pytest.fixture(
name="client",
params=[
"tutorial002",
pytest.param("tutorial002_py310", marks=needs_py310),
"tutorial002_an",
pytest.param("tutorial002_an_py39", marks=needs_py39),
pytest.param("tutorial002_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.background_tasks.{request.param}")
client = TestClient(mod.app)
return client
def test(client: TestClient):
log = Path("log.txt")
if log.is_file():
os.remove(log) # pragma: no cover

View File

@@ -1,19 +0,0 @@
import os
from pathlib import Path
from fastapi.testclient import TestClient
from docs_src.background_tasks.tutorial002_an import app
client = TestClient(app)
def test():
log = Path("log.txt")
if log.is_file():
os.remove(log) # pragma: no cover
response = client.post("/send-notification/foo@example.com?q=some-query")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Message sent"}
with open("./log.txt") as f:
assert "found query: some-query\nmessage to foo@example.com" in f.read()

View File

@@ -1,21 +0,0 @@
import os
from pathlib import Path
from fastapi.testclient import TestClient
from ...utils import needs_py310
@needs_py310
def test():
from docs_src.background_tasks.tutorial002_an_py310 import app
client = TestClient(app)
log = Path("log.txt")
if log.is_file():
os.remove(log) # pragma: no cover
response = client.post("/send-notification/foo@example.com?q=some-query")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Message sent"}
with open("./log.txt") as f:
assert "found query: some-query\nmessage to foo@example.com" in f.read()

View File

@@ -1,21 +0,0 @@
import os
from pathlib import Path
from fastapi.testclient import TestClient
from ...utils import needs_py39
@needs_py39
def test():
from docs_src.background_tasks.tutorial002_an_py39 import app
client = TestClient(app)
log = Path("log.txt")
if log.is_file():
os.remove(log) # pragma: no cover
response = client.post("/send-notification/foo@example.com?q=some-query")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Message sent"}
with open("./log.txt") as f:
assert "found query: some-query\nmessage to foo@example.com" in f.read()

View File

@@ -1,21 +0,0 @@
import os
from pathlib import Path
from fastapi.testclient import TestClient
from ...utils import needs_py310
@needs_py310
def test():
from docs_src.background_tasks.tutorial002_py310 import app
client = TestClient(app)
log = Path("log.txt")
if log.is_file():
os.remove(log) # pragma: no cover
response = client.post("/send-notification/foo@example.com?q=some-query")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Message sent"}
with open("./log.txt") as f:
assert "found query: some-query\nmessage to foo@example.com" in f.read()

View File

@@ -1,13 +1,24 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.bigger_applications.app.main import app
client = TestClient(app)
@pytest.fixture(
name="client",
params=[
"app_an.main",
pytest.param("app_an_py39.main", marks=needs_py39),
"app.main",
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.bigger_applications.{request.param}")
client = TestClient(mod.app)
return client

View File

@@ -1,715 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
@pytest.fixture(name="client")
def get_client():
from docs_src.bigger_applications.app_an.main import app
client = TestClient(app)
return client
def test_users_token_jessica(client: TestClient):
response = client.get("/users?token=jessica")
assert response.status_code == 200
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
def test_users_with_no_token(client: TestClient):
response = client.get("/users")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_users_foo_token_jessica(client: TestClient):
response = client.get("/users/foo?token=jessica")
assert response.status_code == 200
assert response.json() == {"username": "foo"}
def test_users_foo_with_no_token(client: TestClient):
response = client.get("/users/foo")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_users_me_token_jessica(client: TestClient):
response = client.get("/users/me?token=jessica")
assert response.status_code == 200
assert response.json() == {"username": "fakecurrentuser"}
def test_users_me_with_no_token(client: TestClient):
response = client.get("/users/me")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_users_token_monica_with_no_jessica(client: TestClient):
response = client.get("/users?token=monica")
assert response.status_code == 400
assert response.json() == {"detail": "No Jessica token provided"}
def test_items_token_jessica(client: TestClient):
response = client.get(
"/items?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200
assert response.json() == {
"plumbus": {"name": "Plumbus"},
"gun": {"name": "Portal Gun"},
}
def test_items_with_no_token_jessica(client: TestClient):
response = client.get("/items", headers={"X-Token": "fake-super-secret-token"})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_items_plumbus_token_jessica(client: TestClient):
response = client.get(
"/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200
assert response.json() == {"name": "Plumbus", "item_id": "plumbus"}
def test_items_bar_token_jessica(client: TestClient):
response = client.get(
"/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 404
assert response.json() == {"detail": "Item not found"}
def test_items_plumbus_with_no_token(client: TestClient):
response = client.get(
"/items/plumbus", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_items_with_invalid_token(client: TestClient):
response = client.get("/items?token=jessica", headers={"X-Token": "invalid"})
assert response.status_code == 400
assert response.json() == {"detail": "X-Token header invalid"}
def test_items_bar_with_invalid_token(client: TestClient):
response = client.get("/items/bar?token=jessica", headers={"X-Token": "invalid"})
assert response.status_code == 400
assert response.json() == {"detail": "X-Token header invalid"}
def test_items_with_missing_x_token_header(client: TestClient):
response = client.get("/items?token=jessica")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
def test_items_plumbus_with_missing_x_token_header(client: TestClient):
response = client.get("/items/plumbus?token=jessica")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
def test_root_token_jessica(client: TestClient):
response = client.get("/?token=jessica")
assert response.status_code == 200
assert response.json() == {"message": "Hello Bigger Applications!"}
def test_root_with_no_token(client: TestClient):
response = client.get("/")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_put_no_header(client: TestClient):
response = client.put("/items/foo")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_put_invalid_header(client: TestClient):
response = client.put("/items/foo", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_put(client: TestClient):
response = client.put(
"/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200, response.text
assert response.json() == {"item_id": "plumbus", "name": "The great Plumbus"}
def test_put_forbidden(client: TestClient):
response = client.put(
"/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 403, response.text
assert response.json() == {"detail": "You can only update the item: plumbus"}
def test_admin(client: TestClient):
response = client.post(
"/admin/?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200, response.text
assert response.json() == {"message": "Admin getting schwifty"}
def test_admin_invalid_header(client: TestClient):
response = client.post("/admin/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/": {
"get": {
"tags": ["users"],
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/users/me": {
"get": {
"tags": ["users"],
"summary": "Read User Me",
"operationId": "read_user_me_users_me_get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/users/{username}": {
"get": {
"tags": ["users"],
"summary": "Read User",
"operationId": "read_user_users__username__get",
"parameters": [
{
"required": True,
"schema": {"title": "Username", "type": "string"},
"name": "username",
"in": "path",
},
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/items/": {
"get": {
"tags": ["items"],
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"404": {"description": "Not found"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/items/{item_id}": {
"get": {
"tags": ["items"],
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"404": {"description": "Not found"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
},
"put": {
"tags": ["items", "custom"],
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"404": {"description": "Not found"},
"403": {"description": "Operation forbidden"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
},
},
"/admin/": {
"post": {
"tags": ["admin"],
"summary": "Update Admin",
"operationId": "update_admin_admin__post",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"418": {"description": "I'm a teapot"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/": {
"get": {
"summary": "Root",
"operationId": "root__get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
},
"components": {
"schemas": {
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
}
},
}

View File

@@ -1,742 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.bigger_applications.app_an_py39.main import app
client = TestClient(app)
return client
@needs_py39
def test_users_token_jessica(client: TestClient):
response = client.get("/users?token=jessica")
assert response.status_code == 200
assert response.json() == [{"username": "Rick"}, {"username": "Morty"}]
@needs_py39
def test_users_with_no_token(client: TestClient):
response = client.get("/users")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_users_foo_token_jessica(client: TestClient):
response = client.get("/users/foo?token=jessica")
assert response.status_code == 200
assert response.json() == {"username": "foo"}
@needs_py39
def test_users_foo_with_no_token(client: TestClient):
response = client.get("/users/foo")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_users_me_token_jessica(client: TestClient):
response = client.get("/users/me?token=jessica")
assert response.status_code == 200
assert response.json() == {"username": "fakecurrentuser"}
@needs_py39
def test_users_me_with_no_token(client: TestClient):
response = client.get("/users/me")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_users_token_monica_with_no_jessica(client: TestClient):
response = client.get("/users?token=monica")
assert response.status_code == 400
assert response.json() == {"detail": "No Jessica token provided"}
@needs_py39
def test_items_token_jessica(client: TestClient):
response = client.get(
"/items?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200
assert response.json() == {
"plumbus": {"name": "Plumbus"},
"gun": {"name": "Portal Gun"},
}
@needs_py39
def test_items_with_no_token_jessica(client: TestClient):
response = client.get("/items", headers={"X-Token": "fake-super-secret-token"})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_items_plumbus_token_jessica(client: TestClient):
response = client.get(
"/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200
assert response.json() == {"name": "Plumbus", "item_id": "plumbus"}
@needs_py39
def test_items_bar_token_jessica(client: TestClient):
response = client.get(
"/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 404
assert response.json() == {"detail": "Item not found"}
@needs_py39
def test_items_plumbus_with_no_token(client: TestClient):
response = client.get(
"/items/plumbus", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_items_with_invalid_token(client: TestClient):
response = client.get("/items?token=jessica", headers={"X-Token": "invalid"})
assert response.status_code == 400
assert response.json() == {"detail": "X-Token header invalid"}
@needs_py39
def test_items_bar_with_invalid_token(client: TestClient):
response = client.get("/items/bar?token=jessica", headers={"X-Token": "invalid"})
assert response.status_code == 400
assert response.json() == {"detail": "X-Token header invalid"}
@needs_py39
def test_items_with_missing_x_token_header(client: TestClient):
response = client.get("/items?token=jessica")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
@needs_py39
def test_items_plumbus_with_missing_x_token_header(client: TestClient):
response = client.get("/items/plumbus?token=jessica")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
@needs_py39
def test_root_token_jessica(client: TestClient):
response = client.get("/?token=jessica")
assert response.status_code == 200
assert response.json() == {"message": "Hello Bigger Applications!"}
@needs_py39
def test_root_with_no_token(client: TestClient):
response = client.get("/")
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_put_no_header(client: TestClient):
response = client.put("/items/foo")
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["query", "token"],
"msg": "Field required",
"input": None,
},
{
"type": "missing",
"loc": ["header", "x-token"],
"msg": "Field required",
"input": None,
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["query", "token"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["header", "x-token"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py39
def test_put_invalid_header(client: TestClient):
response = client.put("/items/foo", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
@needs_py39
def test_put(client: TestClient):
response = client.put(
"/items/plumbus?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200, response.text
assert response.json() == {"item_id": "plumbus", "name": "The great Plumbus"}
@needs_py39
def test_put_forbidden(client: TestClient):
response = client.put(
"/items/bar?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 403, response.text
assert response.json() == {"detail": "You can only update the item: plumbus"}
@needs_py39
def test_admin(client: TestClient):
response = client.post(
"/admin/?token=jessica", headers={"X-Token": "fake-super-secret-token"}
)
assert response.status_code == 200, response.text
assert response.json() == {"message": "Admin getting schwifty"}
@needs_py39
def test_admin_invalid_header(client: TestClient):
response = client.post("/admin/", headers={"X-Token": "invalid"})
assert response.status_code == 400, response.text
assert response.json() == {"detail": "X-Token header invalid"}
@needs_py39
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/users/": {
"get": {
"tags": ["users"],
"summary": "Read Users",
"operationId": "read_users_users__get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/users/me": {
"get": {
"tags": ["users"],
"summary": "Read User Me",
"operationId": "read_user_me_users_me_get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/users/{username}": {
"get": {
"tags": ["users"],
"summary": "Read User",
"operationId": "read_user_users__username__get",
"parameters": [
{
"required": True,
"schema": {"title": "Username", "type": "string"},
"name": "username",
"in": "path",
},
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/items/": {
"get": {
"tags": ["items"],
"summary": "Read Items",
"operationId": "read_items_items__get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"404": {"description": "Not found"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/items/{item_id}": {
"get": {
"tags": ["items"],
"summary": "Read Item",
"operationId": "read_item_items__item_id__get",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"404": {"description": "Not found"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
},
"put": {
"tags": ["items", "custom"],
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "string"},
"name": "item_id",
"in": "path",
},
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"404": {"description": "Not found"},
"403": {"description": "Operation forbidden"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
},
},
"/admin/": {
"post": {
"tags": ["admin"],
"summary": "Update Admin",
"operationId": "update_admin_admin__post",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
},
{
"required": True,
"schema": {"title": "X-Token", "type": "string"},
"name": "x-token",
"in": "header",
},
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"418": {"description": "I'm a teapot"},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/": {
"get": {
"summary": "Root",
"operationId": "root__get",
"parameters": [
{
"required": True,
"schema": {"title": "Token", "type": "string"},
"name": "token",
"in": "query",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
},
"components": {
"schemas": {
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
}
},
}

View File

@@ -1,15 +1,24 @@
import importlib
from unittest.mock import patch
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture
def client():
from docs_src.body.tutorial001 import app
client = TestClient(app)
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body.{request.param}")
client = TestClient(mod.app)
return client

View File

@@ -1,498 +0,0 @@
from unittest.mock import patch
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture
def client():
from docs_src.body.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_body_float(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
}
@needs_py310
def test_post_with_str_float(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": "50.5"})
assert response.status_code == 200
assert response.json() == {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
}
@needs_py310
def test_post_with_str_float_description(client: TestClient):
response = client.post(
"/items/", json={"name": "Foo", "price": "50.5", "description": "Some Foo"}
)
assert response.status_code == 200
assert response.json() == {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": None,
}
@needs_py310
def test_post_with_str_float_description_tax(client: TestClient):
response = client.post(
"/items/",
json={"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3},
)
assert response.status_code == 200
assert response.json() == {
"name": "Foo",
"price": 50.5,
"description": "Some Foo",
"tax": 0.3,
}
@needs_py310
def test_post_with_only_name(client: TestClient):
response = client.post("/items/", json={"name": "Foo"})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["body", "price"],
"msg": "Field required",
"input": {"name": "Foo"},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "price"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
@needs_py310
def test_post_with_only_name_price(client: TestClient):
response = client.post("/items/", json={"name": "Foo", "price": "twenty"})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "float_parsing",
"loc": ["body", "price"],
"msg": "Input should be a valid number, unable to parse string as a number",
"input": "twenty",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "price"],
"msg": "value is not a valid float",
"type": "type_error.float",
}
]
}
)
@needs_py310
def test_post_with_no_data(client: TestClient):
response = client.post("/items/", json={})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["body", "name"],
"msg": "Field required",
"input": {},
},
{
"type": "missing",
"loc": ["body", "price"],
"msg": "Field required",
"input": {},
},
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", "name"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "price"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
@needs_py310
def test_post_with_none(client: TestClient):
response = client.post("/items/", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "missing",
"loc": ["body"],
"msg": "Field required",
"input": None,
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
@needs_py310
def test_post_broken_body(client: TestClient):
response = client.post(
"/items/",
headers={"content-type": "application/json"},
content="{some broken json}",
)
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "json_invalid",
"loc": ["body", 1],
"msg": "JSON decode error",
"input": {},
"ctx": {
"error": "Expecting property name enclosed in double quotes"
},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body", 1],
"msg": "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
"type": "value_error.jsondecode",
"ctx": {
"msg": "Expecting property name enclosed in double quotes",
"doc": "{some broken json}",
"pos": 1,
"lineno": 1,
"colno": 2,
},
}
]
}
)
@needs_py310
def test_post_form_for_json(client: TestClient):
response = client.post("/items/", data={"name": "Foo", "price": 50.5})
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "model_attributes_type",
"loc": ["body"],
"msg": "Input should be a valid dictionary or object to extract fields from",
"input": "name=Foo&price=50.5",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body"],
"msg": "value is not a valid dict",
"type": "type_error.dict",
}
]
}
)
@needs_py310
def test_explicit_content_type(client: TestClient):
response = client.post(
"/items/",
content='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/json"},
)
assert response.status_code == 200, response.text
@needs_py310
def test_geo_json(client: TestClient):
response = client.post(
"/items/",
content='{"name": "Foo", "price": 50.5}',
headers={"Content-Type": "application/geo+json"},
)
assert response.status_code == 200, response.text
@needs_py310
def test_no_content_type_is_json(client: TestClient):
response = client.post(
"/items/",
content='{"name": "Foo", "price": 50.5}',
)
assert response.status_code == 200, response.text
assert response.json() == {
"name": "Foo",
"description": None,
"price": 50.5,
"tax": None,
}
@needs_py310
def test_wrong_headers(client: TestClient):
data = '{"name": "Foo", "price": 50.5}'
response = client.post(
"/items/", content=data, headers={"Content-Type": "text/plain"}
)
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "model_attributes_type",
"loc": ["body"],
"msg": "Input should be a valid dictionary or object to extract fields from",
"input": '{"name": "Foo", "price": 50.5}',
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body"],
"msg": "value is not a valid dict",
"type": "type_error.dict",
}
]
}
)
response = client.post(
"/items/", content=data, headers={"Content-Type": "application/geo+json-seq"}
)
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "model_attributes_type",
"loc": ["body"],
"msg": "Input should be a valid dictionary or object to extract fields from",
"input": '{"name": "Foo", "price": 50.5}',
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body"],
"msg": "value is not a valid dict",
"type": "type_error.dict",
}
]
}
)
response = client.post(
"/items/", content=data, headers={"Content-Type": "application/not-really-json"}
)
assert response.status_code == 422, response.text
assert response.json() == IsDict(
{
"detail": [
{
"type": "model_attributes_type",
"loc": ["body"],
"msg": "Input should be a valid dictionary or object to extract fields from",
"input": '{"name": "Foo", "price": 50.5}',
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["body"],
"msg": "value is not a valid dict",
"type": "type_error.dict",
}
]
}
)
@needs_py310
def test_other_exceptions(client: TestClient):
with patch("json.loads", side_effect=Exception):
response = client.post("/items/", json={"test": "test2"})
assert response.status_code == 400, response.text
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"post": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Create Item",
"operationId": "create_item_items__post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/Item"}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"price": {"title": "Price", "type": "number"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,13 +1,26 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39, needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.body_fields.tutorial001 import app
client = TestClient(app)
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py39", marks=needs_py39),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_fields.{request.param}")
client = TestClient(mod.app)
return client

View File

@@ -1,203 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
@pytest.fixture(name="client")
def get_client():
from docs_src.body_fields.tutorial001_an import app
client = TestClient(app)
return client
def test_items_5(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {"name": "Foo", "price": 3.0, "description": None, "tax": None},
}
def test_items_6(client: TestClient):
response = client.put(
"/items/6",
json={
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": "5.4",
}
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 6,
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": 5.4,
},
}
def test_invalid_price(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "greater_than",
"loc": ["body", "item", "price"],
"msg": "Input should be greater than 0",
"input": -3.0,
"ctx": {"gt": 0.0},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"ctx": {"limit_value": 0},
"loc": ["body", "item", "price"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt",
}
]
}
)
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put"
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "The description of the item",
"anyOf": [
{"maxLength": 300, "type": "string"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "The description of the item",
"maxLength": 300,
"type": "string",
}
),
"price": {
"title": "Price",
"exclusiveMinimum": 0.0,
"type": "number",
"description": "The price must be greater than zero",
},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"Body_update_item_items__item_id__put": {
"title": "Body_update_item_items__item_id__put",
"required": ["item"],
"type": "object",
"properties": {"item": {"$ref": "#/components/schemas/Item"}},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,209 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.body_fields.tutorial001_an_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_items_5(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {"name": "Foo", "price": 3.0, "description": None, "tax": None},
}
@needs_py310
def test_items_6(client: TestClient):
response = client.put(
"/items/6",
json={
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": "5.4",
}
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 6,
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": 5.4,
},
}
@needs_py310
def test_invalid_price(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "greater_than",
"loc": ["body", "item", "price"],
"msg": "Input should be greater than 0",
"input": -3.0,
"ctx": {"gt": 0.0},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"ctx": {"limit_value": 0},
"loc": ["body", "item", "price"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt",
}
]
}
)
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put"
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "The description of the item",
"anyOf": [
{"maxLength": 300, "type": "string"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "The description of the item",
"maxLength": 300,
"type": "string",
}
),
"price": {
"title": "Price",
"exclusiveMinimum": 0.0,
"type": "number",
"description": "The price must be greater than zero",
},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"Body_update_item_items__item_id__put": {
"title": "Body_update_item_items__item_id__put",
"required": ["item"],
"type": "object",
"properties": {"item": {"$ref": "#/components/schemas/Item"}},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,209 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.body_fields.tutorial001_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_items_5(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {"name": "Foo", "price": 3.0, "description": None, "tax": None},
}
@needs_py39
def test_items_6(client: TestClient):
response = client.put(
"/items/6",
json={
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": "5.4",
}
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 6,
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": 5.4,
},
}
@needs_py39
def test_invalid_price(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "greater_than",
"loc": ["body", "item", "price"],
"msg": "Input should be greater than 0",
"input": -3.0,
"ctx": {"gt": 0.0},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"ctx": {"limit_value": 0},
"loc": ["body", "item", "price"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt",
}
]
}
)
@needs_py39
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put"
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "The description of the item",
"anyOf": [
{"maxLength": 300, "type": "string"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "The description of the item",
"maxLength": 300,
"type": "string",
}
),
"price": {
"title": "Price",
"exclusiveMinimum": 0.0,
"type": "number",
"description": "The price must be greater than zero",
},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"Body_update_item_items__item_id__put": {
"title": "Body_update_item_items__item_id__put",
"required": ["item"],
"type": "object",
"properties": {"item": {"$ref": "#/components/schemas/Item"}},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,209 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.body_fields.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_items_5(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": 3.0}})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {"name": "Foo", "price": 3.0, "description": None, "tax": None},
}
@needs_py310
def test_items_6(client: TestClient):
response = client.put(
"/items/6",
json={
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": "5.4",
}
},
)
assert response.status_code == 200
assert response.json() == {
"item_id": 6,
"item": {
"name": "Bar",
"price": 0.2,
"description": "Some bar",
"tax": 5.4,
},
}
@needs_py310
def test_invalid_price(client: TestClient):
response = client.put("/items/5", json={"item": {"name": "Foo", "price": -3.0}})
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "greater_than",
"loc": ["body", "item", "price"],
"msg": "Input should be greater than 0",
"input": -3.0,
"ctx": {"gt": 0.0},
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"ctx": {"limit_value": 0},
"loc": ["body", "item", "price"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt",
}
]
}
)
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"title": "Item Id", "type": "integer"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_update_item_items__item_id__put"
}
}
},
"required": True,
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "The description of the item",
"anyOf": [
{"maxLength": 300, "type": "string"},
{"type": "null"},
],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{
"title": "The description of the item",
"maxLength": 300,
"type": "string",
}
),
"price": {
"title": "Price",
"exclusiveMinimum": 0.0,
"type": "number",
"description": "The price must be greater than zero",
},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"Body_update_item_items__item_id__put": {
"title": "Body_update_item_items__item_id__put",
"required": ["item"],
"type": "object",
"properties": {"item": {"$ref": "#/components/schemas/Item"}},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,13 +1,26 @@
import importlib
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39, needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.body_multiple_params.tutorial001 import app
client = TestClient(app)
@pytest.fixture(
name="client",
params=[
"tutorial001",
pytest.param("tutorial001_py310", marks=needs_py310),
"tutorial001_an",
pytest.param("tutorial001_an_py39", marks=needs_py39),
pytest.param("tutorial001_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
mod = importlib.import_module(f"docs_src.body_multiple_params.{request.param}")
client = TestClient(mod.app)
return client

View File

@@ -1,206 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
@pytest.fixture(name="client")
def get_client():
from docs_src.body_multiple_params.tutorial001_an import app
client = TestClient(app)
return client
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,213 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.body_multiple_params.tutorial001_an_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
@needs_py310
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
@needs_py310
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
@needs_py310
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,213 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py39
@pytest.fixture(name="client")
def get_client():
from docs_src.body_multiple_params.tutorial001_an_py39 import app
client = TestClient(app)
return client
@needs_py39
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
@needs_py39
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
@needs_py39
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
@needs_py39
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
@needs_py39
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

View File

@@ -1,213 +0,0 @@
import pytest
from dirty_equals import IsDict
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(name="client")
def get_client():
from docs_src.body_multiple_params.tutorial001_py310 import app
client = TestClient(app)
return client
@needs_py310
def test_post_body_q_bar_content(client: TestClient):
response = client.put("/items/5?q=bar", json={"name": "Foo", "price": 50.5})
assert response.status_code == 200
assert response.json() == {
"item_id": 5,
"item": {
"name": "Foo",
"price": 50.5,
"description": None,
"tax": None,
},
"q": "bar",
}
@needs_py310
def test_post_no_body_q_bar(client: TestClient):
response = client.put("/items/5?q=bar", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5, "q": "bar"}
@needs_py310
def test_post_no_body(client: TestClient):
response = client.put("/items/5", json=None)
assert response.status_code == 200
assert response.json() == {"item_id": 5}
@needs_py310
def test_post_id_foo(client: TestClient):
response = client.put("/items/foo", json=None)
assert response.status_code == 422
assert response.json() == IsDict(
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "foo",
}
]
}
) | IsDict(
# TODO: remove when deprecating Pydantic v1
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
@needs_py310
def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
assert response.json() == {
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/{item_id}": {
"put": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {
"title": "The ID of the item to get",
"maximum": 1000.0,
"minimum": 0.0,
"type": "integer",
},
"name": "item_id",
"in": "path",
},
{
"required": False,
"schema": IsDict(
{
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Q",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Q", "type": "string"}
),
"name": "q",
"in": "query",
},
],
"requestBody": {
"content": {
"application/json": {
"schema": IsDict(
{
"anyOf": [
{"$ref": "#/components/schemas/Item"},
{"type": "null"},
],
"title": "Item",
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"$ref": "#/components/schemas/Item"}
)
}
}
},
}
}
},
"components": {
"schemas": {
"Item": {
"title": "Item",
"required": ["name", "price"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": IsDict(
{
"title": "Description",
"anyOf": [{"type": "string"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Description", "type": "string"}
),
"price": {"title": "Price", "type": "number"},
"tax": IsDict(
{
"title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}],
}
)
| IsDict(
# TODO: remove when deprecating Pydantic v1
{"title": "Tax", "type": "number"}
),
},
},
"ValidationError": {
"title": "ValidationError",
"required": ["loc", "msg", "type"],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"anyOf": [{"type": "string"}, {"type": "integer"}]
},
},
"msg": {"title": "Message", "type": "string"},
"type": {"title": "Error Type", "type": "string"},
},
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {"$ref": "#/components/schemas/ValidationError"},
}
},
},
}
},
}

Some files were not shown because too many files have changed in this diff Show More