From d6db5d4f5800efd92122f7a8f0307ff3e6257423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 1 Jul 2023 20:08:39 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20OpenAPI=203.1.0=20with=20Pydantic?= =?UTF-8?q?=20v2,=20merge=20`master`=20(#9773)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/watch-previews/Dockerfile | 7 - .github/actions/watch-previews/action.yml | 10 - .github/actions/watch-previews/app/main.py | 101 --- .github/workflows/build-docs.yml | 97 ++- .../{preview-docs.yml => deploy-docs.yml} | 21 +- .github/workflows/issue-manager.yml | 3 +- .github/workflows/label-approved.yml | 1 + .github/workflows/people.yml | 1 + .github/workflows/publish.yml | 2 +- .gitignore | 2 + .pre-commit-config.yaml | 4 +- README.md | 1 - docs/az/docs/index.md | 466 ------------- docs/az/mkdocs.yml | 160 ----- docs/cs/docs/index.md | 473 -------------- docs/cs/mkdocs.yml | 154 ----- docs/de/docs/index.md | 464 ------------- docs/de/mkdocs.yml | 162 +---- docs/de/overrides/.gitignore | 0 docs/em/docs/advanced/index.md | 2 +- docs/em/docs/advanced/security/index.md | 2 +- docs/em/docs/deployment/index.md | 2 +- docs/em/docs/tutorial/dependencies/index.md | 2 +- docs/em/docs/tutorial/index.md | 2 +- docs/em/docs/tutorial/security/index.md | 2 +- docs/em/mkdocs.yml | 265 +------- docs/em/overrides/.gitignore | 0 docs/en/data/external_links.yml | 4 + docs/en/data/sponsors.yml | 3 + docs/en/data/sponsors_badge.yml | 1 + docs/en/docs/advanced/additional-responses.md | 4 +- docs/en/docs/advanced/behind-a-proxy.md | 4 +- docs/en/docs/advanced/extending-openapi.md | 16 +- docs/en/docs/advanced/index.md | 2 +- docs/en/docs/advanced/openapi-callbacks.md | 4 +- docs/en/docs/advanced/openapi-webhooks.md | 51 ++ .../path-operation-advanced-configuration.md | 2 +- docs/en/docs/advanced/security/index.md | 2 +- docs/en/docs/advanced/testing-database.md | 2 +- docs/en/docs/contributing.md | 128 ++-- docs/en/docs/deployment/index.md | 2 +- docs/en/docs/img/sponsors/flint.png | Bin 0 -> 10409 bytes .../en/docs/img/tutorial/metadata/image01.png | Bin 90062 -> 86437 bytes .../img/tutorial/openapi-webhooks/image01.png | Bin 0 -> 86925 bytes docs/en/docs/index.md | 1 - docs/en/docs/newsletter.md | 4 +- docs/en/docs/release-notes.md | 82 +++ docs/en/docs/tutorial/debugging.md | 2 +- docs/en/docs/tutorial/dependencies/index.md | 2 +- docs/en/docs/tutorial/first-steps.md | 2 +- docs/en/docs/tutorial/index.md | 2 +- docs/en/docs/tutorial/metadata.md | 15 +- docs/en/docs/tutorial/path-params.md | 2 +- .../tutorial/query-params-str-validations.md | 2 +- docs/en/docs/tutorial/schema-extra-example.md | 134 ++-- docs/en/docs/tutorial/security/index.md | 4 +- docs/en/layouts/custom.yml | 228 +++++++ docs/en/mkdocs.insiders.yml | 7 + docs/en/mkdocs.maybe-insiders.yml | 6 + .../.gitignore => en/mkdocs.no-insiders.yml} | 0 docs/en/mkdocs.yml | 73 +-- docs/es/docs/advanced/index.md | 2 +- docs/es/docs/index.md | 1 - docs/es/docs/tutorial/first-steps.md | 2 +- docs/es/docs/tutorial/index.md | 2 +- docs/es/mkdocs.yml | 171 +---- docs/es/overrides/.gitignore | 0 docs/fa/docs/advanced/sub-applications.md | 72 +++ docs/fa/docs/index.md | 1 - docs/fa/mkdocs.yml | 161 +---- docs/fa/overrides/.gitignore | 0 docs/fr/docs/advanced/index.md | 2 +- docs/fr/docs/deployment/index.md | 2 +- docs/fr/docs/index.md | 1 - docs/fr/mkdocs.yml | 190 +----- docs/fr/overrides/.gitignore | 0 docs/he/docs/index.md | 1 - docs/he/mkdocs.yml | 161 +---- docs/he/overrides/.gitignore | 0 docs/hy/docs/index.md | 467 ------------- docs/hy/mkdocs.yml | 160 ----- docs/hy/overrides/.gitignore | 0 docs/id/docs/index.md | 466 ------------- docs/id/mkdocs.yml | 161 +---- docs/id/overrides/.gitignore | 0 docs/it/docs/index.md | 463 ------------- docs/it/mkdocs.yml | 160 ----- docs/it/overrides/.gitignore | 0 docs/ja/docs/advanced/index.md | 2 +- docs/ja/docs/deployment/index.md | 2 +- docs/ja/docs/index.md | 1 - docs/ja/docs/tutorial/index.md | 2 +- docs/ja/mkdocs.yml | 205 +----- docs/ja/overrides/.gitignore | 0 docs/ko/docs/index.md | 1 - docs/ko/docs/tutorial/index.md | 2 +- docs/ko/mkdocs.yml | 175 +---- docs/ko/overrides/.gitignore | 0 docs/lo/docs/index.md | 469 -------------- docs/lo/mkdocs.yml | 157 ----- docs/lo/overrides/.gitignore | 0 docs/nl/docs/index.md | 468 -------------- docs/nl/mkdocs.yml | 160 ----- docs/nl/overrides/.gitignore | 0 docs/pl/docs/features.md | 200 ++++++ docs/pl/docs/index.md | 1 - docs/pl/docs/tutorial/index.md | 2 +- docs/pl/mkdocs.yml | 164 +---- docs/pl/overrides/.gitignore | 0 docs/pt/docs/advanced/index.md | 2 +- docs/pt/docs/deployment/index.md | 2 +- docs/pt/docs/index.md | 1 - docs/pt/docs/tutorial/index.md | 2 +- docs/pt/docs/tutorial/security/index.md | 2 +- docs/pt/mkdocs.yml | 202 +----- docs/pt/overrides/.gitignore | 0 docs/ru/docs/deployment/index.md | 2 +- docs/ru/docs/index.md | 1 - docs/ru/docs/tutorial/body-nested-models.md | 382 +++++++++++ docs/ru/docs/tutorial/cors.md | 84 +++ docs/ru/docs/tutorial/extra-models.md | 252 ++++++++ docs/ru/docs/tutorial/index.md | 2 +- docs/ru/docs/tutorial/metadata.md | 111 ++++ .../tutorial/path-operation-configuration.md | 179 +++++ docs/ru/docs/tutorial/response-model.md | 480 ++++++++++++++ docs/ru/mkdocs.yml | 195 +----- docs/ru/overrides/.gitignore | 0 docs/sq/docs/index.md | 466 ------------- docs/sq/mkdocs.yml | 160 ----- docs/sq/overrides/.gitignore | 0 docs/sv/docs/index.md | 468 -------------- docs/sv/mkdocs.yml | 160 ----- docs/sv/overrides/.gitignore | 0 docs/ta/mkdocs.yml | 160 ----- docs/ta/overrides/.gitignore | 0 docs/tr/docs/index.md | 5 - docs/tr/mkdocs.yml | 166 +---- docs/tr/overrides/.gitignore | 0 docs/uk/docs/index.md | 466 ------------- docs/uk/mkdocs.yml | 160 ----- docs/uk/overrides/.gitignore | 0 docs/zh/docs/advanced/index.md | 2 +- docs/zh/docs/advanced/security/index.md | 16 + docs/zh/docs/advanced/settings.md | 433 +++++++++++++ docs/zh/docs/advanced/websockets.md | 214 ++++++ docs/zh/docs/index.md | 1 - docs/zh/docs/tutorial/dependencies/index.md | 2 +- docs/zh/docs/tutorial/index.md | 2 +- docs/zh/docs/tutorial/security/index.md | 2 +- docs/zh/docs/tutorial/testing.md | 212 ++++++ docs/zh/mkdocs.yml | 221 +------ docs/zh/overrides/.gitignore | 0 docs_src/extending_openapi/tutorial001.py | 3 +- docs_src/metadata/tutorial001.py | 1 + docs_src/metadata/tutorial001_1.py | 38 ++ docs_src/openapi_webhooks/tutorial001.py | 25 + docs_src/schema_extra_example/tutorial001.py | 14 +- .../schema_extra_example/tutorial001_py310.py | 14 +- docs_src/schema_extra_example/tutorial002.py | 8 +- .../schema_extra_example/tutorial002_py310.py | 8 +- docs_src/schema_extra_example/tutorial003.py | 14 +- .../schema_extra_example/tutorial003_an.py | 14 +- .../tutorial003_an_py310.py | 14 +- .../tutorial003_an_py39.py | 14 +- .../schema_extra_example/tutorial003_py310.py | 14 +- docs_src/schema_extra_example/tutorial004.py | 37 +- .../schema_extra_example/tutorial004_an.py | 37 +- .../tutorial004_an_py310.py | 37 +- .../tutorial004_an_py39.py | 37 +- .../schema_extra_example/tutorial004_py310.py | 37 +- .../sql_app/tests/test_sql_app.py | 7 +- fastapi/_compat.py | 12 +- fastapi/applications.py | 10 +- fastapi/encoders.py | 2 +- fastapi/openapi/docs.py | 4 +- fastapi/openapi/models.py | 93 ++- fastapi/openapi/utils.py | 39 +- fastapi/param_functions.py | 73 ++- fastapi/params.py | 106 ++- pyproject.toml | 4 +- requirements-docs.txt | 10 +- requirements-tests.txt | 10 +- requirements.txt | 4 +- scripts/docs.py | 281 ++------ scripts/mkdocs_hooks.py | 132 ++++ scripts/zip-docs.sh | 11 - tests/test_additional_properties.py | 2 +- tests/test_additional_response_extra.py | 2 +- tests/test_additional_responses_bad.py | 2 +- ...onal_responses_custom_model_in_callback.py | 2 +- ...tional_responses_custom_validationerror.py | 2 +- ...ional_responses_default_validationerror.py | 2 +- ...est_additional_responses_response_class.py | 2 +- tests/test_additional_responses_router.py | 2 +- tests/test_annotated.py | 2 +- tests/test_application.py | 2 +- tests/test_custom_route_class.py | 2 +- tests/test_dependency_duplicates.py | 2 +- tests/test_deprecated_openapi_prefix.py | 2 +- tests/test_duplicate_models_openapi.py | 2 +- tests/test_enforce_once_required_parameter.py | 2 +- tests/test_extra_routes.py | 2 +- .../test_filter_pydantic_sub_model_pv1.py | 2 +- tests/test_filter_pydantic_sub_model_pv2.py | 2 +- tests/test_generate_unique_id_function.py | 14 +- tests/test_get_request_body.py | 2 +- .../test_include_router_defaults_overrides.py | 2 +- tests/test_infer_param_optionality.py | 2 +- tests/test_jsonable_encoder.py | 10 + .../test_modules_same_name_body/test_main.py | 2 +- tests/test_multi_body_errors.py | 2 +- tests/test_multi_query_errors.py | 2 +- .../test_openapi_query_parameter_extension.py | 2 +- tests/test_openapi_route_extensions.py | 2 +- tests/test_openapi_servers.py | 2 +- tests/test_param_in_path_and_dependency.py | 2 +- tests/test_param_include_in_schema.py | 6 +- tests/test_put_no_body.py | 2 +- tests/test_repeated_dependency_schema.py | 2 +- tests/test_repeated_parameter_alias.py | 2 +- tests/test_reponse_set_reponse_code_empty.py | 2 +- ...test_request_body_parameters_media_type.py | 2 +- tests/test_response_by_alias.py | 2 +- tests/test_response_class_no_mediatype.py | 2 +- tests/test_response_code_no_body.py | 2 +- ...est_response_model_as_return_annotation.py | 2 +- tests/test_response_model_sub_types.py | 2 +- tests/test_router_redirect_slashes.py | 40 ++ tests/test_schema_extra_examples.py | 612 +++++++++--------- tests/test_security_api_key_cookie.py | 2 +- ...est_security_api_key_cookie_description.py | 2 +- .../test_security_api_key_cookie_optional.py | 2 +- tests/test_security_api_key_header.py | 2 +- ...est_security_api_key_header_description.py | 2 +- .../test_security_api_key_header_optional.py | 2 +- tests/test_security_api_key_query.py | 2 +- ...test_security_api_key_query_description.py | 2 +- tests/test_security_api_key_query_optional.py | 2 +- tests/test_security_http_base.py | 2 +- tests/test_security_http_base_description.py | 2 +- tests/test_security_http_base_optional.py | 2 +- tests/test_security_http_basic_optional.py | 2 +- tests/test_security_http_basic_realm.py | 2 +- ...t_security_http_basic_realm_description.py | 2 +- tests/test_security_http_bearer.py | 2 +- .../test_security_http_bearer_description.py | 2 +- tests/test_security_http_bearer_optional.py | 2 +- tests/test_security_http_digest.py | 2 +- .../test_security_http_digest_description.py | 2 +- tests/test_security_http_digest_optional.py | 2 +- tests/test_security_oauth2.py | 2 +- ...curity_oauth2_authorization_code_bearer.py | 2 +- ...2_authorization_code_bearer_description.py | 2 +- tests/test_security_oauth2_optional.py | 2 +- ...st_security_oauth2_optional_description.py | 2 +- ...ecurity_oauth2_password_bearer_optional.py | 2 +- ...h2_password_bearer_optional_description.py | 2 +- tests/test_security_openid_connect.py | 2 +- ...est_security_openid_connect_description.py | 2 +- .../test_security_openid_connect_optional.py | 2 +- tests/test_starlette_exception.py | 2 +- tests/test_sub_callbacks.py | 2 +- tests/test_tuples.py | 2 +- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial004.py | 2 +- .../test_tutorial001.py | 2 +- .../test_behind_a_proxy/test_tutorial001.py | 2 +- .../test_behind_a_proxy/test_tutorial002.py | 2 +- .../test_behind_a_proxy/test_tutorial003.py | 2 +- .../test_behind_a_proxy/test_tutorial004.py | 2 +- .../test_bigger_applications/test_main.py | 2 +- .../test_bigger_applications/test_main_an.py | 2 +- .../test_main_an_py39.py | 2 +- .../test_body/test_tutorial001.py | 2 +- .../test_body/test_tutorial001_py310.py | 2 +- .../test_body_fields/test_tutorial001.py | 2 +- .../test_body_fields/test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py310.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_tutorial001_py310.py | 2 +- .../test_tutorial001.py | 2 +- .../test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py310.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_tutorial001_py310.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial003_an.py | 2 +- .../test_tutorial003_an_py310.py | 2 +- .../test_tutorial003_an_py39.py | 2 +- .../test_tutorial003_py310.py | 2 +- .../test_tutorial009.py | 2 +- .../test_tutorial009_py39.py | 2 +- .../test_body_updates/test_tutorial001.py | 2 +- .../test_tutorial001_py310.py | 2 +- .../test_tutorial001_py39.py | 2 +- .../test_tutorial001.py | 2 +- .../test_cookie_params/test_tutorial001.py | 2 +- .../test_cookie_params/test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py310.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_tutorial001_py310.py | 2 +- .../test_custom_response/test_tutorial001.py | 2 +- .../test_custom_response/test_tutorial001b.py | 2 +- .../test_custom_response/test_tutorial004.py | 2 +- .../test_custom_response/test_tutorial005.py | 2 +- .../test_custom_response/test_tutorial006.py | 2 +- .../test_custom_response/test_tutorial006b.py | 2 +- .../test_custom_response/test_tutorial006c.py | 2 +- .../test_dataclasses/test_tutorial001.py | 2 +- .../test_dataclasses/test_tutorial002.py | 5 +- .../test_dataclasses/test_tutorial003.py | 2 +- .../test_dependencies/test_tutorial001.py | 2 +- .../test_dependencies/test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py310.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_tutorial001_py310.py | 2 +- .../test_dependencies/test_tutorial004.py | 2 +- .../test_dependencies/test_tutorial004_an.py | 2 +- .../test_tutorial004_an_py310.py | 2 +- .../test_tutorial004_an_py39.py | 2 +- .../test_tutorial004_py310.py | 2 +- .../test_dependencies/test_tutorial006.py | 2 +- .../test_dependencies/test_tutorial006_an.py | 2 +- .../test_tutorial006_an_py39.py | 2 +- .../test_dependencies/test_tutorial012.py | 2 +- .../test_dependencies/test_tutorial012_an.py | 2 +- .../test_tutorial012_an_py39.py | 2 +- .../test_events/test_tutorial001.py | 2 +- .../test_events/test_tutorial002.py | 2 +- .../test_events/test_tutorial003.py | 2 +- .../test_tutorial001.py | 5 +- .../test_extra_data_types/test_tutorial001.py | 2 +- .../test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py310.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_tutorial001_py310.py | 2 +- .../test_extra_models/test_tutorial003.py | 2 +- .../test_tutorial003_py310.py | 2 +- .../test_extra_models/test_tutorial004.py | 2 +- .../test_tutorial004_py39.py | 2 +- .../test_extra_models/test_tutorial005.py | 2 +- .../test_tutorial005_py39.py | 2 +- .../test_first_steps/test_tutorial001.py | 2 +- .../test_generate_clients/test_tutorial003.py | 2 +- .../test_handling_errors/test_tutorial001.py | 2 +- .../test_handling_errors/test_tutorial002.py | 2 +- .../test_handling_errors/test_tutorial003.py | 2 +- .../test_handling_errors/test_tutorial004.py | 2 +- .../test_handling_errors/test_tutorial005.py | 2 +- .../test_handling_errors/test_tutorial006.py | 2 +- .../test_header_params/test_tutorial001.py | 2 +- .../test_header_params/test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py310.py | 2 +- .../test_tutorial001_py310.py | 2 +- .../test_header_params/test_tutorial002.py | 2 +- .../test_header_params/test_tutorial002_an.py | 2 +- .../test_tutorial002_an_py310.py | 2 +- .../test_tutorial002_an_py39.py | 2 +- .../test_tutorial002_py310.py | 2 +- .../test_header_params/test_tutorial003.py | 2 +- .../test_header_params/test_tutorial003_an.py | 2 +- .../test_tutorial003_an_py310.py | 2 +- .../test_tutorial003_an_py39.py | 2 +- .../test_tutorial003_py310.py | 2 +- .../test_metadata/test_tutorial001.py | 3 +- .../test_metadata/test_tutorial001_1.py | 49 ++ .../test_metadata/test_tutorial004.py | 2 +- .../test_tutorial001.py | 2 +- .../test_openapi_webhooks/__init__.py | 0 .../test_openapi_webhooks/test_tutorial001.py | 117 ++++ .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial004.py | 2 +- .../test_tutorial005.py | 2 +- .../test_tutorial006.py | 2 +- .../test_tutorial007.py | 2 +- .../test_tutorial002b.py | 2 +- .../test_tutorial005.py | 2 +- .../test_tutorial005_py310.py | 2 +- .../test_tutorial005_py39.py | 2 +- .../test_tutorial006.py | 2 +- .../test_path_params/test_tutorial004.py | 2 +- .../test_path_params/test_tutorial005.py | 2 +- .../test_query_params/test_tutorial005.py | 2 +- .../test_query_params/test_tutorial006.py | 2 +- .../test_tutorial006_py310.py | 2 +- .../test_tutorial010.py | 3 +- .../test_tutorial010_an.py | 3 +- .../test_tutorial010_an_py310.py | 3 +- .../test_tutorial010_an_py39.py | 3 +- .../test_tutorial010_py310.py | 3 +- .../test_tutorial011.py | 2 +- .../test_tutorial011_an.py | 2 +- .../test_tutorial011_an_py310.py | 2 +- .../test_tutorial011_an_py39.py | 2 +- .../test_tutorial011_py310.py | 2 +- .../test_tutorial011_py39.py | 2 +- .../test_tutorial012.py | 2 +- .../test_tutorial012_an.py | 2 +- .../test_tutorial012_an_py39.py | 2 +- .../test_tutorial012_py39.py | 2 +- .../test_tutorial013.py | 2 +- .../test_tutorial013_an.py | 2 +- .../test_tutorial013_an_py39.py | 2 +- .../test_tutorial014.py | 2 +- .../test_tutorial014_an.py | 2 +- .../test_tutorial014_an_py310.py | 2 +- .../test_tutorial014_an_py39.py | 2 +- .../test_tutorial014_py310.py | 2 +- .../test_request_files/test_tutorial001.py | 2 +- .../test_request_files/test_tutorial001_02.py | 2 +- .../test_tutorial001_02_an.py | 2 +- .../test_tutorial001_02_an_py310.py | 2 +- .../test_tutorial001_02_an_py39.py | 2 +- .../test_tutorial001_02_py310.py | 2 +- .../test_request_files/test_tutorial001_03.py | 2 +- .../test_tutorial001_03_an.py | 2 +- .../test_tutorial001_03_an_py39.py | 2 +- .../test_request_files/test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_request_files/test_tutorial002.py | 2 +- .../test_request_files/test_tutorial002_an.py | 2 +- .../test_tutorial002_an_py39.py | 2 +- .../test_tutorial002_py39.py | 2 +- .../test_request_files/test_tutorial003.py | 2 +- .../test_request_files/test_tutorial003_an.py | 2 +- .../test_tutorial003_an_py39.py | 2 +- .../test_tutorial003_py39.py | 2 +- .../test_request_forms/test_tutorial001.py | 2 +- .../test_request_forms/test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_tutorial001.py | 2 +- .../test_tutorial001_an.py | 2 +- .../test_tutorial001_an_py39.py | 2 +- .../test_response_model/test_tutorial003.py | 2 +- .../test_tutorial003_01.py | 2 +- .../test_tutorial003_01_py310.py | 2 +- .../test_tutorial003_02.py | 2 +- .../test_tutorial003_03.py | 2 +- .../test_tutorial003_05.py | 2 +- .../test_tutorial003_05_py310.py | 2 +- .../test_tutorial003_py310.py | 2 +- .../test_response_model/test_tutorial004.py | 2 +- .../test_tutorial004_py310.py | 2 +- .../test_tutorial004_py39.py | 2 +- .../test_response_model/test_tutorial005.py | 2 +- .../test_tutorial005_py310.py | 2 +- .../test_response_model/test_tutorial006.py | 2 +- .../test_tutorial006_py310.py | 2 +- .../test_tutorial004.py | 67 +- .../test_tutorial004_an.py | 67 +- .../test_tutorial004_an_py310.py | 67 +- .../test_tutorial004_an_py39.py | 67 +- .../test_tutorial004_py310.py | 67 +- .../test_security/test_tutorial001.py | 2 +- .../test_security/test_tutorial001_an.py | 2 +- .../test_security/test_tutorial001_an_py39.py | 2 +- .../test_security/test_tutorial003.py | 2 +- .../test_security/test_tutorial003_an.py | 2 +- .../test_tutorial003_an_py310.py | 2 +- .../test_security/test_tutorial003_an_py39.py | 2 +- .../test_security/test_tutorial003_py310.py | 2 +- .../test_security/test_tutorial005.py | 2 +- .../test_security/test_tutorial005_an.py | 2 +- .../test_tutorial005_an_py310.py | 2 +- .../test_security/test_tutorial005_an_py39.py | 2 +- .../test_security/test_tutorial005_py310.py | 2 +- .../test_security/test_tutorial005_py39.py | 2 +- .../test_security/test_tutorial006.py | 2 +- .../test_security/test_tutorial006_an.py | 2 +- .../test_security/test_tutorial006_an_py39.py | 2 +- .../test_sql_databases/test_sql_databases.py | 2 +- .../test_sql_databases_middleware.py | 2 +- .../test_sql_databases_middleware_py310.py | 2 +- .../test_sql_databases_middleware_py39.py | 2 +- .../test_sql_databases_py310.py | 2 +- .../test_sql_databases_py39.py | 2 +- .../test_sql_databases_peewee.py | 2 +- .../test_sub_applications/test_tutorial001.py | 4 +- tests/test_tutorial/test_testing/test_main.py | 2 +- .../test_testing/test_tutorial001.py | 2 +- tests/test_union_body.py | 2 +- tests/test_union_inherited_body.py | 2 +- tests/test_webhooks_security.py | 126 ++++ 487 files changed, 5205 insertions(+), 10925 deletions(-) delete mode 100644 .github/actions/watch-previews/Dockerfile delete mode 100644 .github/actions/watch-previews/action.yml delete mode 100644 .github/actions/watch-previews/app/main.py rename .github/workflows/{preview-docs.yml => deploy-docs.yml} (69%) delete mode 100644 docs/az/docs/index.md delete mode 100644 docs/az/mkdocs.yml delete mode 100644 docs/cs/docs/index.md delete mode 100644 docs/cs/mkdocs.yml delete mode 100644 docs/de/docs/index.md delete mode 100644 docs/de/overrides/.gitignore delete mode 100644 docs/em/overrides/.gitignore create mode 100644 docs/en/docs/advanced/openapi-webhooks.md create mode 100644 docs/en/docs/img/sponsors/flint.png create mode 100644 docs/en/docs/img/tutorial/openapi-webhooks/image01.png create mode 100644 docs/en/layouts/custom.yml create mode 100644 docs/en/mkdocs.insiders.yml create mode 100644 docs/en/mkdocs.maybe-insiders.yml rename docs/{az/overrides/.gitignore => en/mkdocs.no-insiders.yml} (100%) delete mode 100644 docs/es/overrides/.gitignore create mode 100644 docs/fa/docs/advanced/sub-applications.md delete mode 100644 docs/fa/overrides/.gitignore delete mode 100644 docs/fr/overrides/.gitignore delete mode 100644 docs/he/overrides/.gitignore delete mode 100644 docs/hy/docs/index.md delete mode 100644 docs/hy/mkdocs.yml delete mode 100644 docs/hy/overrides/.gitignore delete mode 100644 docs/id/docs/index.md delete mode 100644 docs/id/overrides/.gitignore delete mode 100644 docs/it/docs/index.md delete mode 100644 docs/it/mkdocs.yml delete mode 100644 docs/it/overrides/.gitignore delete mode 100644 docs/ja/overrides/.gitignore delete mode 100644 docs/ko/overrides/.gitignore delete mode 100644 docs/lo/docs/index.md delete mode 100644 docs/lo/mkdocs.yml delete mode 100644 docs/lo/overrides/.gitignore delete mode 100644 docs/nl/docs/index.md delete mode 100644 docs/nl/mkdocs.yml delete mode 100644 docs/nl/overrides/.gitignore create mode 100644 docs/pl/docs/features.md delete mode 100644 docs/pl/overrides/.gitignore delete mode 100644 docs/pt/overrides/.gitignore create mode 100644 docs/ru/docs/tutorial/body-nested-models.md create mode 100644 docs/ru/docs/tutorial/cors.md create mode 100644 docs/ru/docs/tutorial/extra-models.md create mode 100644 docs/ru/docs/tutorial/metadata.md create mode 100644 docs/ru/docs/tutorial/path-operation-configuration.md create mode 100644 docs/ru/docs/tutorial/response-model.md delete mode 100644 docs/ru/overrides/.gitignore delete mode 100644 docs/sq/docs/index.md delete mode 100644 docs/sq/mkdocs.yml delete mode 100644 docs/sq/overrides/.gitignore delete mode 100644 docs/sv/docs/index.md delete mode 100644 docs/sv/mkdocs.yml delete mode 100644 docs/sv/overrides/.gitignore delete mode 100644 docs/ta/mkdocs.yml delete mode 100644 docs/ta/overrides/.gitignore delete mode 100644 docs/tr/overrides/.gitignore delete mode 100644 docs/uk/docs/index.md delete mode 100644 docs/uk/mkdocs.yml delete mode 100644 docs/uk/overrides/.gitignore create mode 100644 docs/zh/docs/advanced/security/index.md create mode 100644 docs/zh/docs/advanced/settings.md create mode 100644 docs/zh/docs/advanced/websockets.md create mode 100644 docs/zh/docs/tutorial/testing.md delete mode 100644 docs/zh/overrides/.gitignore create mode 100644 docs_src/metadata/tutorial001_1.py create mode 100644 docs_src/openapi_webhooks/tutorial001.py create mode 100644 scripts/mkdocs_hooks.py delete mode 100644 scripts/zip-docs.sh create mode 100644 tests/test_router_redirect_slashes.py create mode 100644 tests/test_tutorial/test_metadata/test_tutorial001_1.py rename docs/cs/overrides/.gitignore => tests/test_tutorial/test_openapi_webhooks/__init__.py (100%) create mode 100644 tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py create mode 100644 tests/test_webhooks_security.py diff --git a/.github/actions/watch-previews/Dockerfile b/.github/actions/watch-previews/Dockerfile deleted file mode 100644 index b8cc64d94..000000000 --- a/.github/actions/watch-previews/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM python:3.7 - -RUN pip install httpx PyGithub "pydantic==1.5.1" - -COPY ./app /app - -CMD ["python", "/app/main.py"] diff --git a/.github/actions/watch-previews/action.yml b/.github/actions/watch-previews/action.yml deleted file mode 100644 index 5c09ad487..000000000 --- a/.github/actions/watch-previews/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Watch docs previews in PRs" -description: "Check PRs and trigger new docs deploys" -author: "Sebastián Ramírez " -inputs: - token: - description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}' - required: true -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/watch-previews/app/main.py b/.github/actions/watch-previews/app/main.py deleted file mode 100644 index 51285d02b..000000000 --- a/.github/actions/watch-previews/app/main.py +++ /dev/null @@ -1,101 +0,0 @@ -import logging -from datetime import datetime -from pathlib import Path -from typing import List, Union - -import httpx -from github import Github -from github.NamedUser import NamedUser -from pydantic import BaseModel, BaseSettings, SecretStr - -github_api = "https://api.github.com" -netlify_api = "https://api.netlify.com" - - -class Settings(BaseSettings): - input_token: SecretStr - github_repository: str - github_event_path: Path - github_event_name: Union[str, None] = None - - -class Artifact(BaseModel): - id: int - node_id: str - name: str - size_in_bytes: int - url: str - archive_download_url: str - expired: bool - created_at: datetime - updated_at: datetime - - -class ArtifactResponse(BaseModel): - total_count: int - artifacts: List[Artifact] - - -def get_message(commit: str) -> str: - return f"Docs preview for commit {commit} at" - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - settings = Settings() - logging.info(f"Using config: {settings.json()}") - g = Github(settings.input_token.get_secret_value()) - repo = g.get_repo(settings.github_repository) - owner: NamedUser = repo.owner - headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} - prs = list(repo.get_pulls(state="open")) - response = httpx.get( - f"{github_api}/repos/{settings.github_repository}/actions/artifacts", - headers=headers, - ) - data = response.json() - artifacts_response = ArtifactResponse.parse_obj(data) - for pr in prs: - logging.info("-----") - logging.info(f"Processing PR #{pr.number}: {pr.title}") - pr_comments = list(pr.get_issue_comments()) - pr_commits = list(pr.get_commits()) - last_commit = pr_commits[0] - for pr_commit in pr_commits: - if pr_commit.commit.author.date > last_commit.commit.author.date: - last_commit = pr_commit - commit = last_commit.commit.sha - logging.info(f"Last commit: {commit}") - message = get_message(commit) - notified = False - for pr_comment in pr_comments: - if message in pr_comment.body: - notified = True - logging.info(f"Docs preview was notified: {notified}") - if not notified: - artifact_name = f"docs-zip-{commit}" - use_artifact: Union[Artifact, None] = None - for artifact in artifacts_response.artifacts: - if artifact.name == artifact_name: - use_artifact = artifact - break - if not use_artifact: - logging.info("Artifact not available") - else: - logging.info(f"Existing artifact: {use_artifact.name}") - response = httpx.post( - "https://api.github.com/repos/tiangolo/fastapi/actions/workflows/preview-docs.yml/dispatches", - headers=headers, - json={ - "ref": "master", - "inputs": { - "pr": f"{pr.number}", - "name": artifact_name, - "commit": commit, - }, - }, - ) - logging.info( - f"Trigger sent, response status: {response.status_code} - content: {response.content}" - ) - logging.info("Finished") diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index a0e83e5c8..a155ecfec 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -4,10 +4,68 @@ on: branches: - master pull_request: - types: [opened, synchronize] + types: + - opened + - synchronize jobs: + changes: + runs-on: ubuntu-latest + # Required permissions + permissions: + pull-requests: read + # Set job outputs to values from filter step + outputs: + docs: ${{ steps.filter.outputs.docs }} + steps: + - uses: actions/checkout@v3 + # For pull requests it's not necessary to checkout the code but for master it is + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + docs: + - README.md + - docs/** + - docs_src/** + - requirements-docs.txt + langs: + needs: + - changes + runs-on: ubuntu-latest + outputs: + langs: ${{ steps.show-langs.outputs.langs }} + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - uses: actions/cache@v3 + id: cache + with: + path: ${{ env.pythonLocation }} + key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v05 + - name: Install docs extras + if: steps.cache.outputs.cache-hit != 'true' + run: pip install -r requirements-docs.txt + # Install MkDocs Material Insiders here just to put it in the cache for the rest of the steps + - name: Install Material for MkDocs Insiders + if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true' + run: pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git + - name: Export Language Codes + id: show-langs + run: | + echo "langs=$(python ./scripts/docs.py langs-json)" >> $GITHUB_OUTPUT + build-docs: + needs: + - changes + - langs + if: ${{ needs.changes.outputs.docs == 'true' }} runs-on: ubuntu-latest + strategy: + matrix: + lang: ${{ fromJson(needs.langs.outputs.langs) }} steps: - name: Dump GitHub context env: @@ -22,28 +80,35 @@ jobs: id: cache with: path: ${{ env.pythonLocation }} - key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v03 + key: ${{ runner.os }}-python-docs-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-docs.txt') }}-v05 - name: Install docs extras if: steps.cache.outputs.cache-hit != 'true' run: pip install -r requirements-docs.txt - name: Install Material for MkDocs Insiders if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false ) && steps.cache.outputs.cache-hit != 'true' run: pip install git+https://${{ secrets.ACTIONS_TOKEN }}@github.com/squidfunk/mkdocs-material-insiders.git + - name: Update Languages + run: python ./scripts/docs.py update-languages + - uses: actions/cache@v3 + with: + key: mkdocs-cards-${{ matrix.lang }}-${{ github.ref }} + path: docs/${{ matrix.lang }}/.cache - name: Build Docs - run: python ./scripts/docs.py build-all - - name: Zip docs - run: bash ./scripts/zip-docs.sh + run: python ./scripts/docs.py build-lang ${{ matrix.lang }} - uses: actions/upload-artifact@v3 with: - name: docs-zip - path: ./site/docs.zip - - name: Deploy to Netlify - uses: nwtgck/actions-netlify@v2.0.0 + name: docs-site + path: ./site/** + + # https://github.com/marketplace/actions/alls-green#why + docs-all-green: # This job does nothing and is only used for the branch protection + if: always() + needs: + - build-docs + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 with: - publish-dir: './site' - production-branch: master - github-token: ${{ secrets.FASTAPI_BUILD_DOCS_NETLIFY }} - enable-commit-comment: false - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} + jobs: ${{ toJSON(needs) }} + allowed-skips: build-docs diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/deploy-docs.yml similarity index 69% rename from .github/workflows/preview-docs.yml rename to .github/workflows/deploy-docs.yml index 298f75b02..312d835af 100644 --- a/.github/workflows/preview-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,4 +1,4 @@ -name: Preview Docs +name: Deploy Docs on: workflow_run: workflows: @@ -7,39 +7,42 @@ on: - completed jobs: - preview-docs: + deploy-docs: runs-on: ubuntu-latest steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v3 - name: Clean site run: | rm -rf ./site mkdir ./site - name: Download Artifact Docs + id: download uses: dawidd6/action-download-artifact@v2.27.0 with: + if_no_artifact_found: ignore github_token: ${{ secrets.FASTAPI_PREVIEW_DOCS_DOWNLOAD_ARTIFACTS }} workflow: build-docs.yml run_id: ${{ github.event.workflow_run.id }} - name: docs-zip + name: docs-site path: ./site/ - - name: Unzip docs - run: | - cd ./site - unzip docs.zip - rm -f docs.zip - name: Deploy to Netlify + if: steps.download.outputs.found_artifact == 'true' id: netlify uses: nwtgck/actions-netlify@v2.0.0 with: publish-dir: './site' - production-deploy: false + production-deploy: ${{ github.event.workflow_run.head_repository.full_name == github.repository && github.event.workflow_run.head_branch == 'master' }} github-token: ${{ secrets.FASTAPI_PREVIEW_DOCS_NETLIFY }} enable-commit-comment: false env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} - name: Comment Deploy + if: steps.netlify.outputs.deploy-url != '' uses: ./.github/actions/comment-docs-preview-in-pr with: token: ${{ secrets.FASTAPI_PREVIEW_DOCS_COMMENT_DEPLOY }} diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml index 617105b6e..324623103 100644 --- a/.github/workflows/issue-manager.yml +++ b/.github/workflows/issue-manager.yml @@ -2,7 +2,7 @@ name: Issue Manager on: schedule: - - cron: "0 0 * * *" + - cron: "10 3 * * *" issue_comment: types: - created @@ -16,6 +16,7 @@ on: jobs: issue-manager: + if: github.repository_owner == 'tiangolo' runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.4.0 diff --git a/.github/workflows/label-approved.yml b/.github/workflows/label-approved.yml index 4a73b02aa..976d29f74 100644 --- a/.github/workflows/label-approved.yml +++ b/.github/workflows/label-approved.yml @@ -6,6 +6,7 @@ on: jobs: label-approved: + if: github.repository_owner == 'tiangolo' runs-on: ubuntu-latest steps: - uses: docker://tiangolo/label-approved:0.0.2 diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml index b167c268f..15ea464a1 100644 --- a/.github/workflows/people.yml +++ b/.github/workflows/people.yml @@ -12,6 +12,7 @@ on: jobs: fastapi-people: + if: github.repository_owner == 'tiangolo' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bdadcc6d3..b84c5bf17 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -32,7 +32,7 @@ jobs: - name: Build distribution run: python -m build - name: Publish - uses: pypa/gh-action-pypi-publish@v1.8.5 + uses: pypa/gh-action-pypi-publish@v1.8.6 with: password: ${{ secrets.PYPI_API_TOKEN }} - name: Dump GitHub context diff --git a/.gitignore b/.gitignore index a26bb5cd6..d380d16b7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ Pipfile.lock env3.* env docs_build +site_build venv docs.zip archive.zip @@ -23,3 +24,4 @@ archive.zip # vim temporary files *~ .*.sw? +.cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a8a03136..9f7085f72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,14 +14,14 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.7.0 hooks: - id: pyupgrade args: - --py3-plus - --keep-runtime-typing - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.272 + rev: v0.0.275 hooks: - id: ruff args: diff --git a/README.md b/README.md index ee25f1803..7dc199367 100644 --- a/README.md +++ b/README.md @@ -446,7 +446,6 @@ To understand more about it, see the section ujson - for faster JSON "parsing". * email_validator - for email validation. Used by Starlette: diff --git a/docs/az/docs/index.md b/docs/az/docs/index.md deleted file mode 100644 index 282c15032..000000000 --- a/docs/az/docs/index.md +++ /dev/null @@ -1,466 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```Python -from typing import Optional - -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: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="9 14" -from typing import Optional - -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: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Optional[bool] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on `requests` and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* graphene - Required for `GraphQLApp` support. -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install fastapi[all]`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml deleted file mode 100644 index 1d2930494..000000000 --- a/docs/az/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/az/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: en -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/cs/docs/index.md b/docs/cs/docs/index.md deleted file mode 100644 index bde72f851..000000000 --- a/docs/cs/docs/index.md +++ /dev/null @@ -1,473 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._" - -
Deon Pillsbury - Cisco (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.7+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with Strawberry and other libraries. -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install "fastapi[all]"`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/cs/mkdocs.yml b/docs/cs/mkdocs.yml deleted file mode 100644 index 539d7d65d..000000000 --- a/docs/cs/mkdocs.yml +++ /dev/null @@ -1,154 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/cs/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: cs -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md deleted file mode 100644 index 68fc8b753..000000000 --- a/docs/de/docs/index.md +++ /dev/null @@ -1,464 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on `requests` and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install fastapi[all]`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/de/mkdocs.yml b/docs/de/mkdocs.yml index e475759a8..de18856f4 100644 --- a/docs/de/mkdocs.yml +++ b/docs/de/mkdocs.yml @@ -1,161 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/de/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: de -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/de/overrides/.gitignore b/docs/de/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/em/docs/advanced/index.md b/docs/em/docs/advanced/index.md index 6a43a09e7..abe8d357c 100644 --- a/docs/em/docs/advanced/index.md +++ b/docs/em/docs/advanced/index.md @@ -1,4 +1,4 @@ -# 🏧 👩‍💻 🦮 - 🎶 +# 🏧 👩‍💻 🦮 ## 🌖 ⚒ diff --git a/docs/em/docs/advanced/security/index.md b/docs/em/docs/advanced/security/index.md index 20ee85553..f2bb66df4 100644 --- a/docs/em/docs/advanced/security/index.md +++ b/docs/em/docs/advanced/security/index.md @@ -1,4 +1,4 @@ -# 🏧 💂‍♂ - 🎶 +# 🏧 💂‍♂ ## 🌖 ⚒ diff --git a/docs/em/docs/deployment/index.md b/docs/em/docs/deployment/index.md index 1010c589f..9bcf427b6 100644 --- a/docs/em/docs/deployment/index.md +++ b/docs/em/docs/deployment/index.md @@ -1,4 +1,4 @@ -# 🛠️ - 🎶 +# 🛠️ 🛠️ **FastAPI** 🈸 📶 ⏩. diff --git a/docs/em/docs/tutorial/dependencies/index.md b/docs/em/docs/tutorial/dependencies/index.md index f1c28c573..ffd38d716 100644 --- a/docs/em/docs/tutorial/dependencies/index.md +++ b/docs/em/docs/tutorial/dependencies/index.md @@ -1,4 +1,4 @@ -# 🔗 - 🥇 🔁 +# 🔗 **FastAPI** ✔️ 📶 🏋️ ✋️ 🏋️ **🔗 💉** ⚙️. diff --git a/docs/em/docs/tutorial/index.md b/docs/em/docs/tutorial/index.md index 8536dc3ee..26b4c1913 100644 --- a/docs/em/docs/tutorial/index.md +++ b/docs/em/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# 🔰 - 👩‍💻 🦮 - 🎶 +# 🔰 - 👩‍💻 🦮 👉 🔰 🎦 👆 ❔ ⚙️ **FastAPI** ⏮️ 🌅 🚮 ⚒, 🔁 🔁. diff --git a/docs/em/docs/tutorial/security/index.md b/docs/em/docs/tutorial/security/index.md index 5b507af3e..d76f7203f 100644 --- a/docs/em/docs/tutorial/security/index.md +++ b/docs/em/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# 💂‍♂ 🎶 +# 💂‍♂ 📤 📚 🌌 🍵 💂‍♂, 🤝 & ✔. diff --git a/docs/em/mkdocs.yml b/docs/em/mkdocs.yml index 2c48de93a..de18856f4 100644 --- a/docs/em/mkdocs.yml +++ b/docs/em/mkdocs.yml @@ -1,264 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/em/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: en -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -- 🔰 - 👩‍💻 🦮: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/body.md - - tutorial/query-params-str-validations.md - - tutorial/path-params-numeric-validations.md - - tutorial/body-multiple-params.md - - tutorial/body-fields.md - - tutorial/body-nested-models.md - - tutorial/schema-extra-example.md - - tutorial/extra-data-types.md - - tutorial/cookie-params.md - - tutorial/header-params.md - - tutorial/response-model.md - - tutorial/extra-models.md - - tutorial/response-status-code.md - - tutorial/request-forms.md - - tutorial/request-files.md - - tutorial/request-forms-and-files.md - - tutorial/handling-errors.md - - tutorial/path-operation-configuration.md - - tutorial/encoder.md - - tutorial/body-updates.md - - 🔗: - - tutorial/dependencies/index.md - - tutorial/dependencies/classes-as-dependencies.md - - tutorial/dependencies/sub-dependencies.md - - tutorial/dependencies/dependencies-in-path-operation-decorators.md - - tutorial/dependencies/global-dependencies.md - - tutorial/dependencies/dependencies-with-yield.md - - 💂‍♂: - - tutorial/security/index.md - - tutorial/security/first-steps.md - - tutorial/security/get-current-user.md - - tutorial/security/simple-oauth2.md - - tutorial/security/oauth2-jwt.md - - tutorial/middleware.md - - tutorial/cors.md - - tutorial/sql-databases.md - - tutorial/bigger-applications.md - - tutorial/background-tasks.md - - tutorial/metadata.md - - tutorial/static-files.md - - tutorial/testing.md - - tutorial/debugging.md -- 🏧 👩‍💻 🦮: - - advanced/index.md - - advanced/path-operation-advanced-configuration.md - - advanced/additional-status-codes.md - - advanced/response-directly.md - - advanced/custom-response.md - - advanced/additional-responses.md - - advanced/response-cookies.md - - advanced/response-headers.md - - advanced/response-change-status-code.md - - advanced/advanced-dependencies.md - - 🏧 💂‍♂: - - advanced/security/index.md - - advanced/security/oauth2-scopes.md - - advanced/security/http-basic-auth.md - - advanced/using-request-directly.md - - advanced/dataclasses.md - - advanced/middleware.md - - advanced/sql-databases-peewee.md - - advanced/async-sql-databases.md - - advanced/nosql-databases.md - - advanced/sub-applications.md - - advanced/behind-a-proxy.md - - advanced/templates.md - - advanced/graphql.md - - advanced/websockets.md - - advanced/events.md - - advanced/custom-request-and-route.md - - advanced/testing-websockets.md - - advanced/testing-events.md - - advanced/testing-dependencies.md - - advanced/testing-database.md - - advanced/async-tests.md - - advanced/settings.md - - advanced/conditional-openapi.md - - advanced/extending-openapi.md - - advanced/openapi-callbacks.md - - advanced/wsgi.md - - advanced/generate-clients.md -- async.md -- 🛠️: - - deployment/index.md - - deployment/versions.md - - deployment/https.md - - deployment/manually.md - - deployment/concepts.md - - deployment/deta.md - - deployment/server-workers.md - - deployment/docker.md -- project-generation.md -- alternatives.md -- history-design-future.md -- external-links.md -- benchmarks.md -- help-fastapi.md -- contributing.md -- release-notes.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/em/overrides/.gitignore b/docs/em/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml index af5810778..ad738df35 100644 --- a/docs/en/data/external_links.yml +++ b/docs/en/data/external_links.yml @@ -233,6 +233,10 @@ articles: link: https://medium.com/@krishnardt365/fastapi-docker-and-postgres-91943e71be92 title: Fastapi, Docker(Docker compose) and Postgres german: + - author: Marcel Sander (actidoo) + author_link: https://www.actidoo.com + link: https://www.actidoo.com/de/blog/python-fastapi-domain-driven-design + title: Domain-driven Design mit Python und FastAPI - author: Nico Axtmann author_link: https://twitter.com/_nicoax link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/ diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml index 9913c5df5..1b5240b5e 100644 --- a/docs/en/data/sponsors.yml +++ b/docs/en/data/sponsors.yml @@ -31,3 +31,6 @@ bronze: - url: https://www.exoflare.com/open-source/?utm_source=FastAPI&utm_campaign=open_source title: Biosecurity risk assessments made easy. img: https://fastapi.tiangolo.com/img/sponsors/exoflare.png + - url: https://www.flint.sh + title: IT expertise, consulting and development by passionate people + img: https://fastapi.tiangolo.com/img/sponsors/flint.png diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml index 014744a10..b3cb06327 100644 --- a/docs/en/data/sponsors_badge.yml +++ b/docs/en/data/sponsors_badge.yml @@ -16,3 +16,4 @@ logins: - armand-sauzay - databento-bot - nanram22 + - Flint-company diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md index dca5f6a98..624036ce9 100644 --- a/docs/en/docs/advanced/additional-responses.md +++ b/docs/en/docs/advanced/additional-responses.md @@ -236,5 +236,5 @@ For example: To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification: -* OpenAPI Responses Object, it includes the `Response Object`. -* OpenAPI Response Object, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`. +* OpenAPI Responses Object, it includes the `Response Object`. +* OpenAPI Response Object, you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`. diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index 03198851a..e7af77f3d 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -46,7 +46,7 @@ The docs UI would also need the OpenAPI schema to declare that this API `server` ```JSON hl_lines="4-8" { - "openapi": "3.0.2", + "openapi": "3.1.0", // More stuff here "servers": [ { @@ -298,7 +298,7 @@ Will generate an OpenAPI schema like: ```JSON hl_lines="5-7" { - "openapi": "3.0.2", + "openapi": "3.1.0", // More stuff here "servers": [ { diff --git a/docs/en/docs/advanced/extending-openapi.md b/docs/en/docs/advanced/extending-openapi.md index 36619696b..c47f939af 100644 --- a/docs/en/docs/advanced/extending-openapi.md +++ b/docs/en/docs/advanced/extending-openapi.md @@ -29,10 +29,14 @@ And that function `get_openapi()` receives as parameters: * `title`: The OpenAPI title, shown in the docs. * `version`: The version of your API, e.g. `2.5.0`. -* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.0.2`. -* `description`: The description of your API. +* `openapi_version`: The version of the OpenAPI specification used. By default, the latest: `3.1.0`. +* `summary`: A short summary of the API. +* `description`: The description of your API, this can include markdown and will be shown in the docs. * `routes`: A list of routes, these are each of the registered *path operations*. They are taken from `app.routes`. +!!! info + The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by FastAPI 0.99.0 and above. + ## Overriding the defaults Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need. @@ -51,7 +55,7 @@ First, write all your **FastAPI** application as normally: Then, use the same utility function to generate the OpenAPI schema, inside a `custom_openapi()` function: -```Python hl_lines="2 15-20" +```Python hl_lines="2 15-21" {!../../../docs_src/extending_openapi/tutorial001.py!} ``` @@ -59,7 +63,7 @@ Then, use the same utility function to generate the OpenAPI schema, inside a `cu Now you can add the ReDoc extension, adding a custom `x-logo` to the `info` "object" in the OpenAPI schema: -```Python hl_lines="21-23" +```Python hl_lines="22-24" {!../../../docs_src/extending_openapi/tutorial001.py!} ``` @@ -71,7 +75,7 @@ That way, your application won't have to generate the schema every time a user o It will be generated only once, and then the same cached schema will be used for the next requests. -```Python hl_lines="13-14 24-25" +```Python hl_lines="13-14 25-26" {!../../../docs_src/extending_openapi/tutorial001.py!} ``` @@ -79,7 +83,7 @@ It will be generated only once, and then the same cached schema will be used for Now you can replace the `.openapi()` method with your new function. -```Python hl_lines="28" +```Python hl_lines="29" {!../../../docs_src/extending_openapi/tutorial001.py!} ``` diff --git a/docs/en/docs/advanced/index.md b/docs/en/docs/advanced/index.md index 917f4a62e..467f0833e 100644 --- a/docs/en/docs/advanced/index.md +++ b/docs/en/docs/advanced/index.md @@ -1,4 +1,4 @@ -# Advanced User Guide - Intro +# Advanced User Guide ## Additional Features diff --git a/docs/en/docs/advanced/openapi-callbacks.md b/docs/en/docs/advanced/openapi-callbacks.md index 71924ce8b..37339eae5 100644 --- a/docs/en/docs/advanced/openapi-callbacks.md +++ b/docs/en/docs/advanced/openapi-callbacks.md @@ -103,11 +103,11 @@ It should look just like a normal FastAPI *path operation*: There are 2 main differences from a normal *path operation*: * It doesn't need to have any actual code, because your app will never call this code. It's only used to document the *external API*. So, the function could just have `pass`. -* The *path* can contain an OpenAPI 3 expression (see more below) where it can use variables with parameters and parts of the original request sent to *your API*. +* The *path* can contain an OpenAPI 3 expression (see more below) where it can use variables with parameters and parts of the original request sent to *your API*. ### The callback path expression -The callback *path* can have an OpenAPI 3 expression that can contain parts of the original request sent to *your API*. +The callback *path* can have an OpenAPI 3 expression that can contain parts of the original request sent to *your API*. In this case, it's the `str`: diff --git a/docs/en/docs/advanced/openapi-webhooks.md b/docs/en/docs/advanced/openapi-webhooks.md new file mode 100644 index 000000000..63cbdc610 --- /dev/null +++ b/docs/en/docs/advanced/openapi-webhooks.md @@ -0,0 +1,51 @@ +# OpenAPI Webhooks + +There are cases where you want to tell your API **users** that your app could call *their* app (sending a request) with some data, normally to **notify** of some type of **event**. + +This means that instead of the normal process of your users sending requests to your API, it's **your API** (or your app) that could **send requests to their system** (to their API, their app). + +This is normally called a **webhook**. + +## Webhooks steps + +The process normally is that **you define** in your code what is the message that you will send, the **body of the request**. + +You also define in some way at which **moments** your app will send those requests or events. + +And **your users** define in some way (for example in a web dashboard somewhere) the **URL** where your app should send those requests. + +All the **logic** about how to register the URLs for webhooks and the code to actually send those requests is up to you. You write it however you want to in **your own code**. + +## Documenting webhooks with **FastAPI** and OpenAPI + +With **FastAPI**, using OpenAPI, you can define the names of these webhooks, the types of HTTP operations that your app can send (e.g. `POST`, `PUT`, etc.) and the request **bodies** that your app would send. + +This can make it a lot easier for your users to **implement their APIs** to receive your **webhook** requests, they might even be able to autogenerate some of their own API code. + +!!! info + Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` and above. + +## An app with webhooks + +When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`. + +```Python hl_lines="9-13 36-53" +{!../../../docs_src/openapi_webhooks/tutorial001.py!} +``` + +The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**. + +!!! info + The `app.webhooks` object is actually just an `APIRouter`, the same type you would use when structuring your app with multiple files. + +Notice that with webhooks you are actually not declaring a *path* (like `/items/`), the text you pass there is just an **identifier** of the webhook (the name of the event), for example in `@app.webhooks.post("new-subscription")`, the webhook name is `new-subscription`. + +This is because it is expected that **your users** would define the actual **URL path** where they want to receive the webhook request in some other way (e.g. a web dashboard). + +### Check the docs + +Now you can start your app with Uvicorn and go to http://127.0.0.1:8000/docs. + +You will see your docs have the normal *path operations* and now also some **webhooks**: + + diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index a1c902ef2..6d9a5fe70 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -97,7 +97,7 @@ And if you see the resulting OpenAPI (at `/openapi.json` in your API), you will ```JSON hl_lines="22" { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" diff --git a/docs/en/docs/advanced/security/index.md b/docs/en/docs/advanced/security/index.md index 0c94986b5..c18baf64b 100644 --- a/docs/en/docs/advanced/security/index.md +++ b/docs/en/docs/advanced/security/index.md @@ -1,4 +1,4 @@ -# Advanced Security - Intro +# Advanced Security ## Additional Features diff --git a/docs/en/docs/advanced/testing-database.md b/docs/en/docs/advanced/testing-database.md index 16484b09a..13a6959b6 100644 --- a/docs/en/docs/advanced/testing-database.md +++ b/docs/en/docs/advanced/testing-database.md @@ -44,7 +44,7 @@ So the new file structure looks like: First, we create a new database session with the new database. -For the tests we'll use a file `test.db` instead of `sql_app.db`. +We'll use an in-memory database that persists during the tests instead of the local file `sql_app.db`. But the rest of the session code is more or less the same, we just copy it. diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index 660914a08..f968489ae 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -195,6 +195,21 @@ It will serve the documentation on `http://127.0.0.1:8008`. That way, you can edit the documentation/source files and see the changes live. +!!! tip + Alternatively, you can perform the same steps that scripts does manually. + + Go into the language directory, for the main docs in English it's at `docs/en/`: + + ```console + $ cd docs/en/ + ``` + + Then run `mkdocs` in that directory: + + ```console + $ mkdocs serve --dev-addr 8008 + ``` + #### Typer CLI (optional) The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly. @@ -245,13 +260,15 @@ Here are the steps to help with translations. Check the docs about adding a pull request review to approve it or request changes. -* Check in the issues to see if there's one coordinating translations for your language. +* Check if there's a GitHub Discussion to coordinate translations for your language. You can subscribe to it, and when there's a new pull request to review, an automatic comment will be added to the discussion. * Add a single pull request per page translated. That will make it much easier for others to review it. For the languages I don't speak, I'll wait for several others to review the translation before merging. * You can also check if there are translations for your language and add a review to them, that will help me know that the translation is correct and I can merge it. + * You could check in the GitHub Discussions for your language. + * Or you can filter the existing PRs by the ones with the label for your language, for example, for Spanish, the label is `lang-es`. * Use the same Python examples and only translate the text in the docs. You don't have to change anything for this to work. @@ -283,11 +300,24 @@ $ python ./scripts/docs.py live es -Now you can go to http://127.0.0.1:8008 and see your changes live. +!!! tip + Alternatively, you can perform the same steps that scripts does manually. -If you look at the FastAPI docs website, you will see that every language has all the pages. But some pages are not translated and have a notification about the missing translation. + Go into the language directory, for the Spanish translations it's at `docs/es/`: -But when you run it locally like this, you will only see the pages that are already translated. + ```console + $ cd docs/es/ + ``` + + Then run `mkdocs` in that directory: + + ```console + $ mkdocs serve --dev-addr 8008 + ``` + +Now you can go to http://127.0.0.1:8008 and see your changes live. + +You will see that every language has all the pages. But some pages are not translated and have a notification about the missing translation. Now let's say that you want to add a translation for the section [Features](features.md){.internal-link target=_blank}. @@ -306,46 +336,6 @@ docs/es/docs/features.md !!! tip Notice that the only change in the path and file name is the language code, from `en` to `es`. -* Now open the MkDocs config file for English at: - -``` -docs/en/mkdocs.yml -``` - -* Find the place where that `docs/features.md` is located in the config file. Somewhere like: - -```YAML hl_lines="8" -site_name: FastAPI -# More stuff -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -* Open the MkDocs config file for the language you are editing, e.g.: - -``` -docs/es/mkdocs.yml -``` - -* Add it there at the exact same location it was for English, e.g.: - -```YAML hl_lines="8" -site_name: FastAPI -# More stuff -nav: -- FastAPI: index.md -- Languages: - - en: / - - es: /es/ -- features.md -``` - -Make sure that if there are other entries, the new entry with your translation is exactly in the same order as in the English version. - If you go to your browser you will see that now the docs show your new section. 🎉 Now you can translate it all and see how it looks as you save the file. @@ -367,55 +357,32 @@ The next step is to run the script to generate a new translation directory: $ python ./scripts/docs.py new-lang ht Successfully initialized: docs/ht -Updating ht -Updating en ``` Now you can check in your code editor the newly created directory `docs/ht/`. -!!! tip - Create a first pull request with just this, to set up the configuration for the new language, before adding translations. +That command created a file `docs/ht/mkdocs.yml` with a simple config that inherits everything from the `en` version: - That way others can help with other pages while you work on the first one. 🚀 - -Start by translating the main page, `docs/ht/index.md`. - -Then you can continue with the previous instructions, for an "Existing Language". - -##### New Language not supported - -If when running the live server script you get an error about the language not being supported, something like: - -``` - raise TemplateNotFound(template) -jinja2.exceptions.TemplateNotFound: partials/language/xx.html +```yaml +INHERIT: ../en/mkdocs.yml ``` -That means that the theme doesn't support that language (in this case, with a fake 2-letter code of `xx`). - -But don't worry, you can set the theme language to English and then translate the content of the docs. +!!! tip + You could also simply create that file with those contents manually. -If you need to do that, edit the `mkdocs.yml` for your new language, it will have something like: +That command also created a dummy file `docs/ht/index.md` for the main page, you can start by translating that one. -```YAML hl_lines="5" -site_name: FastAPI -# More stuff -theme: - # More stuff - language: xx -``` +You can continue with the previous instructions for an "Existing Language" for that process. -Change that language from `xx` (from your language code) to `en`. - -Then you can start the live server again. +You can make the first pull request with those two files, `docs/ht/mkdocs.yml` and `docs/ht/index.md`. 🎉 #### Preview the result -When you use the script at `./scripts/docs.py` with the `live` command it only shows the files and translations available for the current language. +You can use the `./scripts/docs.py` with the `live` command to preview the results (or `mkdocs serve`). -But once you are done, you can test it all as it would look online. +Once you are done, you can also test it all as it would look online, including all the other languages. To do that, first build all the docs: @@ -425,19 +392,14 @@ To do that, first build all the docs: // Use the command "build-all", this will take a bit $ python ./scripts/docs.py build-all -Updating es -Updating en Building docs for: en Building docs for: es Successfully built docs for: es -Copying en index.md to README.md ``` -That generates all the docs at `./docs_build/` for each language. This includes adding any files with missing translations, with a note saying that "this file doesn't have a translation yet". But you don't have to do anything with that directory. - -Then it builds all those independent MkDocs sites for each language, combines them, and generates the final output at `./site/`. +This builds all those independent MkDocs sites for each language, combines them, and generates the final output at `./site/`. Then you can serve that with the command `serve`: diff --git a/docs/en/docs/deployment/index.md b/docs/en/docs/deployment/index.md index f0fd001cd..6c43d8abb 100644 --- a/docs/en/docs/deployment/index.md +++ b/docs/en/docs/deployment/index.md @@ -1,4 +1,4 @@ -# Deployment - Intro +# Deployment Deploying a **FastAPI** application is relatively easy. diff --git a/docs/en/docs/img/sponsors/flint.png b/docs/en/docs/img/sponsors/flint.png new file mode 100644 index 0000000000000000000000000000000000000000..761cc334c241f3c52a574c04a880640065376a3b GIT binary patch literal 10409 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 Kh?R@z`~5e=$(Wh| literal 0 HcmV?d00001 diff --git a/docs/en/docs/img/tutorial/metadata/image01.png b/docs/en/docs/img/tutorial/metadata/image01.png index b7708a3fd98ad2170d880781896623ec8ed80204..4146a8607b5b4278b9d826c5d9e45ee9ff183ce3 100644 GIT binary patch literal 86437 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 literal 90062 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 diff --git a/docs/en/docs/img/tutorial/openapi-webhooks/image01.png b/docs/en/docs/img/tutorial/openapi-webhooks/image01.png new file mode 100644 index 0000000000000000000000000000000000000000..25ced48186d8395b0406b9a66e75afb17aa83fd5 GIT binary patch literal 86925 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�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 literal 0 HcmV?d00001 diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index 9a81f14d1..afd6d7138 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -445,7 +445,6 @@ To understand more about it, see the section ujson - for faster JSON "parsing". * email_validator - for email validation. Used by Starlette: diff --git a/docs/en/docs/newsletter.md b/docs/en/docs/newsletter.md index 6403f31e6..782db1353 100644 --- a/docs/en/docs/newsletter.md +++ b/docs/en/docs/newsletter.md @@ -1,5 +1,5 @@ # FastAPI and friends newsletter - + - + diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 15e951035..b68bd5f00 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -2,6 +2,88 @@ ## Latest Changes + +## 0.99.0 + +### Features + +* ✨ Add support for OpenAPI 3.1.0. PR [#9770](https://github.com/tiangolo/fastapi/pull/9770) by [@tiangolo](https://github.com/tiangolo). + * New support for documenting **webhooks**, read the new docs here: Advanced User Guide: OpenAPI Webhooks. + * Upgrade OpenAPI 3.1.0, this uses JSON Schema 2020-12. + * Upgrade Swagger UI to version 5.x.x, that supports OpenAPI 3.1.0. + * Updated `examples` field in `Query()`, `Cookie()`, `Body()`, etc. based on the latest JSON Schema and OpenAPI. Now it takes a list of examples and they are included directly in the JSON Schema, not outside. Read more about it (including the historical technical details) in the updated docs: Tutorial: Declare Request Example Data. + +* ✨ Add support for `deque` objects and children in `jsonable_encoder`. PR [#9433](https://github.com/tiangolo/fastapi/pull/9433) by [@cranium](https://github.com/cranium). + +### Docs + +* 📝 Fix form for the FastAPI and friends newsletter. PR [#9749](https://github.com/tiangolo/fastapi/pull/9749) by [@tiangolo](https://github.com/tiangolo). + +### Translations + +* 🌐 Add Persian translation for `docs/fa/docs/advanced/sub-applications.md`. PR [#9692](https://github.com/tiangolo/fastapi/pull/9692) by [@mojtabapaso](https://github.com/mojtabapaso). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/response-model.md`. PR [#9675](https://github.com/tiangolo/fastapi/pull/9675) by [@glsglsgls](https://github.com/glsglsgls). + +### Internal + +* 🔨 Enable linenums in MkDocs Material during local live development to simplify highlighting code. PR [#9769](https://github.com/tiangolo/fastapi/pull/9769) by [@tiangolo](https://github.com/tiangolo). +* ⬆ Update httpx requirement from <0.24.0,>=0.23.0 to >=0.23.0,<0.25.0. PR [#9724](https://github.com/tiangolo/fastapi/pull/9724) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump mkdocs-material from 9.1.16 to 9.1.17. PR [#9746](https://github.com/tiangolo/fastapi/pull/9746) by [@dependabot[bot]](https://github.com/apps/dependabot). +* 🔥 Remove missing translation dummy pages, no longer necessary. PR [#9751](https://github.com/tiangolo/fastapi/pull/9751) by [@tiangolo](https://github.com/tiangolo). +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#9259](https://github.com/tiangolo/fastapi/pull/9259) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). +* ✨ Add Material for MkDocs Insiders features and cards. PR [#9748](https://github.com/tiangolo/fastapi/pull/9748) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Remove languages without translations. PR [#9743](https://github.com/tiangolo/fastapi/pull/9743) by [@tiangolo](https://github.com/tiangolo). +* ✨ Refactor docs for building scripts, use MkDocs hooks, simplify (remove) configs for languages. PR [#9742](https://github.com/tiangolo/fastapi/pull/9742) by [@tiangolo](https://github.com/tiangolo). +* 🔨 Add MkDocs hook that renames sections based on the first index file. PR [#9737](https://github.com/tiangolo/fastapi/pull/9737) by [@tiangolo](https://github.com/tiangolo). +* 👷 Make cron jobs run only on main repo, not on forks, to avoid error notifications from missing tokens. PR [#9735](https://github.com/tiangolo/fastapi/pull/9735) by [@tiangolo](https://github.com/tiangolo). +* 🔧 Update MkDocs for other languages. PR [#9734](https://github.com/tiangolo/fastapi/pull/9734) by [@tiangolo](https://github.com/tiangolo). +* 👷 Refactor Docs CI, run in multiple workers with a dynamic matrix to optimize speed. PR [#9732](https://github.com/tiangolo/fastapi/pull/9732) by [@tiangolo](https://github.com/tiangolo). +* 🔥 Remove old internal GitHub Action watch-previews that is no longer needed. PR [#9730](https://github.com/tiangolo/fastapi/pull/9730) by [@tiangolo](https://github.com/tiangolo). +* ⬆️ Upgrade MkDocs and MkDocs Material. PR [#9729](https://github.com/tiangolo/fastapi/pull/9729) by [@tiangolo](https://github.com/tiangolo). +* 👷 Build and deploy docs only on docs changes. PR [#9728](https://github.com/tiangolo/fastapi/pull/9728) by [@tiangolo](https://github.com/tiangolo). + +## 0.98.0 + +### Features + +* ✨ Allow disabling `redirect_slashes` at the FastAPI app level. PR [#3432](https://github.com/tiangolo/fastapi/pull/3432) by [@cyberlis](https://github.com/cyberlis). + +### Docs + +* 📝 Update docs on Pydantic using ujson internally. PR [#5804](https://github.com/tiangolo/fastapi/pull/5804) by [@mvasilkov](https://github.com/mvasilkov). +* ✏ Rewording in `docs/en/docs/tutorial/debugging.md`. PR [#9581](https://github.com/tiangolo/fastapi/pull/9581) by [@ivan-abc](https://github.com/ivan-abc). +* 📝 Add german blog post (Domain-driven Design mit Python und FastAPI). PR [#9261](https://github.com/tiangolo/fastapi/pull/9261) by [@msander](https://github.com/msander). +* ✏️ Tweak wording in `docs/en/docs/tutorial/security/index.md`. PR [#9561](https://github.com/tiangolo/fastapi/pull/9561) by [@jyothish-mohan](https://github.com/jyothish-mohan). +* 📝 Update `Annotated` notes in `docs/en/docs/tutorial/schema-extra-example.md`. PR [#9620](https://github.com/tiangolo/fastapi/pull/9620) by [@Alexandrhub](https://github.com/Alexandrhub). +* ✏️ Fix typo `Annotation` -> `Annotated` in `docs/en/docs/tutorial/query-params-str-validations.md`. PR [#9625](https://github.com/tiangolo/fastapi/pull/9625) by [@mccricardo](https://github.com/mccricardo). +* 📝 Use in memory database for testing SQL in docs. PR [#1223](https://github.com/tiangolo/fastapi/pull/1223) by [@HarshaLaxman](https://github.com/HarshaLaxman). + +### Translations + +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/metadata.md`. PR [#9681](https://github.com/tiangolo/fastapi/pull/9681) by [@TabarakoAkula](https://github.com/TabarakoAkula). +* 🌐 Fix typo in Spanish translation for `docs/es/docs/tutorial/first-steps.md`. PR [#9571](https://github.com/tiangolo/fastapi/pull/9571) by [@lilidl-nft](https://github.com/lilidl-nft). +* 🌐 Add Russian translation for `docs/tutorial/path-operation-configuration.md`. PR [#9696](https://github.com/tiangolo/fastapi/pull/9696) by [@TabarakoAkula](https://github.com/TabarakoAkula). +* 🌐 Add Chinese translation for `docs/zh/docs/advanced/security/index.md`. PR [#9666](https://github.com/tiangolo/fastapi/pull/9666) by [@lordqyxz](https://github.com/lordqyxz). +* 🌐 Add Chinese translations for `docs/zh/docs/advanced/settings.md`. PR [#9652](https://github.com/tiangolo/fastapi/pull/9652) by [@ChoyeonChern](https://github.com/ChoyeonChern). +* 🌐 Add Chinese translations for `docs/zh/docs/advanced/websockets.md`. PR [#9651](https://github.com/tiangolo/fastapi/pull/9651) by [@ChoyeonChern](https://github.com/ChoyeonChern). +* 🌐 Add Chinese translation for `docs/zh/docs/tutorial/testing.md`. PR [#9641](https://github.com/tiangolo/fastapi/pull/9641) by [@wdh99](https://github.com/wdh99). +* 🌐 Add Russian translation for `docs/tutorial/extra-models.md`. PR [#9619](https://github.com/tiangolo/fastapi/pull/9619) by [@ivan-abc](https://github.com/ivan-abc). +* 🌐 Add Russian translation for `docs/tutorial/cors.md`. PR [#9608](https://github.com/tiangolo/fastapi/pull/9608) by [@ivan-abc](https://github.com/ivan-abc). +* 🌐 Add Polish translation for `docs/pl/docs/features.md`. PR [#5348](https://github.com/tiangolo/fastapi/pull/5348) by [@mbroton](https://github.com/mbroton). +* 🌐 Add Russian translation for `docs/ru/docs/tutorial/body-nested-models.md`. PR [#9605](https://github.com/tiangolo/fastapi/pull/9605) by [@Alexandrhub](https://github.com/Alexandrhub). + +### Internal + +* ⬆ Bump ruff from 0.0.272 to 0.0.275. PR [#9721](https://github.com/tiangolo/fastapi/pull/9721) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Update uvicorn[standard] requirement from <0.21.0,>=0.12.0 to >=0.12.0,<0.23.0. PR [#9463](https://github.com/tiangolo/fastapi/pull/9463) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump mypy from 1.3.0 to 1.4.0. PR [#9719](https://github.com/tiangolo/fastapi/pull/9719) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Update pre-commit requirement from <3.0.0,>=2.17.0 to >=2.17.0,<4.0.0. PR [#9251](https://github.com/tiangolo/fastapi/pull/9251) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ⬆ Bump pypa/gh-action-pypi-publish from 1.8.5 to 1.8.6. PR [#9482](https://github.com/tiangolo/fastapi/pull/9482) by [@dependabot[bot]](https://github.com/apps/dependabot). +* ✏️ Fix tooltips for light/dark theme toggler in docs. PR [#9588](https://github.com/tiangolo/fastapi/pull/9588) by [@pankaj1707k](https://github.com/pankaj1707k). +* 🔧 Set minimal hatchling version needed to build the package. PR [#9240](https://github.com/tiangolo/fastapi/pull/9240) by [@mgorny](https://github.com/mgorny). +* 📝 Add repo link to PyPI. PR [#9559](https://github.com/tiangolo/fastapi/pull/9559) by [@JacobCoffee](https://github.com/JacobCoffee). +* ✏️ Fix typos in data for tests. PR [#4958](https://github.com/tiangolo/fastapi/pull/4958) by [@ryanrussell](https://github.com/ryanrussell). +* 🔧 Update sponsors, add Flint. PR [#9699](https://github.com/tiangolo/fastapi/pull/9699) by [@tiangolo](https://github.com/tiangolo). * 👷 Lint in CI only once, only with one version of Python, run tests with all of them. PR [#9686](https://github.com/tiangolo/fastapi/pull/9686) by [@tiangolo](https://github.com/tiangolo). ## 0.97.0 diff --git a/docs/en/docs/tutorial/debugging.md b/docs/en/docs/tutorial/debugging.md index bda889c45..3deba54d5 100644 --- a/docs/en/docs/tutorial/debugging.md +++ b/docs/en/docs/tutorial/debugging.md @@ -64,7 +64,7 @@ from myapp import app # Some more code ``` -in that case, the automatic variable inside of `myapp.py` will not have the variable `__name__` with a value of `"__main__"`. +in that case, the automatically created variable inside of `myapp.py` will not have the variable `__name__` with a value of `"__main__"`. So, the line: diff --git a/docs/en/docs/tutorial/dependencies/index.md b/docs/en/docs/tutorial/dependencies/index.md index 4f5ecea66..f6f4bced0 100644 --- a/docs/en/docs/tutorial/dependencies/index.md +++ b/docs/en/docs/tutorial/dependencies/index.md @@ -1,4 +1,4 @@ -# Dependencies - First Steps +# Dependencies **FastAPI** has a very powerful but intuitive **Dependency Injection** system. diff --git a/docs/en/docs/tutorial/first-steps.md b/docs/en/docs/tutorial/first-steps.md index 6ca5f39eb..cfa159329 100644 --- a/docs/en/docs/tutorial/first-steps.md +++ b/docs/en/docs/tutorial/first-steps.md @@ -99,7 +99,7 @@ It will show a JSON starting with something like: ```JSON { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" diff --git a/docs/en/docs/tutorial/index.md b/docs/en/docs/tutorial/index.md index 8b4a9df9b..75665324d 100644 --- a/docs/en/docs/tutorial/index.md +++ b/docs/en/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Tutorial - User Guide - Intro +# Tutorial - User Guide This tutorial shows you how to use **FastAPI** with most of its features, step by step. diff --git a/docs/en/docs/tutorial/metadata.md b/docs/en/docs/tutorial/metadata.md index cf13e7470..e75b4a0b9 100644 --- a/docs/en/docs/tutorial/metadata.md +++ b/docs/en/docs/tutorial/metadata.md @@ -9,15 +9,16 @@ You can set the following fields that are used in the OpenAPI specification and | Parameter | Type | Description | |------------|------|-------------| | `title` | `str` | The title of the API. | +| `summary` | `str` | A short summary of the API. Available since OpenAPI 3.1.0, FastAPI 0.99.0. | | `description` | `str` | A short description of the API. It can use Markdown. | | `version` | `string` | The version of the API. This is the version of your own application, not of OpenAPI. For example `2.5.0`. | | `terms_of_service` | `str` | A URL to the Terms of Service for the API. If provided, this has to be a URL. | | `contact` | `dict` | The contact information for the exposed API. It can contain several fields.
contact fields
ParameterTypeDescription
namestrThe identifying name of the contact person/organization.
urlstrThe URL pointing to the contact information. MUST be in the format of a URL.
emailstrThe email address of the contact person/organization. MUST be in the format of an email address.
| -| `license_info` | `dict` | The license information for the exposed API. It can contain several fields.
license_info fields
ParameterTypeDescription
namestrREQUIRED (if a license_info is set). The license name used for the API.
urlstrA URL to the license used for the API. MUST be in the format of a URL.
| +| `license_info` | `dict` | The license information for the exposed API. It can contain several fields.
license_info fields
ParameterTypeDescription
namestrREQUIRED (if a license_info is set). The license name used for the API.
identifierstrAn SPDX license expression for the API. The identifier field is mutually exclusive of the url field. Available since OpenAPI 3.1.0, FastAPI 0.99.0.
urlstrA URL to the license used for the API. MUST be in the format of a URL.
| You can set them as follows: -```Python hl_lines="3-16 19-31" +```Python hl_lines="3-16 19-32" {!../../../docs_src/metadata/tutorial001.py!} ``` @@ -28,6 +29,16 @@ With this configuration, the automatic API docs would look like: +## License identifier + +Since OpenAPI 3.1.0 and FastAPI 0.99.0, you can also set the `license_info` with an `identifier` instead of a `url`. + +For example: + +```Python hl_lines="31" +{!../../../docs_src/metadata/tutorial001_1.py!} +``` + ## Metadata for tags You can also add additional metadata for the different tags used to group your path operations with the parameter `openapi_tags`. diff --git a/docs/en/docs/tutorial/path-params.md b/docs/en/docs/tutorial/path-params.md index a0d70692e..6594a7a8b 100644 --- a/docs/en/docs/tutorial/path-params.md +++ b/docs/en/docs/tutorial/path-params.md @@ -83,7 +83,7 @@ And when you open your browser at OpenAPI standard, there are many compatible tools. +And because the generated schema is from the OpenAPI standard, there are many compatible tools. Because of this, **FastAPI** itself provides an alternative API documentation (using ReDoc), which you can access at http://127.0.0.1:8000/redoc: diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index c4b221cb1..549e6c75b 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -44,7 +44,7 @@ To achieve that, first import: === "Python 3.6+" - In versions of Python below Python 3.9 you import `Annotation` from `typing_extensions`. + In versions of Python below Python 3.9 you import `Annotated` from `typing_extensions`. It will already be installed with FastAPI. diff --git a/docs/en/docs/tutorial/schema-extra-example.md b/docs/en/docs/tutorial/schema-extra-example.md index 5312254d9..86ccb1f5a 100644 --- a/docs/en/docs/tutorial/schema-extra-example.md +++ b/docs/en/docs/tutorial/schema-extra-example.md @@ -6,17 +6,17 @@ Here are several ways to do it. ## Pydantic `schema_extra` -You can declare an `example` for a Pydantic model using `Config` and `schema_extra`, as described in Pydantic's docs: Schema customization: +You can declare `examples` for a Pydantic model using `Config` and `schema_extra`, as described in Pydantic's docs: Schema customization: === "Python 3.10+" - ```Python hl_lines="13-21" + ```Python hl_lines="13-23" {!> ../../../docs_src/schema_extra_example/tutorial001_py310.py!} ``` === "Python 3.6+" - ```Python hl_lines="15-23" + ```Python hl_lines="15-25" {!> ../../../docs_src/schema_extra_example/tutorial001.py!} ``` @@ -27,11 +27,16 @@ That extra info will be added as-is to the output **JSON Schema** for that model For example you could use it to add metadata for a frontend user interface, etc. -## `Field` additional arguments +!!! info + OpenAPI 3.1.0 (used since FastAPI 0.99.0) added support for `examples`, which is part of the **JSON Schema** standard. + + Before that, it only supported the keyword `example` with a single example. That is still supported by OpenAPI 3.1.0, but is deprecated and is not part of the JSON Schema standard. So you are encouraged to migrate `example` to `examples`. 🤓 -When using `Field()` with Pydantic models, you can also declare extra info for the **JSON Schema** by passing any other arbitrary arguments to the function. + You can read more at the end of this page. + +## `Field` additional arguments -You can use this to add `example` for each field: +When using `Field()` with Pydantic models, you can also declare additional `examples`: === "Python 3.10+" @@ -45,10 +50,7 @@ You can use this to add `example` for each field: {!> ../../../docs_src/schema_extra_example/tutorial002.py!} ``` -!!! warning - Keep in mind that those extra arguments passed won't add any validation, only extra information, for documentation purposes. - -## `example` and `examples` in OpenAPI +## `examples` in OpenAPI When using any of: @@ -60,33 +62,36 @@ When using any of: * `Form()` * `File()` -you can also declare a data `example` or a group of `examples` with additional information that will be added to **OpenAPI**. +you can also declare a group of `examples` with additional information that will be added to **OpenAPI**. -### `Body` with `example` +### `Body` with `examples` -Here we pass an `example` of the data expected in `Body()`: +Here we pass `examples` containing one example of the data expected in `Body()`: === "Python 3.10+" - ```Python hl_lines="22-27" + ```Python hl_lines="22-29" {!> ../../../docs_src/schema_extra_example/tutorial003_an_py310.py!} ``` === "Python 3.9+" - ```Python hl_lines="22-27" + ```Python hl_lines="22-29" {!> ../../../docs_src/schema_extra_example/tutorial003_an_py39.py!} ``` === "Python 3.6+" - ```Python hl_lines="23-28" + ```Python hl_lines="23-30" {!> ../../../docs_src/schema_extra_example/tutorial003_an.py!} ``` === "Python 3.10+ non-Annotated" - ```Python hl_lines="18-23" + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="18-25" {!> ../../../docs_src/schema_extra_example/tutorial003_py310.py!} ``` @@ -95,7 +100,7 @@ Here we pass an `example` of the data expected in `Body()`: !!! tip Prefer to use the `Annotated` version if possible. - ```Python hl_lines="20-25" + ```Python hl_lines="20-27" {!> ../../../docs_src/schema_extra_example/tutorial003.py!} ``` @@ -107,38 +112,32 @@ With any of the methods above it would look like this in the `/docs`: ### `Body` with multiple `examples` -Alternatively to the single `example`, you can pass `examples` using a `dict` with **multiple examples**, each with extra information that will be added to **OpenAPI** too. - -The keys of the `dict` identify each example, and each value is another `dict`. - -Each specific example `dict` in the `examples` can contain: - -* `summary`: Short description for the example. -* `description`: A long description that can contain Markdown text. -* `value`: This is the actual example shown, e.g. a `dict`. -* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`. +You can of course also pass multiple `examples`: === "Python 3.10+" - ```Python hl_lines="23-49" + ```Python hl_lines="23-38" {!> ../../../docs_src/schema_extra_example/tutorial004_an_py310.py!} ``` === "Python 3.9+" - ```Python hl_lines="23-49" + ```Python hl_lines="23-38" {!> ../../../docs_src/schema_extra_example/tutorial004_an_py39.py!} ``` === "Python 3.6+" - ```Python hl_lines="24-50" + ```Python hl_lines="24-39" {!> ../../../docs_src/schema_extra_example/tutorial004_an.py!} ``` === "Python 3.10+ non-Annotated" - ```Python hl_lines="19-45" + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python hl_lines="19-34" {!> ../../../docs_src/schema_extra_example/tutorial004_py310.py!} ``` @@ -147,7 +146,7 @@ Each specific example `dict` in the `examples` can contain: !!! tip Prefer to use the `Annotated` version if possible. - ```Python hl_lines="21-47" + ```Python hl_lines="21-36" {!> ../../../docs_src/schema_extra_example/tutorial004.py!} ``` @@ -159,25 +158,76 @@ With `examples` added to `Body()` the `/docs` would look like: ## Technical Details +!!! tip + If you are already using **FastAPI** version **0.99.0 or above**, you can probably **skip** these details. + + They are more relevant for older versions, before OpenAPI 3.1.0 was available. + + You can consider this a brief OpenAPI and JSON Schema **history lesson**. 🤓 + !!! warning These are very technical details about the standards **JSON Schema** and **OpenAPI**. If the ideas above already work for you, that might be enough, and you probably don't need these details, feel free to skip them. -When you add an example inside of a Pydantic model, using `schema_extra` or `Field(example="something")` that example is added to the **JSON Schema** for that Pydantic model. +Before OpenAPI 3.1.0, OpenAPI used an older and modified version of **JSON Schema**. -And that **JSON Schema** of the Pydantic model is included in the **OpenAPI** of your API, and then it's used in the docs UI. +JSON Schema didn't have `examples`, so OpenAPI added it's own `example` field to its own modified version. + +OpenAPI also added `example` and `examples` fields to other parts of the specification: + +* `Parameter Object` (in the specification) that was used by FastAPI's: + * `Path()` + * `Query()` + * `Header()` + * `Cookie()` +* `Request Body Object`, in the field `content`, on the `Media Type Object` (in the specification) that was used by FastAPI's: + * `Body()` + * `File()` + * `Form()` + +### OpenAPI's `examples` field + +The shape of this field `examples` from OpenAPI is a `dict` with **multiple examples**, each with extra information that will be added to **OpenAPI** too. + +The keys of the `dict` identify each example, and each value is another `dict`. + +Each specific example `dict` in the `examples` can contain: + +* `summary`: Short description for the example. +* `description`: A long description that can contain Markdown text. +* `value`: This is the actual example shown, e.g. a `dict`. +* `externalValue`: alternative to `value`, a URL pointing to the example. Although this might not be supported by as many tools as `value`. + +This applies to those other parts of the OpenAPI specification apart from JSON Schema. + +### JSON Schema's `examples` field + +But then JSON Schema added an `examples` field to a new version of the specification. + +And then the new OpenAPI 3.1.0 was based on the latest version (JSON Schema 2020-12) that included this new field `examples`. -**JSON Schema** doesn't really have a field `example` in the standards. Recent versions of JSON Schema define a field `examples`, but OpenAPI 3.0.3 is based on an older version of JSON Schema that didn't have `examples`. +And now this new `examples` field takes precedence over the old single (and custom) `example` field, that is now deprecated. -So, OpenAPI 3.0.3 defined its own `example` for the modified version of **JSON Schema** it uses, for the same purpose (but it's a single `example`, not `examples`), and that's what is used by the API docs UI (using Swagger UI). +This new `examples` field in JSON Schema is **just a `list`** of examples, not a dict with extra metadata as in the other places in OpenAPI (described above). + +!!! info + Even after OpenAPI 3.1.0 was released with this new simpler integration with JSON Schema, for a while, Swagger UI, the tool that provides the automatic docs, didn't support OpenAPI 3.1.0 (it does since version 5.0.0 🎉). + + Because of that, versions of FastAPI previous to 0.99.0 still used versions of OpenAPI lower than 3.1.0. + +### Pydantic and FastAPI `examples` + +When you add `examples` inside of a Pydantic model, using `schema_extra` or `Field(examples=["something"])` that example is added to the **JSON Schema** for that Pydantic model. + +And that **JSON Schema** of the Pydantic model is included in the **OpenAPI** of your API, and then it's used in the docs UI. -So, although `example` is not part of JSON Schema, it is part of OpenAPI's custom version of JSON Schema, and that's what will be used by the docs UI. +In versions of FastAPI before 0.99.0 (0.99.0 and above use the newer OpenAPI 3.1.0) when you used `example` or `examples` with any of the other utilities (`Query()`, `Body()`, etc.) those examples were not added to the JSON Schema that describes that data (not even to OpenAPI's own version of JSON Schema), they were added directly to the *path operation* declaration in OpenAPI (outside the parts of OpenAPI that use JSON Schema). -But when you use `example` or `examples` with any of the other utilities (`Query()`, `Body()`, etc.) those examples are not added to the JSON Schema that describes that data (not even to OpenAPI's own version of JSON Schema), they are added directly to the *path operation* declaration in OpenAPI (outside the parts of OpenAPI that use JSON Schema). +But now that FastAPI 0.99.0 and above uses OpenAPI 3.1.0, that uses JSON Schema 2020-12, and Swagger UI 5.0.0 and above, everything is more consistent and the examples are included in JSON Schema. -For `Path()`, `Query()`, `Header()`, and `Cookie()`, the `example` or `examples` are added to the OpenAPI definition, to the `Parameter Object` (in the specification). +### Summary -And for `Body()`, `File()`, and `Form()`, the `example` or `examples` are equivalently added to the OpenAPI definition, to the `Request Body Object`, in the field `content`, on the `Media Type Object` (in the specification). +I used to say I didn't like history that much... and look at me now giving "tech history" lessons. 😅 -On the other hand, there's a newer version of OpenAPI: **3.1.0**, recently released. It is based on the latest JSON Schema and most of the modifications from OpenAPI's custom version of JSON Schema are removed, in exchange of the features from the recent versions of JSON Schema, so all these small differences are reduced. Nevertheless, Swagger UI currently doesn't support OpenAPI 3.1.0, so, for now, it's better to continue using the ideas above. +In short, **upgrade to FastAPI 0.99.0 or above**, and things are much **simpler, consistent, and intuitive**, and you don't have to know all these historic details. 😎 diff --git a/docs/en/docs/tutorial/security/index.md b/docs/en/docs/tutorial/security/index.md index 9aed2adb5..659a94dc3 100644 --- a/docs/en/docs/tutorial/security/index.md +++ b/docs/en/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# Security Intro +# Security There are many ways to handle security, authentication and authorization. @@ -26,7 +26,7 @@ That's what all the systems with "login with Facebook, Google, Twitter, GitHub" ### OAuth 1 -There was an OAuth 1, which is very different from OAuth2, and more complex, as it included directly specifications on how to encrypt the communication. +There was an OAuth 1, which is very different from OAuth2, and more complex, as it included direct specifications on how to encrypt the communication. It is not very popular or used nowadays. diff --git a/docs/en/layouts/custom.yml b/docs/en/layouts/custom.yml new file mode 100644 index 000000000..aad81af28 --- /dev/null +++ b/docs/en/layouts/custom.yml @@ -0,0 +1,228 @@ +# Copyright (c) 2016-2023 Martin Donath + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# ----------------------------------------------------------------------------- +# Configuration +# ----------------------------------------------------------------------------- + +# The same default card with a a configurable logo + +# Definitions +definitions: + + # Background image + - &background_image >- + {{ layout.background_image or "" }} + + # Background color (default: indigo) + - &background_color >- + {%- if layout.background_color -%} + {{ layout.background_color }} + {%- else -%} + {%- set palette = config.theme.palette or {} -%} + {%- if not palette is mapping -%} + {%- set palette = palette | first -%} + {%- endif -%} + {%- set primary = palette.get("primary", "indigo") -%} + {%- set primary = primary.replace(" ", "-") -%} + {{ { + "red": "#ef5552", + "pink": "#e92063", + "purple": "#ab47bd", + "deep-purple": "#7e56c2", + "indigo": "#4051b5", + "blue": "#2094f3", + "light-blue": "#02a6f2", + "cyan": "#00bdd6", + "teal": "#009485", + "green": "#4cae4f", + "light-green": "#8bc34b", + "lime": "#cbdc38", + "yellow": "#ffec3d", + "amber": "#ffc105", + "orange": "#ffa724", + "deep-orange": "#ff6e42", + "brown": "#795649", + "grey": "#757575", + "blue-grey": "#546d78", + "black": "#000000", + "white": "#ffffff" + }[primary] or "#4051b5" }} + {%- endif -%} + + # Text color (default: white) + - &color >- + {%- if layout.color -%} + {{ layout.color }} + {%- else -%} + {%- set palette = config.theme.palette or {} -%} + {%- if not palette is mapping -%} + {%- set palette = palette | first -%} + {%- endif -%} + {%- set primary = palette.get("primary", "indigo") -%} + {%- set primary = primary.replace(" ", "-") -%} + {{ { + "red": "#ffffff", + "pink": "#ffffff", + "purple": "#ffffff", + "deep-purple": "#ffffff", + "indigo": "#ffffff", + "blue": "#ffffff", + "light-blue": "#ffffff", + "cyan": "#ffffff", + "teal": "#ffffff", + "green": "#ffffff", + "light-green": "#ffffff", + "lime": "#000000", + "yellow": "#000000", + "amber": "#000000", + "orange": "#000000", + "deep-orange": "#ffffff", + "brown": "#ffffff", + "grey": "#ffffff", + "blue-grey": "#ffffff", + "black": "#ffffff", + "white": "#000000" + }[primary] or "#ffffff" }} + {%- endif -%} + + # Font family (default: Roboto) + - &font_family >- + {%- if layout.font_family -%} + {{ layout.font_family }} + {%- elif config.theme.font != false -%} + {{ config.theme.font.get("text", "Roboto") }} + {%- else -%} + Roboto + {%- endif -%} + + # Site name + - &site_name >- + {{ config.site_name }} + + # Page title + - &page_title >- + {{ page.meta.get("title", page.title) }} + + # Page title with site name + - &page_title_with_site_name >- + {%- if not page.is_homepage -%} + {{ page.meta.get("title", page.title) }} - {{ config.site_name }} + {%- else -%} + {{ page.meta.get("title", page.title) }} + {%- endif -%} + + # Page description + - &page_description >- + {{ page.meta.get("description", config.site_description) or "" }} + + + # Start of custom modified logic + # Logo + - &logo >- + {%- if layout.logo -%} + {{ layout.logo }} + {%- elif config.theme.logo -%} + {{ config.docs_dir }}/{{ config.theme.logo }} + {%- endif -%} + # End of custom modified logic + + # Logo (icon) + - &logo_icon >- + {{ config.theme.icon.logo or "" }} + +# Meta tags +tags: + + # Open Graph + og:type: website + og:title: *page_title_with_site_name + og:description: *page_description + og:image: "{{ image.url }}" + og:image:type: "{{ image.type }}" + og:image:width: "{{ image.width }}" + og:image:height: "{{ image.height }}" + og:url: "{{ page.canonical_url }}" + + # Twitter + twitter:card: summary_large_image + twitter.title: *page_title_with_site_name + twitter:description: *page_description + twitter:image: "{{ image.url }}" + +# ----------------------------------------------------------------------------- +# Specification +# ----------------------------------------------------------------------------- + +# Card size and layers +size: { width: 1200, height: 630 } +layers: + + # Background + - background: + image: *background_image + color: *background_color + + # Logo + - size: { width: 144, height: 144 } + offset: { x: 992, y: 64 } + background: + image: *logo + icon: + value: *logo_icon + color: *color + + # Site name + - size: { width: 832, height: 42 } + offset: { x: 64, y: 64 } + typography: + content: *site_name + color: *color + font: + family: *font_family + style: Bold + + # Page title + - size: { width: 832, height: 310 } + offset: { x: 62, y: 160 } + typography: + content: *page_title + align: start + color: *color + line: + amount: 3 + height: 1.25 + font: + family: *font_family + style: Bold + + # Page description + - size: { width: 832, height: 64 } + offset: { x: 64, y: 512 } + typography: + content: *page_description + align: start + color: *color + line: + amount: 2 + height: 1.5 + font: + family: *font_family + style: Regular diff --git a/docs/en/mkdocs.insiders.yml b/docs/en/mkdocs.insiders.yml new file mode 100644 index 000000000..d204974b8 --- /dev/null +++ b/docs/en/mkdocs.insiders.yml @@ -0,0 +1,7 @@ +plugins: + social: + cards_layout_dir: ../en/layouts + cards_layout: custom + cards_layout_options: + logo: ../en/docs/img/icon-white.svg + typeset: diff --git a/docs/en/mkdocs.maybe-insiders.yml b/docs/en/mkdocs.maybe-insiders.yml new file mode 100644 index 000000000..37fd9338e --- /dev/null +++ b/docs/en/mkdocs.maybe-insiders.yml @@ -0,0 +1,6 @@ +# Define this here and not in the main mkdocs.yml file because that one is auto +# updated and written, and the script would remove the env var +INHERIT: !ENV [INSIDERS_FILE, '../en/mkdocs.no-insiders.yml'] +markdown_extensions: + pymdownx.highlight: + linenums: !ENV [LINENUMS, false] diff --git a/docs/az/overrides/.gitignore b/docs/en/mkdocs.no-insiders.yml similarity index 100% rename from docs/az/overrides/.gitignore rename to docs/en/mkdocs.no-insiders.yml diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index b7cefee53..030bbe5d3 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -1,9 +1,10 @@ +INHERIT: ../en/mkdocs.maybe-insiders.yml site_name: FastAPI site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production site_url: https://fastapi.tiangolo.com/ theme: name: material - custom_dir: overrides + custom_dir: ../en/overrides palette: - media: '(prefers-color-scheme: light)' scheme: default @@ -11,18 +12,24 @@ theme: accent: amber toggle: icon: material/lightbulb - name: Switch to light mode + name: Switch to dark mode - media: '(prefers-color-scheme: dark)' scheme: slate primary: teal accent: amber toggle: icon: material/lightbulb-outline - name: Switch to dark mode + name: Switch to light mode features: - search.suggest - search.highlight - content.tabs.link + - navigation.indexes + - content.tooltips + - navigation.path + - content.code.annotate + - content.code.copy + - content.code.select icon: repo: fontawesome/brands/github-alt logo: img/icon-white.svg @@ -32,36 +39,26 @@ repo_name: tiangolo/fastapi repo_url: https://github.com/tiangolo/fastapi edit_uri: '' plugins: -- search -- markdownextradata: - data: data + search: null + markdownextradata: + data: ../en/data nav: - FastAPI: index.md - Languages: - en: / - - az: /az/ - - cs: /cs/ - de: /de/ - em: /em/ - es: /es/ - fa: /fa/ - fr: /fr/ - he: /he/ - - hy: /hy/ - id: /id/ - - it: /it/ - ja: /ja/ - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - pl: /pl/ - pt: /pt/ - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - tr: /tr/ - - uk: /uk/ - zh: /zh/ - features.md - fastapi-people.md @@ -150,6 +147,7 @@ nav: - advanced/conditional-openapi.md - advanced/extending-openapi.md - advanced/openapi-callbacks.md + - advanced/openapi-webhooks.md - advanced/wsgi.md - advanced/generate-clients.md - async.md @@ -168,27 +166,28 @@ nav: - external-links.md - benchmarks.md - help-fastapi.md +- newsletter.md - contributing.md - release-notes.md markdown_extensions: -- toc: + toc: permalink: true -- markdown.extensions.codehilite: + markdown.extensions.codehilite: guess_lang: false -- mdx_include: + mdx_include: base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: + admonition: + codehilite: + extra: + pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: + pymdownx.tabbed: alternate_style: true -- attr_list -- md_in_html + attr_list: + md_in_html: extra: analytics: provider: google @@ -211,10 +210,6 @@ extra: alternate: - link: / name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - link: /de/ name: de - link: /em/ @@ -227,36 +222,20 @@ extra: name: fr - français - link: /he/ name: he - - link: /hy/ - name: hy - link: /id/ name: id - - link: /it/ - name: it - italiano - link: /ja/ name: ja - 日本語 - link: /ko/ name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - link: /pl/ name: pl - link: /pt/ name: pt - português - link: /ru/ name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - link: /tr/ name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - link: /zh/ name: zh - 汉语 extra_css: @@ -265,3 +244,5 @@ extra_css: extra_javascript: - js/termynal.js - js/custom.js +hooks: +- ../../scripts/mkdocs_hooks.py diff --git a/docs/es/docs/advanced/index.md b/docs/es/docs/advanced/index.md index 1bee540f2..ba1d20b0d 100644 --- a/docs/es/docs/advanced/index.md +++ b/docs/es/docs/advanced/index.md @@ -1,4 +1,4 @@ -# Guía de Usuario Avanzada - Introducción +# Guía de Usuario Avanzada ## Características Adicionales diff --git a/docs/es/docs/index.md b/docs/es/docs/index.md index 727a6617b..5b75880c0 100644 --- a/docs/es/docs/index.md +++ b/docs/es/docs/index.md @@ -433,7 +433,6 @@ Para entender más al respecto revisa la sección ujson - para "parsing" de JSON más rápido. * email_validator - para validación de emails. Usados por Starlette: diff --git a/docs/es/docs/tutorial/first-steps.md b/docs/es/docs/tutorial/first-steps.md index 110036e8c..efa61f994 100644 --- a/docs/es/docs/tutorial/first-steps.md +++ b/docs/es/docs/tutorial/first-steps.md @@ -181,7 +181,7 @@ $ uvicorn main:my_awesome_api --reload -### Paso 3: crea un *operación de path* +### Paso 3: crea una *operación de path* #### Path diff --git a/docs/es/docs/tutorial/index.md b/docs/es/docs/tutorial/index.md index e3671f381..1cff8b4e3 100644 --- a/docs/es/docs/tutorial/index.md +++ b/docs/es/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Tutorial - Guía de Usuario - Introducción +# Tutorial - Guía de Usuario Este tutorial te muestra cómo usar **FastAPI** con la mayoría de sus características paso a paso. diff --git a/docs/es/mkdocs.yml b/docs/es/mkdocs.yml index 8152c91e3..de18856f4 100644 --- a/docs/es/mkdocs.yml +++ b/docs/es/mkdocs.yml @@ -1,170 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/es/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: es -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- python-types.md -- Tutorial - Guía de Usuario: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md -- Guía de Usuario Avanzada: - - advanced/index.md -- async.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/es/overrides/.gitignore b/docs/es/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/fa/docs/advanced/sub-applications.md b/docs/fa/docs/advanced/sub-applications.md new file mode 100644 index 000000000..f3a948414 --- /dev/null +++ b/docs/fa/docs/advanced/sub-applications.md @@ -0,0 +1,72 @@ +# زیر برنامه ها - اتصال + +اگر نیاز دارید که دو برنامه مستقل FastAPI، با OpenAPI مستقل و رابط‌های کاربری اسناد خود داشته باشید، می‌توانید یک برنامه +اصلی داشته باشید و یک (یا چند) زیر برنامه را به آن متصل کنید. + +## اتصال (mount) به یک برنامه **FastAPI** + +کلمه "Mounting" به معنای افزودن یک برنامه کاملاً مستقل در یک مسیر خاص است، که پس از آن مدیریت همه چیز در آن مسیر، با path operations (عملیات های مسیر) اعلام شده در آن زیر برنامه می باشد. + +### برنامه سطح بالا + +ابتدا برنامه اصلی سطح بالا، **FastAPI** و path operations آن را ایجاد کنید: + + +```Python hl_lines="3 6-8" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### زیر برنامه + +سپس، زیر برنامه خود و path operations آن را ایجاد کنید. + +این زیر برنامه فقط یکی دیگر از برنامه های استاندارد FastAPI است، اما این برنامه ای است که متصل می شود: + +```Python hl_lines="11 14-16" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### اتصال زیر برنامه + +در برنامه سطح بالا `app` اتصال زیر برنامه `subapi` در این نمونه `/subapi` در مسیر قرار میدهد و میشود: + +```Python hl_lines="11 19" +{!../../../docs_src/sub_applications/tutorial001.py!} +``` + +### اسناد API خودکار را بررسی کنید + +برنامه را با استفاده از ‘uvicorn‘ اجرا کنید، اگر فایل شما ‘main.py‘ نام دارد، دستور زیر را وارد کنید: +
+ +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +صفحه مستندات را در آدرس http://127.0.0.1:8000/docs باز کنید. + +اسناد API خودکار برنامه اصلی را مشاهده خواهید کرد که فقط شامل path operations خود می شود: + + + +و سپس اسناد زیر برنامه را در آدرس http://127.0.0.1:8000/subapi/docs. باز کنید. + +اسناد API خودکار برای زیر برنامه را خواهید دید، که فقط شامل path operations خود می شود، همه در زیر مسیر `/subapi` قرار دارند: + + + +اگر سعی کنید با هر یک از این دو رابط کاربری تعامل داشته باشید، آنها به درستی کار می کنند، زیرا مرورگر می تواند با هر یک از برنامه ها یا زیر برنامه های خاص صحبت کند. + +### جرئیات فنی : `root_path` + +هنگامی که یک زیر برنامه را همانطور که در بالا توضیح داده شد متصل می کنید, FastAPI با استفاده از مکانیزمی از مشخصات ASGI به نام `root_path` ارتباط مسیر mount را برای زیر برنامه انجام می دهد. + +به این ترتیب، زیر برنامه می داند که از آن پیشوند مسیر برای رابط کاربری اسناد (docs UI) استفاده کند. + +و زیر برنامه ها نیز می تواند زیر برنامه های متصل شده خود را داشته باشد و همه چیز به درستی کار کند، زیرا FastAPI تمام این مسیرهای `root_path` را به طور خودکار مدیریت می کند. + +در بخش [پشت پراکسی](./behind-a-proxy.md){.internal-link target=_blank}. درباره `root_path` و نحوه استفاده درست از آن بیشتر خواهید آموخت. diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md index ebaa8085a..248084389 100644 --- a/docs/fa/docs/index.md +++ b/docs/fa/docs/index.md @@ -436,7 +436,6 @@ item: Item استفاده شده توسط Pydantic: -* ujson - برای "تجزیه (parse)" سریع‌تر JSON . * email_validator - برای اعتبارسنجی آدرس‌های ایمیل. استفاده شده توسط Starlette: diff --git a/docs/fa/mkdocs.yml b/docs/fa/mkdocs.yml index 2a966f664..de18856f4 100644 --- a/docs/fa/mkdocs.yml +++ b/docs/fa/mkdocs.yml @@ -1,160 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/fa/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: fa -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/fa/overrides/.gitignore b/docs/fa/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/fr/docs/advanced/index.md b/docs/fr/docs/advanced/index.md index 41737889a..f4fa5ecf6 100644 --- a/docs/fr/docs/advanced/index.md +++ b/docs/fr/docs/advanced/index.md @@ -1,4 +1,4 @@ -# Guide de l'utilisateur avancé - Introduction +# Guide de l'utilisateur avancé ## Caractéristiques supplémentaires diff --git a/docs/fr/docs/deployment/index.md b/docs/fr/docs/deployment/index.md index e855adfa3..e2014afe9 100644 --- a/docs/fr/docs/deployment/index.md +++ b/docs/fr/docs/deployment/index.md @@ -1,4 +1,4 @@ -# Déploiement - Intro +# Déploiement Le déploiement d'une application **FastAPI** est relativement simple. diff --git a/docs/fr/docs/index.md b/docs/fr/docs/index.md index 5ee8b462f..7c7547be1 100644 --- a/docs/fr/docs/index.md +++ b/docs/fr/docs/index.md @@ -445,7 +445,6 @@ Pour en savoir plus, consultez la section ujson - pour un "décodage" JSON plus rapide. * email_validator - pour la validation des adresses email. Utilisées par Starlette : diff --git a/docs/fr/mkdocs.yml b/docs/fr/mkdocs.yml index 0b73d3cae..de18856f4 100644 --- a/docs/fr/mkdocs.yml +++ b/docs/fr/mkdocs.yml @@ -1,189 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/fr/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: fr -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -- Tutoriel - Guide utilisateur: - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/body.md - - tutorial/background-tasks.md - - tutorial/debugging.md -- Guide utilisateur avancé: - - advanced/index.md - - advanced/path-operation-advanced-configuration.md - - advanced/additional-status-codes.md - - advanced/response-directly.md - - advanced/additional-responses.md -- async.md -- Déploiement: - - deployment/index.md - - deployment/versions.md - - deployment/https.md - - deployment/deta.md - - deployment/docker.md - - deployment/manually.md -- project-generation.md -- alternatives.md -- history-design-future.md -- external-links.md -- help-fastapi.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/fr/overrides/.gitignore b/docs/fr/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/he/docs/index.md b/docs/he/docs/index.md index 19f2f2041..802dbe8b5 100644 --- a/docs/he/docs/index.md +++ b/docs/he/docs/index.md @@ -440,7 +440,6 @@ item: Item בשימוש Pydantic: -- ujson - "פרסור" JSON. - email_validator - לאימות כתובות אימייל. בשימוש Starlette: diff --git a/docs/he/mkdocs.yml b/docs/he/mkdocs.yml index b8a674812..de18856f4 100644 --- a/docs/he/mkdocs.yml +++ b/docs/he/mkdocs.yml @@ -1,160 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/he/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: he -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/he/overrides/.gitignore b/docs/he/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/hy/docs/index.md b/docs/hy/docs/index.md deleted file mode 100644 index cc82b33cf..000000000 --- a/docs/hy/docs/index.md +++ /dev/null @@ -1,467 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.7+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with Strawberry and other libraries. -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install "fastapi[all]"`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/hy/mkdocs.yml b/docs/hy/mkdocs.yml deleted file mode 100644 index 19d747c31..000000000 --- a/docs/hy/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/hy/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: hy -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/hy/overrides/.gitignore b/docs/hy/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/id/docs/index.md b/docs/id/docs/index.md deleted file mode 100644 index 66fc2859e..000000000 --- a/docs/id/docs/index.md +++ /dev/null @@ -1,466 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```Python -from typing import Optional - -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: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="9 14" -from typing import Optional - -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: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Optional[bool] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* graphene - Required for `GraphQLApp` support. -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install fastapi[all]`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/id/mkdocs.yml b/docs/id/mkdocs.yml index 460cb6914..de18856f4 100644 --- a/docs/id/mkdocs.yml +++ b/docs/id/mkdocs.yml @@ -1,160 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/id/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: id -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/id/overrides/.gitignore b/docs/id/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/it/docs/index.md b/docs/it/docs/index.md deleted file mode 100644 index 9d95dd6d7..000000000 --- a/docs/it/docs/index.md +++ /dev/null @@ -1,463 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Build Status - - - Coverage - - - Package version - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```Python -from fastapi import FastAPI -from typing import Optional - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: str = Optional[None]): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="7 12" -from fastapi import FastAPI -from typing import Optional - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="2 7-10 23-25" -from fastapi import FastAPI -from pydantic import BaseModel -from typing import Optional - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: bool = Optional[None] - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* graphene - Required for `GraphQLApp` support. -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install fastapi[all]`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/it/mkdocs.yml b/docs/it/mkdocs.yml deleted file mode 100644 index b3a48482b..000000000 --- a/docs/it/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/it/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: it -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/it/overrides/.gitignore b/docs/it/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/ja/docs/advanced/index.md b/docs/ja/docs/advanced/index.md index 676f60359..0732fc405 100644 --- a/docs/ja/docs/advanced/index.md +++ b/docs/ja/docs/advanced/index.md @@ -1,4 +1,4 @@ -# ユーザーガイド 応用編 +# 高度なユーザーガイド ## さらなる機能 diff --git a/docs/ja/docs/deployment/index.md b/docs/ja/docs/deployment/index.md index 40710a93a..897956e38 100644 --- a/docs/ja/docs/deployment/index.md +++ b/docs/ja/docs/deployment/index.md @@ -1,4 +1,4 @@ -# デプロイ - イントロ +# デプロイ **FastAPI** 製のアプリケーションは比較的容易にデプロイできます。 diff --git a/docs/ja/docs/index.md b/docs/ja/docs/index.md index f3a159f70..a9c381a23 100644 --- a/docs/ja/docs/index.md +++ b/docs/ja/docs/index.md @@ -431,7 +431,6 @@ item: Item Pydantic によって使用されるもの: -- ujson - より速い JSON への"変換". - email_validator - E メールの検証 Starlette によって使用されるもの: diff --git a/docs/ja/docs/tutorial/index.md b/docs/ja/docs/tutorial/index.md index a2dd59c9b..856cde44b 100644 --- a/docs/ja/docs/tutorial/index.md +++ b/docs/ja/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# チュートリアル - ユーザーガイド - はじめに +# チュートリアル - ユーザーガイド このチュートリアルは**FastAPI**のほぼすべての機能の使い方を段階的に紹介します。 diff --git a/docs/ja/mkdocs.yml b/docs/ja/mkdocs.yml index 91b9a6658..de18856f4 100644 --- a/docs/ja/mkdocs.yml +++ b/docs/ja/mkdocs.yml @@ -1,204 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/ja/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: ja -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- チュートリアル - ユーザーガイド: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/body.md - - tutorial/query-params-str-validations.md - - tutorial/cookie-params.md - - tutorial/header-params.md - - tutorial/request-forms.md - - tutorial/body-updates.md - - セキュリティ: - - tutorial/security/first-steps.md - - tutorial/security/oauth2-jwt.md - - tutorial/middleware.md - - tutorial/cors.md - - tutorial/static-files.md - - tutorial/testing.md - - tutorial/debugging.md -- 高度なユーザーガイド: - - advanced/index.md - - advanced/path-operation-advanced-configuration.md - - advanced/additional-status-codes.md - - advanced/response-directly.md - - advanced/custom-response.md - - advanced/nosql-databases.md - - advanced/websockets.md - - advanced/conditional-openapi.md -- async.md -- デプロイ: - - deployment/index.md - - deployment/versions.md - - deployment/deta.md - - deployment/docker.md - - deployment/manually.md -- project-generation.md -- alternatives.md -- history-design-future.md -- external-links.md -- benchmarks.md -- help-fastapi.md -- contributing.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/ja/overrides/.gitignore b/docs/ja/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md index c64713705..a6991a9b8 100644 --- a/docs/ko/docs/index.md +++ b/docs/ko/docs/index.md @@ -437,7 +437,6 @@ item: Item Pydantic이 사용하는: -* ujson - 더 빠른 JSON "파싱". * email_validator - 이메일 유효성 검사. Starlette이 사용하는: diff --git a/docs/ko/docs/tutorial/index.md b/docs/ko/docs/tutorial/index.md index d6db525e8..deb5ca8f2 100644 --- a/docs/ko/docs/tutorial/index.md +++ b/docs/ko/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# 자습서 - 사용자 안내서 - 도입부 +# 자습서 - 사용자 안내서 이 자습서는 **FastAPI**의 대부분의 기능을 단계별로 사용하는 방법을 보여줍니다. diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml index aec1c7569..de18856f4 100644 --- a/docs/ko/mkdocs.yml +++ b/docs/ko/mkdocs.yml @@ -1,174 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/ko/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: en -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- 자습서 - 사용자 안내서: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/header-params.md - - tutorial/path-params-numeric-validations.md - - tutorial/response-status-code.md - - tutorial/request-files.md - - tutorial/request-forms-and-files.md - - tutorial/encoder.md - - tutorial/cors.md - - 의존성: - - tutorial/dependencies/classes-as-dependencies.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/ko/overrides/.gitignore b/docs/ko/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/lo/docs/index.md b/docs/lo/docs/index.md deleted file mode 100644 index 9a81f14d1..000000000 --- a/docs/lo/docs/index.md +++ /dev/null @@ -1,469 +0,0 @@ -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.7+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._" - -
Deon Pillsbury - Cisco (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.7+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with Strawberry and other libraries. -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install "fastapi[all]"`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/lo/mkdocs.yml b/docs/lo/mkdocs.yml deleted file mode 100644 index 450ebcd2b..000000000 --- a/docs/lo/mkdocs.yml +++ /dev/null @@ -1,157 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/lo/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: en -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/lo/overrides/.gitignore b/docs/lo/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/nl/docs/index.md b/docs/nl/docs/index.md deleted file mode 100644 index 23143a96f..000000000 --- a/docs/nl/docs/index.md +++ /dev/null @@ -1,468 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with Strawberry and other libraries. -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install "fastapi[all]"`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/nl/mkdocs.yml b/docs/nl/mkdocs.yml deleted file mode 100644 index 96c93abff..000000000 --- a/docs/nl/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/nl/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: nl -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/nl/overrides/.gitignore b/docs/nl/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/pl/docs/features.md b/docs/pl/docs/features.md new file mode 100644 index 000000000..49d362dd9 --- /dev/null +++ b/docs/pl/docs/features.md @@ -0,0 +1,200 @@ +# Cechy + +## Cechy FastAPI + +**FastAPI** zapewnia Ci następujące korzyści: + +### Oparcie o standardy open + +* OpenAPI do tworzenia API, w tym deklaracji ścieżek operacji, parametrów, ciał zapytań, bezpieczeństwa, itp. +* Automatyczna dokumentacja modelu danych za pomocą JSON Schema (ponieważ OpenAPI bazuje na JSON Schema). +* Zaprojektowane z myślą o zgodności z powyższymi standardami zamiast dodawania ich obsługi po fakcie. +* Możliwość automatycznego **generowania kodu klienta** w wielu językach. + +### Automatyczna dokumentacja + +Interaktywna dokumentacja i webowe interfejsy do eksploracji API. Z racji tego, że framework bazuje na OpenAPI, istnieje wiele opcji, z czego 2 są domyślnie dołączone. + +* Swagger UI, z interaktywnym interfejsem - odpytuj i testuj swoje API bezpośrednio z przeglądarki. + +![Swagger UI interakcja](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) + +* Alternatywna dokumentacja API z ReDoc. + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) + +### Nowoczesny Python + +Wszystko opiera się na standardowych deklaracjach typu **Python 3.6** (dzięki Pydantic). Brak nowej składni do uczenia. Po prostu standardowy, współczesny Python. + +Jeśli potrzebujesz szybkiego przypomnienia jak używać deklaracji typów w Pythonie (nawet jeśli nie używasz FastAPI), sprawdź krótki samouczek: [Python Types](python-types.md){.internal-link target=_blank}. + +Wystarczy, że napiszesz standardowe deklaracje typów Pythona: + +```Python +from datetime import date + +from pydantic import BaseModel + +# Zadeklaruj parametr jako str +# i uzyskaj wsparcie edytora wewnątrz funkcji +def main(user_id: str): + return user_id + + +# Model Pydantic +class User(BaseModel): + id: int + name: str + joined: date +``` + +A one będą mogły zostać później użyte w następujący sposób: + +```Python +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +!!! info + `**second_user_data` oznacza: + + Przekaż klucze i wartości słownika `second_user_data` bezpośrednio jako argumenty klucz-wartość, co jest równoznaczne z: `User(id=4, name="Mary", joined="2018-11-30")` + +### Wsparcie edytora + +Cały framework został zaprojektowany tak, aby był łatwy i intuicyjny w użyciu. Wszystkie pomysły zostały przetestowane na wielu edytorach jeszcze przed rozpoczęciem procesu tworzenia, aby zapewnić najlepsze wrażenia programistyczne. + +Ostatnia ankieta Python developer survey jasno wskazuje, że najczęściej używaną funkcjonalnością jest autouzupełnianie w edytorze. + +Cała struktura frameworku **FastAPI** jest na tym oparta. Autouzupełnianie działa wszędzie. + +Rzadko będziesz musiał wracać do dokumentacji. + +Oto, jak twój edytor może Ci pomóc: + +* Visual Studio Code: + +![wsparcie edytora](https://fastapi.tiangolo.com/img/vscode-completion.png) + +* PyCharm: + +![wsparcie edytora](https://fastapi.tiangolo.com/img/pycharm-completion.png) + +Otrzymasz uzupełnienie nawet w miejscach, w których normalnie uzupełnienia nie ma. Na przykład klucz "price" w treści JSON (który mógł być zagnieżdżony), który pochodzi z zapytania. + +Koniec z wpisywaniem błędnych nazw kluczy, przechodzeniem tam i z powrotem w dokumentacji lub przewijaniem w górę i w dół, aby sprawdzić, czy w końcu użyłeś nazwy `username` czy `user_name`. + +### Zwięzłość + +Wszystko posiada sensowne **domyślne wartości**. Wszędzie znajdziesz opcjonalne konfiguracje. Wszystkie parametry możesz dostroić, aby zrobić to co potrzebujesz do zdefiniowania API. + +Ale domyślnie wszystko **"po prostu działa"**. + +### Walidacja + +* Walidacja większości (lub wszystkich?) **typów danych** Pythona, w tym: + * Obiektów JSON (`dict`). + * Tablic JSON (`list`) ze zdefiniowanym typem elementów. + * Pól tekstowych (`str`) z określeniem minimalnej i maksymalnej długości. + * Liczb (`int`, `float`) z wartościami minimalnymi, maksymalnymi, itp. + +* Walidacja bardziej egzotycznych typów danych, takich jak: + * URL. + * Email. + * UUID. + * ...i inne. + +Cała walidacja jest obsługiwana przez ugruntowaną i solidną bibliotekę **Pydantic**. + +### Bezpieczeństwo i uwierzytelnianie + +Bezpieczeństwo i uwierzytelnianie jest zintegrowane. Bez żadnych kompromisów z bazami czy modelami danych. + +Wszystkie schematy bezpieczeństwa zdefiniowane w OpenAPI, w tym: + +* Podstawowy protokół HTTP. +* **OAuth2** (również z **tokenami JWT**). Sprawdź samouczek [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* Klucze API w: + * Nagłówkach. + * Parametrach zapytań. + * Ciasteczkach, itp. + +Plus wszystkie funkcje bezpieczeństwa Starlette (włączając w to **ciasteczka sesyjne**). + +Wszystko zbudowane jako narzędzia i komponenty wielokrotnego użytku, które można łatwo zintegrować z systemami, magazynami oraz bazami danych - relacyjnymi, NoSQL, itp. + +### Wstrzykiwanie Zależności + +FastAPI zawiera niezwykle łatwy w użyciu, ale niezwykle potężny system Wstrzykiwania Zależności. + +* Nawet zależności mogą mieć zależności, tworząc hierarchię lub **"graf" zależności**. +* Wszystko jest **obsługiwane automatycznie** przez framework. +* Wszystkie zależności mogą wymagać danych w żądaniach oraz rozszerzać ograniczenia i automatyczną dokumentację **operacji na ścieżce**. +* **Automatyczna walidacja** parametrów *operacji na ścieżce* zdefiniowanych w zależnościach. +* Obsługa złożonych systemów uwierzytelniania użytkowników, **połączeń z bazami danych**, itp. +* Bazy danych, front end, itp. **bez kompromisów**, ale wciąż łatwe do integracji. + +### Nieograniczone "wtyczki" + +Lub ujmując to inaczej - brak potrzeby wtyczek. Importuj i używaj kod, który potrzebujesz. + +Każda integracja została zaprojektowana tak, aby była tak prosta w użyciu (z zależnościami), że możesz utworzyć "wtyczkę" dla swojej aplikacji w 2 liniach kodu, używając tej samej struktury i składni, które są używane w *operacjach na ścieżce*. + +### Testy + +* 100% pokrycia kodu testami. +* 100% adnotacji typów. +* Używany w aplikacjach produkcyjnych. + +## Cechy Starlette + +**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) Starlette. Tak więc każdy dodatkowy kod Starlette, który posiadasz, również będzie działał. + +`FastAPI` jest w rzeczywistości podklasą `Starlette`, więc jeśli już znasz lub używasz Starlette, większość funkcji będzie działać w ten sam sposób. + +Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Starlette** (ponieważ FastAPI to po prostu Starlette na sterydach): + +* Bardzo imponująca wydajność. Jest to jeden z najszybszych dostępnych frameworków Pythona, na równi z **NodeJS** i **Go**. +* Wsparcie dla **WebSocket**. +* Zadania w tle. +* Eventy startup i shutdown. +* Klient testowy zbudowany na bazie biblioteki `requests`. +* **CORS**, GZip, pliki statyczne, streamy. +* Obsługa **sesji i ciasteczek**. +* 100% pokrycie testami. +* 100% adnotacji typów. + +## Cechy Pydantic + +**FastAPI** jest w pełni kompatybilny z (oraz bazuje na) Pydantic. Tak więc każdy dodatkowy kod Pydantic, który posiadasz, również będzie działał. + +Wliczając w to zewnętrzne biblioteki, również oparte o Pydantic, takie jak ORM, ODM dla baz danych. + +Oznacza to, że w wielu przypadkach możesz przekazać ten sam obiekt, który otrzymasz z żądania **bezpośrednio do bazy danych**, ponieważ wszystko jest walidowane automatycznie. + +Działa to również w drugą stronę, w wielu przypadkach możesz po prostu przekazać obiekt otrzymany z bazy danych **bezpośrednio do klienta**. + +Dzięki **FastAPI** otrzymujesz wszystkie funkcje **Pydantic** (ponieważ FastAPI bazuje na Pydantic do obsługi wszystkich danych): + +* **Bez prania mózgu**: + * Brak nowego mikrojęzyka do definiowania schematu, którego trzeba się nauczyć. + * Jeśli znasz adnotacje typów Pythona to wiesz jak używać Pydantic. +* Dobrze współpracuje z Twoim **IDE/linterem/mózgiem**: + * Ponieważ struktury danych Pydantic to po prostu instancje klas, które definiujesz; autouzupełnianie, linting, mypy i twoja intuicja powinny działać poprawnie z Twoimi zwalidowanymi danymi. +* **Szybkość**: + * w benchmarkach Pydantic jest szybszy niż wszystkie inne testowane biblioteki. +* Walidacja **złożonych struktur**: + * Wykorzystanie hierarchicznych modeli Pydantic, Pythonowego modułu `typing` zawierającego `List`, `Dict`, itp. + * Walidatory umożliwiają jasne i łatwe definiowanie, sprawdzanie złożonych struktur danych oraz dokumentowanie ich jako JSON Schema. + * Możesz mieć głęboko **zagnieżdżone obiekty JSON** i wszystkie je poddać walidacji i adnotować. +* **Rozszerzalność**: + * Pydantic umożliwia zdefiniowanie niestandardowych typów danych lub rozszerzenie walidacji o metody na modelu, na których użyty jest dekorator walidatora. +* 100% pokrycie testami. diff --git a/docs/pl/docs/index.md b/docs/pl/docs/index.md index 98e1e82fc..bade7a88c 100644 --- a/docs/pl/docs/index.md +++ b/docs/pl/docs/index.md @@ -435,7 +435,6 @@ Aby dowiedzieć się o tym więcej, zobacz sekcję ujson - dla szybszego "parsowania" danych JSON. * email_validator - dla walidacji adresów email. Używane przez Starlette: diff --git a/docs/pl/docs/tutorial/index.md b/docs/pl/docs/tutorial/index.md index ed8752a95..f8c5c6022 100644 --- a/docs/pl/docs/tutorial/index.md +++ b/docs/pl/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Samouczek - Wprowadzenie +# Samouczek Ten samouczek pokaże Ci, krok po kroku, jak używać większości funkcji **FastAPI**. diff --git a/docs/pl/mkdocs.yml b/docs/pl/mkdocs.yml index 0d7a783fc..de18856f4 100644 --- a/docs/pl/mkdocs.yml +++ b/docs/pl/mkdocs.yml @@ -1,163 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/pl/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: pl -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- Samouczek: - - tutorial/index.md - - tutorial/first-steps.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/pl/overrides/.gitignore b/docs/pl/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/pt/docs/advanced/index.md b/docs/pt/docs/advanced/index.md index d1a57c6d1..7e276f732 100644 --- a/docs/pt/docs/advanced/index.md +++ b/docs/pt/docs/advanced/index.md @@ -1,4 +1,4 @@ -# Guia de Usuário Avançado - Introdução +# Guia de Usuário Avançado ## Recursos Adicionais diff --git a/docs/pt/docs/deployment/index.md b/docs/pt/docs/deployment/index.md index 1ff0e44a0..6b4290d1d 100644 --- a/docs/pt/docs/deployment/index.md +++ b/docs/pt/docs/deployment/index.md @@ -1,4 +1,4 @@ -# Implantação - Introdução +# Implantação A implantação de uma aplicação **FastAPI** é relativamente simples. diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index 76668b4da..591e7f3d4 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -430,7 +430,6 @@ Para entender mais sobre performance, veja a seção ujson - para JSON mais rápido "parsing". * email_validator - para validação de email. Usados por Starlette: diff --git a/docs/pt/docs/tutorial/index.md b/docs/pt/docs/tutorial/index.md index b1abd32bc..5fc0485a0 100644 --- a/docs/pt/docs/tutorial/index.md +++ b/docs/pt/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Tutorial - Guia de Usuário - Introdução +# Tutorial - Guia de Usuário Esse tutorial mostra como usar o **FastAPI** com a maior parte de seus recursos, passo a passo. diff --git a/docs/pt/docs/tutorial/security/index.md b/docs/pt/docs/tutorial/security/index.md index 70f864040..f94a8ab62 100644 --- a/docs/pt/docs/tutorial/security/index.md +++ b/docs/pt/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# Introdução à segurança +# Segurança Há várias formas de lidar segurança, autenticação e autorização. diff --git a/docs/pt/mkdocs.yml b/docs/pt/mkdocs.yml index 023944618..de18856f4 100644 --- a/docs/pt/mkdocs.yml +++ b/docs/pt/mkdocs.yml @@ -1,201 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/pt/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: pt -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- Tutorial - Guia de Usuário: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/body.md - - tutorial/body-multiple-params.md - - tutorial/body-fields.md - - tutorial/body-nested-models.md - - tutorial/extra-data-types.md - - tutorial/extra-models.md - - tutorial/query-params-str-validations.md - - tutorial/path-params-numeric-validations.md - - tutorial/path-operation-configuration.md - - tutorial/cookie-params.md - - tutorial/header-params.md - - tutorial/response-status-code.md - - tutorial/request-forms.md - - tutorial/request-forms-and-files.md - - tutorial/handling-errors.md - - tutorial/encoder.md - - Segurança: - - tutorial/security/index.md - - tutorial/background-tasks.md - - tutorial/static-files.md - - Guia de Usuário Avançado: - - advanced/index.md - - advanced/events.md -- Implantação: - - deployment/index.md - - deployment/versions.md - - deployment/https.md - - deployment/deta.md - - deployment/docker.md -- alternatives.md -- history-design-future.md -- external-links.md -- benchmarks.md -- help-fastapi.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/pt/overrides/.gitignore b/docs/pt/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/ru/docs/deployment/index.md b/docs/ru/docs/deployment/index.md index 4dc4e482e..d214a9d62 100644 --- a/docs/ru/docs/deployment/index.md +++ b/docs/ru/docs/deployment/index.md @@ -1,4 +1,4 @@ -# Развёртывание - Введение +# Развёртывание Развернуть приложение **FastAPI** довольно просто. diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md index 14a6d5a8b..30c32e046 100644 --- a/docs/ru/docs/index.md +++ b/docs/ru/docs/index.md @@ -439,7 +439,6 @@ item: Item Используется Pydantic: -* ujson - для более быстрого JSON "парсинга". * email_validator - для проверки электронной почты. Используется Starlette: diff --git a/docs/ru/docs/tutorial/body-nested-models.md b/docs/ru/docs/tutorial/body-nested-models.md new file mode 100644 index 000000000..6435e316f --- /dev/null +++ b/docs/ru/docs/tutorial/body-nested-models.md @@ -0,0 +1,382 @@ +# Body - Вложенные модели + +С помощью **FastAPI**, вы можете определять, валидировать, документировать и использовать модели произвольной вложенности (благодаря библиотеке Pydantic). + +## Определение полей содержащих списки + +Вы можете определять атрибут как подтип. Например, тип `list` в Python: + +=== "Python 3.10+" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial001.py!} + ``` + +Это приведёт к тому, что обьект `tags` преобразуется в список, несмотря на то что тип его элементов не объявлен. + +## Определение полей содержащих список с определением типов его элементов + +Однако в Python есть способ объявления списков с указанием типов для вложенных элементов: + +### Импортируйте `List` из модуля typing + +В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡 + +Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing` в Python: + +```Python hl_lines="1" +{!> ../../../docs_src/body_nested_models/tutorial002.py!} +``` + +### Объявление `list` с указанием типов для вложенных элементов + +Объявление типов для элементов (внутренних типов) вложенных в такие типы как `list`, `dict`, `tuple`: + +* Если у вас Python версии ниже чем 3.9, импортируйте их аналог из модуля `typing` +* Передайте внутренний(ие) тип(ы) как "параметры типа", используя квадратные скобки: `[` и `]` + +В Python версии 3.9 это будет выглядеть так: + +```Python +my_list: list[str] +``` + +В версиях Python до 3.9 это будет выглядеть так: + +```Python +from typing import List + +my_list: List[str] +``` + +Это всё стандартный синтаксис Python для объявления типов. + +Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами. + +Таким образом, в нашем примере мы можем явно указать тип данных для поля `tags` как "список строк": + +=== "Python 3.10+" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial002_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial002.py!} + ``` + +## Типы множеств + +Но затем мы подумали и поняли, что теги не должны повторяться и, вероятно, они должны быть уникальными строками. + +И в Python есть специальный тип данных для множеств уникальных элементов - `set`. + +Тогда мы может обьявить поле `tags` как множество строк: + +=== "Python 3.10+" + + ```Python hl_lines="12" + {!> ../../../docs_src/body_nested_models/tutorial003_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="14" + {!> ../../../docs_src/body_nested_models/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 14" + {!> ../../../docs_src/body_nested_models/tutorial003.py!} + ``` + +С помощью этого, даже если вы получите запрос с повторяющимися данными, они будут преобразованы в множество уникальных элементов. + +И когда вы выводите эти данные, даже если исходный набор содержал дубликаты, они будут выведены в виде множества уникальных элементов. + +И они также будут соответствующим образом аннотированы / задокументированы. + +## Вложенные Модели + +У каждого атрибута Pydantic-модели есть тип. + +Но этот тип может сам быть другой моделью Pydantic. + +Таким образом вы можете объявлять глубоко вложенные JSON "объекты" с определёнными именами атрибутов, типами и валидацией. + +Всё это может быть произвольно вложенным. + +### Определение подмодели + +Например, мы можем определить модель `Image`: + +=== "Python 3.10+" + + ```Python hl_lines="7-9" + {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9-11" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-11" + {!> ../../../docs_src/body_nested_models/tutorial004.py!} + ``` + +### Использование вложенной модели в качестве типа + +Также мы можем использовать эту модель как тип атрибута: + +=== "Python 3.10+" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_nested_models/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial004.py!} + ``` + +Это означает, что **FastAPI** будет ожидать тело запроса, аналогичное этому: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите: + +* Поддержку редакторов IDE (автодополнение и т.д), даже для вложенных моделей +* Преобразование данных +* Валидацию данных +* Автоматическую документацию + +## Особые типы и валидация + +Помимо обычных простых типов, таких как `str`, `int`, `float`, и т.д. Вы можете использовать более сложные базовые типы, которые наследуются от типа `str`. + +Чтобы увидеть все варианты, которые у вас есть, ознакомьтесь с документацией по необычным типам Pydantic. Вы увидите некоторые примеры в следующей главе. + +Например, так как в модели `Image` у нас есть поле `url`, то мы можем объявить его как тип `HttpUrl` из модуля Pydantic вместо типа `str`: + +=== "Python 3.10+" + + ```Python hl_lines="2 8" + {!> ../../../docs_src/body_nested_models/tutorial005_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="4 10" + {!> ../../../docs_src/body_nested_models/tutorial005_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="4 10" + {!> ../../../docs_src/body_nested_models/tutorial005.py!} + ``` + +Строка будет проверена на соответствие допустимому URL-адресу и задокументирована в JSON схему / OpenAPI. + +## Атрибуты, содержащие списки подмоделей + +Вы также можете использовать модели Pydantic в качестве типов вложенных в `list`, `set` и т.д: + +=== "Python 3.10+" + + ```Python hl_lines="18" + {!> ../../../docs_src/body_nested_models/tutorial006_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial006_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20" + {!> ../../../docs_src/body_nested_models/tutorial006.py!} + ``` + +Такая реализация будет ожидать (конвертировать, валидировать, документировать и т.д) JSON-содержимое в следующем формате: + +```JSON hl_lines="11" +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +!!! info "Информация" + Заметьте, что теперь у ключа `images` есть список объектов изображений. + +## Глубоко вложенные модели + +Вы можете определять модели с произвольным уровнем вложенности: + +=== "Python 3.10+" + + ```Python hl_lines="7 12 18 21 25" + {!> ../../../docs_src/body_nested_models/tutorial007_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="9 14 20 23 27" + {!> ../../../docs_src/body_nested_models/tutorial007_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 14 20 23 27" + {!> ../../../docs_src/body_nested_models/tutorial007.py!} + ``` + +!!! info "Информация" + Заметьте, что у объекта `Offer` есть список объектов `Item`, которые, в свою очередь, могут содержать необязательный список объектов `Image` + +## Тела с чистыми списками элементов + +Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python - `list`), вы можете объявить тип в параметре функции, так же, как в моделях Pydantic: + +```Python +images: List[Image] +``` + +в Python 3.9 и выше: + +```Python +images: list[Image] +``` + +например так: + +=== "Python 3.9+" + + ```Python hl_lines="13" + {!> ../../../docs_src/body_nested_models/tutorial008_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="15" + {!> ../../../docs_src/body_nested_models/tutorial008.py!} + ``` + +## Универсальная поддержка редактора + +И вы получаете поддержку редактора везде. + +Даже для элементов внутри списков: + + + +Вы не могли бы получить такую поддержку редактора, если бы работали напрямую с `dict`, а не с моделями Pydantic. + +Но вы также не должны беспокоиться об этом, входящие словари автоматически конвертируются, а ваш вывод также автоматически преобразуется в формат JSON. + +## Тела запросов с произвольными словарями (`dict` ) + +Вы также можете объявить тело запроса как `dict` с ключами определенного типа и значениями другого типа данных. + +Без необходимости знать заранее, какие значения являются допустимыми для имён полей/атрибутов (как это было бы в случае с моделями Pydantic). + +Это было бы полезно, если вы хотите получить ключи, которые вы еще не знаете. + +--- + +Другой полезный случай - когда вы хотите чтобы ключи были другого типа данных, например, `int`. + +Именно это мы сейчас и увидим здесь. + +В этом случае вы принимаете `dict`, пока у него есть ключи типа `int` со значениями типа `float`: + +=== "Python 3.9+" + + ```Python hl_lines="7" + {!> ../../../docs_src/body_nested_models/tutorial009_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/body_nested_models/tutorial009.py!} + ``` + +!!! tip "Совет" + Имейте в виду, что JSON поддерживает только ключи типа `str`. + + Но Pydantic обеспечивает автоматическое преобразование данных. + + Это значит, что даже если пользователи вашего API могут отправлять только строки в качестве ключей, при условии, что эти строки содержат целые числа, Pydantic автоматический преобразует и валидирует эти данные. + + А `dict`, с именем `weights`, который вы получите в качестве ответа Pydantic, действительно будет иметь ключи типа `int` и значения типа `float`. + +## Резюме + +С помощью **FastAPI** вы получаете максимальную гибкость, предоставляемую моделями Pydantic, сохраняя при этом простоту, краткость и элегантность вашего кода. + +И дополнительно вы получаете: + +* Поддержку редактора (автодополнение доступно везде!) +* Преобразование данных (также известно как парсинг / сериализация) +* Валидацию данных +* Документацию схемы данных +* Автоматическую генерацию документации diff --git a/docs/ru/docs/tutorial/cors.md b/docs/ru/docs/tutorial/cors.md new file mode 100644 index 000000000..8c7fbc046 --- /dev/null +++ b/docs/ru/docs/tutorial/cors.md @@ -0,0 +1,84 @@ +# CORS (Cross-Origin Resource Sharing) + +Понятие CORS или "Cross-Origin Resource Sharing" относится к ситуациям, при которых запущенный в браузере фронтенд содержит JavaScript-код, который взаимодействует с бэкендом, находящимся на другом "источнике" ("origin"). + +## Источник + +Источник - это совокупность протокола (`http`, `https`), домена (`myapp.com`, `localhost`, `localhost.tiangolo.com`) и порта (`80`, `443`, `8080`). + +Поэтому это три разных источника: + +* `http://localhost` +* `https://localhost` +* `http://localhost:8080` + +Даже если они все расположены в `localhost`, они используют разные протоколы и порты, а значит, являются разными источниками. + +## Шаги + +Допустим, у вас есть фронтенд, запущенный в браузере по адресу `http://localhost:8080`, и его JavaScript-код пытается взаимодействовать с бэкендом, запущенным по адресу `http://localhost` (поскольку мы не указали порт, браузер по умолчанию будет использовать порт `80`). + +Затем браузер отправит бэкенду HTTP-запрос `OPTIONS`, и если бэкенд вернёт соответствующие заголовки для авторизации взаимодействия с другим источником (`http://localhost:8080`), то браузер разрешит JavaScript-коду на фронтенде отправить запрос на этот бэкенд. + +Чтобы это работало, у бэкенда должен быть список "разрешённых источников" ("allowed origins"). + +В таком случае этот список должен содержать `http://localhost:8080`, чтобы фронтенд работал корректно. + +## Подстановочный символ `"*"` + +В качестве списка источников можно указать подстановочный символ `"*"` ("wildcard"), чтобы разрешить любые источники. + +Но тогда не будут разрешены некоторые виды взаимодействия, включая всё связанное с учётными данными: куки, заголовки Authorization с Bearer-токенами наподобие тех, которые мы использовали ранее и т.п. + +Поэтому, чтобы всё работало корректно, лучше явно указывать список разрешённых источников. + +## Использование `CORSMiddleware` + +Вы можете настроить этот механизм в вашем **FastAPI** приложении, используя `CORSMiddleware`. + +* Импортируйте `CORSMiddleware`. +* Создайте список разрешённых источников (в виде строк). +* Добавьте его как "middleware" к вашему **FastAPI** приложению. + +Вы также можете указать, разрешает ли ваш бэкенд использование: + +* Учётных данных (включая заголовки Authorization, куки и т.п.). +* Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`. +* Отдельных HTTP-заголовков или всех вместе, используя `"*"`. + +```Python hl_lines="2 6-11 13-19" +{!../../../docs_src/cors/tutorial001.py!} +``` + +`CORSMiddleware` использует для параметров "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте. + +Поддерживаются следующие аргументы: + +* `allow_origins` - Список источников, на которые разрешено выполнять кросс-доменные запросы. Например, `['https://example.org', 'https://www.example.org']`. Можно использовать `['*']`, чтобы разрешить любые источники. +* `allow_origin_regex` - Регулярное выражение для определения источников, на которые разрешено выполнять кросс-доменные запросы. Например, `'https://.*\.example\.org'`. +* `allow_methods` - Список HTTP-методов, которые разрешены для кросс-доменных запросов. По умолчанию равно `['GET']`. Можно использовать `['*']`, чтобы разрешить все стандартные методы. +* `allow_headers` - Список HTTP-заголовков, которые должны поддерживаться при кросс-доменных запросах. По умолчанию равно `[]`. Можно использовать `['*']`, чтобы разрешить все заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` и `Content-Type` всегда разрешены для простых CORS-запросов. +* `allow_credentials` - указывает, что куки разрешены в кросс-доменных запросах. По умолчанию равно `False`. Также, `allow_origins` нельзя присвоить `['*']`, если разрешено использование учётных данных. В таком случае должен быть указан список источников. +* `expose_headers` - Указывает любые заголовки ответа, которые должны быть доступны браузеру. По умолчанию равно `[]`. +* `max_age` - Устанавливает максимальное время в секундах, в течение которого браузер кэширует CORS-ответы. По умолчанию равно `600`. + +`CORSMiddleware` отвечает на два типа HTTP-запросов... + +### CORS-запросы с предварительной проверкой + +Это любые `OPTIONS` запросы с заголовками `Origin` и `Access-Control-Request-Method`. + +В этом случае middleware перехватит входящий запрос и отправит соответствующие CORS-заголовки в ответе, а также ответ `200` или `400` в информационных целях. + +### Простые запросы + +Любые запросы с заголовком `Origin`. В этом случае middleware передаст запрос дальше как обычно, но добавит соответствующие CORS-заголовки к ответу. + +## Больше информации + +Для получения более подробной информации о CORS, обратитесь к Документации CORS от Mozilla. + +!!! note "Технические детали" + Вы также можете использовать `from starlette.middleware.cors import CORSMiddleware`. + + **FastAPI** предоставляет несколько middleware в `fastapi.middleware` только для вашего удобства как разработчика. Но большинство доступных middleware взяты напрямую из Starlette. diff --git a/docs/ru/docs/tutorial/extra-models.md b/docs/ru/docs/tutorial/extra-models.md new file mode 100644 index 000000000..a346f7432 --- /dev/null +++ b/docs/ru/docs/tutorial/extra-models.md @@ -0,0 +1,252 @@ +# Дополнительные модели + +В продолжение прошлого примера будет уже обычным делом иметь несколько связанных между собой моделей. + +Это особенно применимо в случае моделей пользователя, потому что: + +* **Модель для ввода** должна иметь возможность содержать пароль. +* **Модель для вывода** не должна содержать пароль. +* **Модель для базы данных**, возможно, должна содержать хэшированный пароль. + +!!! danger "Внимание" + Никогда не храните пароли пользователей в чистом виде. Всегда храните "безопасный хэш", который вы затем сможете проверить. + + Если вам это не знакомо, вы можете узнать про "хэш пароля" в [главах о безопасности](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. + +## Множественные модели + +Ниже изложена основная идея того, как могут выглядеть эти модели с полями для паролей, а также описаны места, где они используются: + +=== "Python 3.10+" + + ```Python hl_lines="7 9 14 20 22 27-28 31-33 38-39" + {!> ../../../docs_src/extra_models/tutorial001_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 11 16 22 24 29-30 33-35 40-41" + {!> ../../../docs_src/extra_models/tutorial001.py!} + ``` + +### Про `**user_in.dict()` + +#### `.dict()` из Pydantic + +`user_in` - это Pydantic-модель класса `UserIn`. + +У Pydantic-моделей есть метод `.dict()`, который возвращает `dict` с данными модели. + +Поэтому, если мы создадим Pydantic-объект `user_in` таким способом: + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +и затем вызовем: + +```Python +user_dict = user_in.dict() +``` + +то теперь у нас есть `dict` с данными модели в переменной `user_dict` (это `dict` вместо объекта Pydantic-модели). + +И если мы вызовем: + +```Python +print(user_dict) +``` + +мы можем получить `dict` с такими данными: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### Распаковка `dict` + +Если мы возьмём `dict` наподобие `user_dict` и передадим его в функцию (или класс), используя `**user_dict`, Python распакует его. Он передаст ключи и значения `user_dict` напрямую как аргументы типа ключ-значение. + +Поэтому, продолжая описанный выше пример с `user_dict`, написание такого кода: + +```Python +UserInDB(**user_dict) +``` + +Будет работать так же, как примерно такой код: + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +Или, если для большей точности мы напрямую используем `user_dict` с любым потенциальным содержимым, то этот пример будет выглядеть так: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### Pydantic-модель из содержимого другой модели + +Как в примере выше мы получили `user_dict` из `user_in.dict()`, этот код: + +```Python +user_dict = user_in.dict() +UserInDB(**user_dict) +``` + +будет равнозначен такому: + +```Python +UserInDB(**user_in.dict()) +``` + +...потому что `user_in.dict()` - это `dict`, и затем мы указываем, чтобы Python его "распаковал", когда передаём его в `UserInDB` и ставим перед ним `**`. + +Таким образом мы получаем Pydantic-модель на основе данных из другой Pydantic-модели. + +#### Распаковка `dict` и дополнительные именованные аргументы + +И затем, если мы добавим дополнительный именованный аргумент `hashed_password=hashed_password` как здесь: + +```Python +UserInDB(**user_in.dict(), hashed_password=hashed_password) +``` + +... то мы получим что-то подобное: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +!!! warning "Предупреждение" + Цель использованных в примере вспомогательных функций - не более чем демонстрация возможных операций с данными, но, конечно, они не обеспечивают настоящую безопасность. + +## Сократите дублирование + +Сокращение дублирования кода - это одна из главных идей **FastAPI**. + +Поскольку дублирование кода повышает риск появления багов, проблем с безопасностью, проблем десинхронизации кода (когда вы обновляете код в одном месте, но не обновляете в другом), и т.д. + +А все описанные выше модели используют много общих данных и дублируют названия атрибутов и типов. + +Мы можем это улучшить. + +Мы можем определить модель `UserBase`, которая будет базовой для остальных моделей. И затем мы можем создать подклассы этой модели, которые будут наследовать её атрибуты (объявления типов, валидацию, и т.п.). + +Все операции конвертации, валидации, документации, и т.п. будут по-прежнему работать нормально. + +В этом случае мы можем определить только различия между моделями (с `password` в чистом виде, с `hashed_password` и без пароля): + +=== "Python 3.10+" + + ```Python hl_lines="7 13-14 17-18 21-22" + {!> ../../../docs_src/extra_models/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 15-16 19-20 23-24" + {!> ../../../docs_src/extra_models/tutorial002.py!} + ``` + +## `Union` или `anyOf` + +Вы можете определить ответ как `Union` из двух типов. Это означает, что ответ должен соответствовать одному из них. + +Он будет определён в OpenAPI как `anyOf`. + +Для этого используйте стандартные аннотации типов в Python `typing.Union`: + +!!! note "Примечание" + При объявлении `Union`, сначала указывайте наиболее детальные типы, затем менее детальные. В примере ниже более детальный `PlaneItem` стоит перед `CarItem` в `Union[PlaneItem, CarItem]`. + +=== "Python 3.10+" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 14-15 18-20 33" + {!> ../../../docs_src/extra_models/tutorial003.py!} + ``` + +### `Union` в Python 3.10 + +В этом примере мы передаём `Union[PlaneItem, CarItem]` в качестве значения аргумента `response_model`. + +Поскольку мы передаём его как **значение аргумента** вместо того, чтобы поместить его в **аннотацию типа**, нам придётся использовать `Union` даже в Python 3.10. + +Если оно было бы указано в аннотации типа, то мы могли бы использовать вертикальную черту как в примере: + +```Python +some_variable: PlaneItem | CarItem +``` + +Но если мы помещаем его в `response_model=PlaneItem | CarItem` мы получим ошибку, потому что Python попытается произвести **некорректную операцию** между `PlaneItem` и `CarItem` вместо того, чтобы интерпретировать это как аннотацию типа. + +## Список моделей + +Таким же образом вы можете определять ответы как списки объектов. + +Для этого используйте `typing.List` из стандартной библиотеки Python (или просто `list` в Python 3.9 и выше): + +=== "Python 3.9+" + + ```Python hl_lines="18" + {!> ../../../docs_src/extra_models/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 20" + {!> ../../../docs_src/extra_models/tutorial004.py!} + ``` + +## Ответ с произвольным `dict` + +Вы также можете определить ответ, используя произвольный одноуровневый `dict` и определяя только типы ключей и значений без использования Pydantic-моделей. + +Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели). + +В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше): + +=== "Python 3.9+" + + ```Python hl_lines="6" + {!> ../../../docs_src/extra_models/tutorial005_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 8" + {!> ../../../docs_src/extra_models/tutorial005.py!} + ``` + +## Резюме + +Используйте несколько Pydantic-моделей и свободно применяйте наследование для каждой из них. + +Вам не обязательно иметь единственную модель данных для каждой сущности, если эта сущность должна иметь возможность быть в разных "состояниях". Как в случае с "сущностью" пользователя, у которого есть состояния с полями `password`, `password_hash` и без пароля. diff --git a/docs/ru/docs/tutorial/index.md b/docs/ru/docs/tutorial/index.md index 4277a6c4f..ea3a1c37a 100644 --- a/docs/ru/docs/tutorial/index.md +++ b/docs/ru/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Учебник - Руководство пользователя - Введение +# Учебник - Руководство пользователя В этом руководстве шаг за шагом показано, как использовать **FastApi** с большинством его функций. diff --git a/docs/ru/docs/tutorial/metadata.md b/docs/ru/docs/tutorial/metadata.md new file mode 100644 index 000000000..331c96734 --- /dev/null +++ b/docs/ru/docs/tutorial/metadata.md @@ -0,0 +1,111 @@ +# URL-адреса метаданных и документации + +Вы можете настроить несколько конфигураций метаданных в вашем **FastAPI** приложении. + +## Метаданные для API + +Вы можете задать следующие поля, которые используются в спецификации OpenAPI и в UI автоматической документации API: + +| Параметр | Тип | Описание | +|------------|--|-------------| +| `title` | `str` | Заголовок API. | +| `description` | `str` | Краткое описание API. Может быть использован Markdown. | +| `version` | `string` | Версия API. Версия вашего собственного приложения, а не OpenAPI. К примеру `2.5.0`. | +| `terms_of_service` | `str` | Ссылка к условиям пользования API. Если указано, то это должен быть URL-адрес. | +| `contact` | `dict` | Контактная информация для открытого API. Может содержать несколько полей.
поля contact
ПараметрТипОписание
namestrИдентификационное имя контактного лица/организации.
urlstrURL указывающий на контактную информацию. ДОЛЖЕН быть в формате URL.
emailstrEmail адрес контактного лица/организации. ДОЛЖЕН быть в формате email адреса.
| +| `license_info` | `dict` | Информация о лицензии открытого API. Может содержать несколько полей.
поля license_info
ПараметрТипОписание
namestrОБЯЗАТЕЛЬНО (если установлен параметр license_info). Название лицензии, используемой для API
urlstrURL, указывающий на лицензию, используемую для API. ДОЛЖЕН быть в формате URL.
| + +Вы можете задать их следующим образом: + +```Python hl_lines="3-16 19-31" +{!../../../docs_src/metadata/tutorial001.py!} +``` + +!!! tip "Подсказка" + Вы можете использовать Markdown в поле `description`, и оно будет отображено в выводе. + +С этой конфигурацией автоматическая документация API будут выглядеть так: + + + +## Метаданные для тегов + +Вы также можете добавить дополнительные метаданные для различных тегов, используемых для группировки ваших операций пути с помощью параметра `openapi_tags`. + +Он принимает список, содержащий один словарь для каждого тега. + +Каждый словарь может содержать в себе: + +* `name` (**обязательно**): `str`-значение с тем же именем тега, которое вы используете в параметре `tags` в ваших *операциях пути* и `APIRouter`ах. +* `description`: `str`-значение с кратким описанием для тега. Может содержать Markdown и будет отображаться в UI документации. +* `externalDocs`: `dict`-значение описывающее внешнюю документацию. Включает в себя: + * `description`: `str`-значение с кратким описанием для внешней документации. + * `url` (**обязательно**): `str`-значение с URL-адресом для внешней документации. + +### Создание метаданных для тегов + +Давайте попробуем сделать это на примере с тегами для `users` и `items`. + +Создайте метаданные для ваших тегов и передайте их в параметре `openapi_tags`: + +```Python hl_lines="3-16 18" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +Помните, что вы можете использовать Markdown внутри описания, к примеру "login" будет отображен жирным шрифтом (**login**) и "fancy" будет отображаться курсивом (_fancy_). + +!!! tip "Подсказка" + Вам необязательно добавлять метаданные для всех используемых тегов + +### Используйте собственные теги +Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги: + +```Python hl_lines="21 26" +{!../../../docs_src/metadata/tutorial004.py!} +``` + +!!! info "Дополнительная информация" + Узнайте больше о тегах в [Конфигурации операции пути](../path-operation-configuration/#tags){.internal-link target=_blank}. + +### Проверьте документацию + +Теперь, если вы проверите документацию, вы увидите всю дополнительную информацию: + + + +### Порядок расположения тегов + +Порядок расположения словарей метаданных для каждого тега определяет также порядок, отображаемый в документах UI + +К примеру, несмотря на то, что `users` будут идти после `items` в алфавитном порядке, они отображаются раньше, потому что мы добавляем свои метаданные в качестве первого словаря в списке. + +## URL-адреса OpenAPI + +По умолчанию схема OpenAPI отображена по адресу `/openapi.json`. + +Но вы можете изменить это с помощью параметра `openapi_url`. + +К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial002.py!} +``` + +Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые его использует. + +## URL-адреса документации + +Вы можете изменить конфигурацию двух пользовательских интерфейсов документации, среди которых + +* **Swagger UI**: отображаемый по адресу `/docs`. + * Вы можете задать его URL с помощью параметра `docs_url`. + * Вы можете отключить это с помощью настройки `docs_url=None`. +* **ReDoc**: отображаемый по адресу `/redoc`. + * Вы можете задать его URL с помощью параметра `redoc_url`. + * Вы можете отключить это с помощью настройки `redoc_url=None`. + +К примеру, чтобы задать отображение Swagger UI по адресу `/documentation` и отключить ReDoc: + +```Python hl_lines="3" +{!../../../docs_src/metadata/tutorial003.py!} +``` diff --git a/docs/ru/docs/tutorial/path-operation-configuration.md b/docs/ru/docs/tutorial/path-operation-configuration.md new file mode 100644 index 000000000..013903add --- /dev/null +++ b/docs/ru/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,179 @@ +# Конфигурация операций пути + +Существует несколько параметров, которые вы можете передать вашему *декоратору операций пути* для его настройки. + +!!! warning "Внимание" + Помните, что эти параметры передаются непосредственно *декоратору операций пути*, а не вашей *функции-обработчику операций пути*. + +## Коды состояния + +Вы можете определить (HTTP) `status_code`, который будет использован в ответах вашей *операции пути*. + +Вы можете передать только `int`-значение кода, например `404`. + +Но если вы не помните, для чего нужен каждый числовой код, вы можете использовать сокращенные константы в параметре `status`: + +=== "Python 3.10+" + + ```Python hl_lines="1 15" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="3 17" + {!> ../../../docs_src/path_operation_configuration/tutorial001.py!} + ``` + +Этот код состояния будет использован в ответе и будет добавлен в схему OpenAPI. + +!!! note "Технические детали" + Вы также можете использовать `from starlette import status`. + + **FastAPI** предоставляет тот же `starlette.status` под псевдонимом `fastapi.status` для удобства разработчика. Но его источник - это непосредственно Starlette. + +## Теги + +Вы можете добавлять теги к вашим *операциям пути*, добавив параметр `tags` с `list` заполненным `str`-значениями (обычно в нём только одна строка): + +=== "Python 3.10+" + + ```Python hl_lines="15 20 25" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17 22 27" + {!> ../../../docs_src/path_operation_configuration/tutorial002.py!} + ``` + +Они будут добавлены в схему OpenAPI и будут использованы в автоматической документации интерфейса: + + + +### Теги с перечислениями + +Если у вас большое приложение, вы можете прийти к необходимости добавить **несколько тегов**, и возможно, вы захотите убедиться в том, что всегда используете **один и тот же тег** для связанных *операций пути*. + +В этих случаях, имеет смысл хранить теги в классе `Enum`. + +**FastAPI** поддерживает это так же, как и в случае с обычными строками: + +```Python hl_lines="1 8-10 13 18" +{!../../../docs_src/path_operation_configuration/tutorial002b.py!} +``` + +## Краткое и развёрнутое содержание + +Вы можете добавить параметры `summary` и `description`: + +=== "Python 3.10+" + + ```Python hl_lines="18-19" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="20-21" + {!> ../../../docs_src/path_operation_configuration/tutorial003.py!} + ``` + +## Описание из строк документации + +Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции строки документации и **FastAPI** прочитает её отсюда. + +Вы можете использовать Markdown в строке документации, и он будет интерпретирован и отображён корректно (с учетом отступа в строке документации). + +=== "Python 3.10+" + + ```Python hl_lines="17-25" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="19-27" + {!> ../../../docs_src/path_operation_configuration/tutorial004.py!} + ``` + +Он будет использован в интерактивной документации: + + + +## Описание ответа + +Вы можете указать описание ответа с помощью параметра `response_description`: + +=== "Python 3.10+" + + ```Python hl_lines="19" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="21" + {!> ../../../docs_src/path_operation_configuration/tutorial005.py!} + ``` + +!!! info "Дополнительная информация" + Помните, что `response_description` относится конкретно к ответу, а `description` относится к *операции пути* в целом. + +!!! check "Технические детали" + OpenAPI указывает, что каждой *операции пути* необходимо описание ответа. + + Если вдруг вы не укажете его, то **FastAPI** автоматически сгенерирует это описание с текстом "Successful response". + + + +## Обозначение *операции пути* как устаревшей + +Если вам необходимо пометить *операцию пути* как устаревшую, при этом не удаляя её, передайте параметр `deprecated`: + +```Python hl_lines="16" +{!../../../docs_src/path_operation_configuration/tutorial006.py!} +``` + +Он будет четко помечен как устаревший в интерактивной документации: + + + +Проверьте, как будут выглядеть устаревшие и не устаревшие *операции пути*: + + + +## Резюме + +Вы можете легко конфигурировать и добавлять метаданные в ваши *операции пути*, передавая параметры *декораторам операций пути*. diff --git a/docs/ru/docs/tutorial/response-model.md b/docs/ru/docs/tutorial/response-model.md new file mode 100644 index 000000000..c5e111790 --- /dev/null +++ b/docs/ru/docs/tutorial/response-model.md @@ -0,0 +1,480 @@ +# Модель ответа - Возвращаемый тип + +Вы можете объявить тип ответа, указав аннотацию **возвращаемого значения** для *функции операции пути*. + +FastAPI позволяет использовать **аннотации типов** таким же способом, как и для ввода данных в **параметры** функции, вы можете использовать модели Pydantic, списки, словари, скалярные типы (такие, как int, bool и т.д.). + +=== "Python 3.10+" + + ```Python hl_lines="16 21" + {!> ../../../docs_src/response_model/tutorial001_01_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="18 23" + {!> ../../../docs_src/response_model/tutorial001_01_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18 23" + {!> ../../../docs_src/response_model/tutorial001_01.py!} + ``` + +FastAPI будет использовать этот возвращаемый тип для: + +* **Валидации** ответа. + * Если данные невалидны (например, отсутствует одно из полей), это означает, что код *вашего* приложения работает некорректно и функция возвращает не то, что вы ожидаете. В таком случае приложение вернет server error вместо того, чтобы отправить неправильные данные. Таким образом, вы и ваши пользователи можете быть уверены, что получите корректные данные в том виде, в котором они ожидаются. +* Добавьте **JSON схему** для ответа внутри *операции пути* OpenAPI. + * Она будет использована для **автоматически генерируемой документации**. + * А также - для автоматической кодогенерации пользователями. + +Но самое важное: + +* Ответ будет **ограничен и отфильтрован** - т.е. в нем останутся только те данные, которые определены в возвращаемом типе. + * Это особенно важно для **безопасности**, далее мы рассмотрим эту тему подробнее. + +## Параметр `response_model` + +Бывают случаи, когда вам необходимо (или просто хочется) возвращать данные, которые не полностью соответствуют объявленному типу. + +Допустим, вы хотите, чтобы ваша функция **возвращала словарь (dict)** или объект из базы данных, но при этом **объявляете выходной тип как модель Pydantic**. Тогда именно указанная модель будет использована для автоматической документации, валидации и т.п. для объекта, который вы вернули (например, словаря или объекта из базы данных). + +Но если указать аннотацию возвращаемого типа, статическая проверка типов будет выдавать ошибку (абсолютно корректную в данном случае). Она будет говорить о том, что ваша функция должна возвращать данные одного типа (например, dict), а в аннотации вы объявили другой тип (например, модель Pydantic). + +В таком случае можно использовать параметр `response_model` внутри *декоратора операции пути* вместо аннотации возвращаемого значения функции. + +Параметр `response_model` может быть указан для любой *операции пути*: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* и др. + +=== "Python 3.10+" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17 22 24-27" + {!> ../../../docs_src/response_model/tutorial001.py!} + ``` + +!!! note "Технические детали" + Помните, что параметр `response_model` является параметром именно декоратора http-методов (`get`, `post`, и т.п.). Не следует его указывать для *функций операций пути*, как вы бы поступили с другими параметрами или с телом запроса. + +`response_model` принимает те же типы, которые можно указать для какого-либо поля в модели Pydantic. Таким образом, это может быть как одиночная модель Pydantic, так и `список (list)` моделей Pydantic. Например, `List[Item]`. + +FastAPI будет использовать значение `response_model` для того, чтобы автоматически генерировать документацию, производить валидацию и т.п. А также для **конвертации и фильтрации выходных данных** в объявленный тип. + +!!! tip "Подсказка" + Если вы используете анализаторы типов со строгой проверкой (например, mypy), можно указать `Any` в качестве типа возвращаемого значения функции. + + Таким образом вы информируете ваш редактор кода, что намеренно возвращаете данные неопределенного типа. Но возможности FastAPI, такие как автоматическая генерация документации, валидация, фильтрация и т.д. все так же будут работать, просто используя параметр `response_model`. + +### Приоритет `response_model` + +Если одновременно указать аннотацию типа для ответа функции и параметр `response_model` - последний будет иметь больший приоритет и FastAPI будет использовать именно его. + +Таким образом вы можете объявить корректные аннотации типов к вашим функциям, даже если они возвращают тип, отличающийся от указанного в `response_model`. Они будут считаны во время статической проверки типов вашими помощниками, например, mypy. При этом вы все так же используете возможности FastAPI для автоматической документации, валидации и т.д. благодаря `response_model`. + +Вы можете указать значение `response_model=None`, чтобы отключить создание модели ответа для данной *операции пути*. Это может понадобиться, если вы добавляете аннотации типов для данных, не являющихся валидными полями Pydantic. Мы увидим пример кода для такого случая в одном из разделов ниже. + +## Получить и вернуть один и тот же тип данных + +Здесь мы объявили модель `UserIn`, которая хранит пользовательский пароль в открытом виде: + +=== "Python 3.10+" + + ```Python hl_lines="7 9" + {!> ../../../docs_src/response_model/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 11" + {!> ../../../docs_src/response_model/tutorial002.py!} + ``` + +!!! info "Информация" + Чтобы использовать `EmailStr`, прежде необходимо установить `email_validator`. + Используйте `pip install email-validator` + или `pip install pydantic[email]`. + +Далее мы используем нашу модель в аннотациях типа как для аргумента функции, так и для выходного значения: + +=== "Python 3.10+" + + ```Python hl_lines="16" + {!> ../../../docs_src/response_model/tutorial002_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="18" + {!> ../../../docs_src/response_model/tutorial002.py!} + ``` + +Теперь всякий раз, когда клиент создает пользователя с паролем, API будет возвращать его пароль в ответе. + +В данном случае это не такая уж большая проблема, поскольку ответ получит тот же самый пользователь, который и создал пароль. + +Но что если мы захотим использовать эту модель для какой-либо другой *операции пути*? Мы можем, сами того не желая, отправить пароль любому другому пользователю. + +!!! danger "Осторожно" + Никогда не храните пароли пользователей в открытом виде, а также никогда не возвращайте их в ответе, как в примере выше. В противном случае - убедитесь, что вы хорошо продумали и учли все возможные риски такого подхода и вам известно, что вы делаете. + +## Создание модели для ответа + +Вместо этого мы можем создать входную модель, хранящую пароль в открытом виде и выходную модель без пароля: + +=== "Python 3.10+" + + ```Python hl_lines="9 11 16" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9 11 16" + {!> ../../../docs_src/response_model/tutorial003.py!} + ``` + +В таком случае, даже несмотря на то, что наша *функция операции пути* возвращает тот же самый объект пользователя с паролем, полученным на вход: + +=== "Python 3.10+" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial003.py!} + ``` + +...мы указали в `response_model` модель `UserOut`, в которой отсутствует поле, содержащее пароль - и он будет исключен из ответа: + +=== "Python 3.10+" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial003_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial003.py!} + ``` + +Таким образом **FastAPI** позаботится о фильтрации ответа и исключит из него всё, что не указано в выходной модели (при помощи Pydantic). + +### `response_model` или возвращаемый тип данных + +В нашем примере модели входных данных и выходных данных различаются. И если мы укажем аннотацию типа выходного значения функции как `UserOut` - проверка типов выдаст ошибку из-за того, что мы возвращаем некорректный тип. Поскольку это 2 разных класса. + +Поэтому в нашем примере мы можем объявить тип ответа только в параметре `response_model`. + +...но продолжайте читать дальше, чтобы узнать как можно это обойти. + +## Возвращаемый тип и Фильтрация данных + +Продолжим рассматривать предыдущий пример. Мы хотели **аннотировать входные данные одним типом**, а выходное значение - **другим типом**. + +Мы хотим, чтобы FastAPI продолжал **фильтровать** данные, используя `response_model`. + +В прошлом примере, т.к. входной и выходной типы являлись разными классами, мы были вынуждены использовать параметр `response_model`. И как следствие, мы лишались помощи статических анализаторов для проверки ответа функции. + +Но в подавляющем большинстве случаев мы будем хотеть, чтобы модель ответа лишь **фильтровала/удаляла** некоторые данные из ответа, как в нашем примере. + +И в таких случаях мы можем использовать классы и наследование, чтобы пользоваться преимуществами **аннотаций типов** и получать более полную статическую проверку типов. Но при этом все так же получать **фильтрацию ответа** от FastAPI. + +=== "Python 3.10+" + + ```Python hl_lines="7-10 13-14 18" + {!> ../../../docs_src/response_model/tutorial003_01_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9-13 15-16 20" + {!> ../../../docs_src/response_model/tutorial003_01.py!} + ``` + +Таким образом, мы получаем поддержку редактора кода и mypy в части типов, сохраняя при этом фильтрацию данных от FastAPI. + +Как это возможно? Давайте разберемся. 🤓 + +### Аннотации типов и инструменты для их проверки + +Для начала давайте рассмотрим как наш редактор кода, mypy и другие помощники разработчика видят аннотации типов. + +У модели `BaseUser` есть некоторые поля. Затем `UserIn` наследуется от `BaseUser` и добавляет новое поле `password`. Таким образом модель будет включать в себя все поля из первой модели (родителя), а также свои собственные. + +Мы аннотируем возвращаемый тип функции как `BaseUser`, но фактически мы будем возвращать объект типа `UserIn`. + +Редакторы, mypy и другие инструменты не будут иметь возражений против такого подхода, поскольку `UserIn` является подклассом `BaseUser`. Это означает, что такой тип будет *корректным*, т.к. ответ может быть чем угодно, если это будет `BaseUser`. + +### Фильтрация Данных FastAPI + +FastAPI знает тип ответа функции, так что вы можете быть уверены, что на выходе будут **только** те поля, которые вы указали. + +FastAPI совместно с Pydantic выполнит некоторую магию "под капотом", чтобы убедиться, что те же самые правила наследования классов не используются для фильтрации возвращаемых данных, в противном случае вы могли бы в конечном итоге вернуть гораздо больше данных, чем ожидали. + +Таким образом, вы можете получить все самое лучшее из обоих миров: аннотации типов с **поддержкой инструментов для разработки** и **фильтрацию данных**. + +## Автоматическая документация + +Если посмотреть на сгенерированную документацию, вы можете убедиться, что в ней присутствуют обе JSON схемы - как для входной модели, так и для выходной: + + + +И также обе модели будут использованы в интерактивной документации API: + + + +## Другие аннотации типов + +Бывают случаи, когда вы возвращаете что-то, что не является валидным типом для Pydantic и вы указываете аннотацию ответа функции только для того, чтобы работала поддержка различных инструментов (редактор кода, mypy и др.). + +### Возвращаем Response + +Самый частый сценарий использования - это [возвращать Response напрямую, как описано в расширенной документации](../advanced/response-directly.md){.internal-link target=_blank}. + +```Python hl_lines="8 10-11" +{!> ../../../docs_src/response_model/tutorial003_02.py!} +``` + +Это поддерживается FastAPI по-умолчанию, т.к. аннотация проставлена в классе (или подклассе) `Response`. + +И ваши помощники разработки также будут счастливы, т.к. оба класса `RedirectResponse` и `JSONResponse` являются подклассами `Response`. Таким образом мы получаем корректную аннотацию типа. + +### Подкласс Response в аннотации типа + +Вы также можете указать подкласс `Response` в аннотации типа: + +```Python hl_lines="8-9" +{!> ../../../docs_src/response_model/tutorial003_03.py!} +``` + +Это сработает, потому что `RedirectResponse` является подклассом `Response` и FastAPI автоматически обработает этот простейший случай. + +### Некорректные аннотации типов + +Но когда вы возвращаете какой-либо другой произвольный объект, который не является допустимым типом Pydantic (например, объект из базы данных), и вы аннотируете его подобным образом для функции, FastAPI попытается создать из этого типа модель Pydantic и потерпит неудачу. + +То же самое произошло бы, если бы у вас было что-то вроде Union различных типов и один или несколько из них не являлись бы допустимыми типами для Pydantic. Например, такой вариант приведет к ошибке 💥: + +=== "Python 3.10+" + + ```Python hl_lines="8" + {!> ../../../docs_src/response_model/tutorial003_04_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="10" + {!> ../../../docs_src/response_model/tutorial003_04.py!} + ``` + +...такой код вызовет ошибку, потому что в аннотации указан неподдерживаемый Pydantic тип. А также этот тип не является классом или подклассом `Response`. + +### Возможно ли отключить генерацию модели ответа? + +Продолжим рассматривать предыдущий пример. Допустим, что вы хотите отказаться от автоматической валидации ответа, документации, фильтрации и т.д. + +Но в то же время, хотите сохранить аннотацию возвращаемого типа для функции, чтобы обеспечить работу помощников и анализаторов типов (например, mypy). + +В таком случае, вы можете отключить генерацию модели ответа, указав `response_model=None`: + +=== "Python 3.10+" + + ```Python hl_lines="7" + {!> ../../../docs_src/response_model/tutorial003_05_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="9" + {!> ../../../docs_src/response_model/tutorial003_05.py!} + ``` + +Тогда FastAPI не станет генерировать модель ответа и вы сможете сохранить такую аннотацию типа, которая вам требуется, никак не влияя на работу FastAPI. 🤓 + +## Параметры модели ответа + +Модель ответа может иметь значения по умолчанию, например: + +=== "Python 3.10+" + + ```Python hl_lines="9 11-12" + {!> ../../../docs_src/response_model/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="11 13-14" + {!> ../../../docs_src/response_model/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="11 13-14" + {!> ../../../docs_src/response_model/tutorial004.py!} + ``` + +* `description: Union[str, None] = None` (или `str | None = None` в Python 3.10), где `None` является значением по умолчанию. +* `tax: float = 10.5`, где `10.5` является значением по умолчанию. +* `tags: List[str] = []`, где пустой список `[]` является значением по умолчанию. + +но вы, возможно, хотели бы исключить их из ответа, если данные поля не были заданы явно. + +Например, у вас есть модель с множеством необязательных полей в NoSQL базе данных, но вы не хотите отправлять в качестве ответа очень длинный JSON с множеством значений по умолчанию. + +### Используйте параметр `response_model_exclude_unset` + +Установите для *декоратора операции пути* параметр `response_model_exclude_unset=True`: + +=== "Python 3.10+" + + ```Python hl_lines="22" + {!> ../../../docs_src/response_model/tutorial004_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial004_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="24" + {!> ../../../docs_src/response_model/tutorial004.py!} + ``` + +и тогда значения по умолчанию не будут включены в ответ. В нем будут только те поля, значения которых фактически были установлены. + +Итак, если вы отправите запрос на данную *операцию пути* для элемента, с ID = `Foo` - ответ (с исключенными значениями по-умолчанию) будет таким: + +```JSON +{ + "name": "Foo", + "price": 50.2 +} +``` + +!!! info "Информация" + "Под капотом" FastAPI использует метод `.dict()` у объектов моделей Pydantic с параметром `exclude_unset`, чтобы достичь такого эффекта. + +!!! info "Информация" + Вы также можете использовать: + + * `response_model_exclude_defaults=True` + * `response_model_exclude_none=True` + + как описано в документации Pydantic для параметров `exclude_defaults` и `exclude_none`. + +#### Если значение поля отличается от значения по-умолчанию + +Если для некоторых полей модели, имеющих значения по-умолчанию, значения были явно установлены - как для элемента с ID = `Bar`, ответ будет таким: + +```Python hl_lines="3 5" +{ + "name": "Bar", + "description": "The bartenders", + "price": 62, + "tax": 20.2 +} +``` + +они не будут исключены из ответа. + +#### Если значение поля совпадает с его значением по умолчанию + +Если данные содержат те же значения, которые являются для этих полей по умолчанию, но были установлены явно - как для элемента с ID = `baz`, ответ будет таким: + +```Python hl_lines="3 5-6" +{ + "name": "Baz", + "description": None, + "price": 50.2, + "tax": 10.5, + "tags": [] +} +``` + +FastAPI достаточно умен (на самом деле, это заслуга Pydantic), чтобы понять, что, хотя `description`, `tax` и `tags` хранят такие же данные, какие должны быть по умолчанию - для них эти значения были установлены явно (а не получены из значений по умолчанию). + +И поэтому, они также будут включены в JSON ответа. + +!!! tip "Подсказка" + Значением по умолчанию может быть что угодно, не только `None`. + + Им может быть и список (`[]`), значение 10.5 типа `float`, и т.п. + +### `response_model_include` и `response_model_exclude` + +Вы также можете использовать параметры *декоратора операции пути*, такие, как `response_model_include` и `response_model_exclude`. + +Они принимают аргументы типа `set`, состоящий из строк (`str`) с названиями атрибутов, которые либо требуется включить в ответ (при этом исключив все остальные), либо наоборот исключить (оставив в ответе все остальные поля). + +Это можно использовать как быстрый способ исключить данные из ответа, не создавая отдельную модель Pydantic. + +!!! tip "Подсказка" + Но по-прежнему рекомендуется следовать изложенным выше советам и использовать несколько моделей вместо данных параметров. + + Потому как JSON схема OpenAPI, генерируемая вашим приложением (а также документация) все еще будет содержать все поля, даже если вы использовали `response_model_include` или `response_model_exclude` и исключили некоторые атрибуты. + + То же самое применимо к параметру `response_model_by_alias`. + +=== "Python 3.10+" + + ```Python hl_lines="29 35" + {!> ../../../docs_src/response_model/tutorial005_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="31 37" + {!> ../../../docs_src/response_model/tutorial005.py!} + ``` + +!!! tip "Подсказка" + При помощи кода `{"name","description"}` создается объект множества (`set`) с двумя строковыми значениями. + + Того же самого можно достичь используя `set(["name", "description"])`. + +#### Что если использовать `list` вместо `set`? + +Если вы забыли про `set` и использовали структуру `list` или `tuple`, FastAPI автоматически преобразует этот объект в `set`, чтобы обеспечить корректную работу: + +=== "Python 3.10+" + + ```Python hl_lines="29 35" + {!> ../../../docs_src/response_model/tutorial006_py310.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="31 37" + {!> ../../../docs_src/response_model/tutorial006.py!} + ``` + +## Резюме + +Используйте параметр `response_model` у *декоратора операции пути* для того, чтобы задать модель ответа и в большей степени для того, чтобы быть уверенным, что приватная информация будет отфильтрована. + +А также используйте `response_model_exclude_unset`, чтобы возвращать только те значения, которые были заданы явно. diff --git a/docs/ru/mkdocs.yml b/docs/ru/mkdocs.yml index 9fb56ce1b..de18856f4 100644 --- a/docs/ru/mkdocs.yml +++ b/docs/ru/mkdocs.yml @@ -1,194 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/ru/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: ru -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -- Учебник - руководство пользователя: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params-str-validations.md - - tutorial/path-params-numeric-validations.md - - tutorial/body-fields.md - - tutorial/background-tasks.md - - tutorial/extra-data-types.md - - tutorial/cookie-params.md - - tutorial/testing.md - - tutorial/response-status-code.md - - tutorial/query-params.md - - tutorial/body-multiple-params.md - - tutorial/static-files.md - - tutorial/debugging.md - - tutorial/schema-extra-example.md -- async.md -- Развёртывание: - - deployment/index.md - - deployment/versions.md - - deployment/concepts.md - - deployment/https.md - - deployment/manually.md -- project-generation.md -- alternatives.md -- history-design-future.md -- external-links.md -- benchmarks.md -- help-fastapi.md -- contributing.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/ru/overrides/.gitignore b/docs/ru/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/sq/docs/index.md b/docs/sq/docs/index.md deleted file mode 100644 index cff2c2804..000000000 --- a/docs/sq/docs/index.md +++ /dev/null @@ -1,466 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* graphene - Required for `GraphQLApp` support. -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install fastapi[all]`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/sq/mkdocs.yml b/docs/sq/mkdocs.yml deleted file mode 100644 index 092c50816..000000000 --- a/docs/sq/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/sq/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: en -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/sq/overrides/.gitignore b/docs/sq/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/sv/docs/index.md b/docs/sv/docs/index.md deleted file mode 100644 index 23143a96f..000000000 --- a/docs/sv/docs/index.md +++ /dev/null @@ -1,468 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with Strawberry and other libraries. -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install "fastapi[all]"`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/sv/mkdocs.yml b/docs/sv/mkdocs.yml deleted file mode 100644 index 215b32f18..000000000 --- a/docs/sv/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/sv/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: sv -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/sv/overrides/.gitignore b/docs/sv/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/ta/mkdocs.yml b/docs/ta/mkdocs.yml deleted file mode 100644 index 4b96d2cad..000000000 --- a/docs/ta/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/ta/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: en -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/ta/overrides/.gitignore b/docs/ta/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/tr/docs/index.md b/docs/tr/docs/index.md index 6bd30d709..e74efbc2f 100644 --- a/docs/tr/docs/index.md +++ b/docs/tr/docs/index.md @@ -1,7 +1,3 @@ - -{!../../../docs/missing-translation.md!} - -

FastAPI

@@ -449,7 +445,6 @@ Daha fazla bilgi için, bu bölüme bir göz at ujson - daha hızlı JSON "dönüşümü" için. * email_validator - email doğrulaması için. Starlette tarafında kullanılan: diff --git a/docs/tr/mkdocs.yml b/docs/tr/mkdocs.yml index 5811f793e..de18856f4 100644 --- a/docs/tr/mkdocs.yml +++ b/docs/tr/mkdocs.yml @@ -1,165 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/tr/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: tr -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -- Tutorial - User Guide: - - tutorial/first-steps.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/tr/overrides/.gitignore b/docs/tr/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md deleted file mode 100644 index cff2c2804..000000000 --- a/docs/uk/docs/index.md +++ /dev/null @@ -1,466 +0,0 @@ - -{!../../../docs/missing-translation.md!} - - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - -

- ---- - -**Documentation**: https://fastapi.tiangolo.com - -**Source Code**: https://github.com/tiangolo/fastapi - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). - -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema. - -* estimation based on tests on an internal development team, building production applications. - -## Sponsors - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Other sponsors - -## Opinions - -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_I’m over the moon excited about **FastAPI**. It’s so fun!_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted **Hug** to be - it's really inspiring to see someone build that._" - -
Timothy Crosley - Hug creator (ref)
- ---- - -"_If you're looking to learn one **modern framework** for building REST APIs, check out **FastAPI** [...] It's fast, easy to use and easy to learn [...]_" - -"_We've switched over to **FastAPI** for our **APIs** [...] I think you'll like it [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out **Typer**. - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -Python 3.7+ - -FastAPI stands on the shoulders of giants: - -* Starlette for the web parts. -* Pydantic for the data parts. - -## Installation - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -You will also need an ASGI server, for production such as Uvicorn or Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Example - -### Create it - -* Create a file `main.py` with: - -```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} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `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} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about `async` and `await` in the docs. - -
- -### Run it - -Run the server with: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command uvicorn main:app --reload... - -The command `uvicorn main:app` refers to: - -* `main`: the file `main.py` (the Python "module"). -* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. -* `--reload`: make the server restart after code changes. Only do this for development. - -
- -### Check it - -Open your browser at http://127.0.0.1:8000/items/5?q=somequery. - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to http://127.0.0.1:8000/docs. - -You will see the automatic interactive API documentation (provided by Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to http://127.0.0.1:8000/redoc. - -You will see the alternative automatic documentation (provided by ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to 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} -``` - -The server should reload automatically (because you added `--reload` to the `uvicorn` command above). - -### Interactive API docs upgrade - -Now go to http://127.0.0.1:8000/docs. - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to http://127.0.0.1:8000/redoc. - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python 3.6+**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, Read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* Many extra features (thanks to Starlette) as: - * **WebSockets** - * **GraphQL** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as one of the fastest Python frameworks available, only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section Benchmarks. - -## Optional Dependencies - -Used by Pydantic: - -* ujson - for faster JSON "parsing". -* email_validator - for email validation. - -Used by Starlette: - -* httpx - Required if you want to use the `TestClient`. -* jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. -* itsdangerous - Required for `SessionMiddleware` support. -* pyyaml - Required for Starlette's `SchemaGenerator` support (you probably don't need it with FastAPI). -* graphene - Required for `GraphQLApp` support. -* ujson - Required if you want to use `UJSONResponse`. - -Used by FastAPI / Starlette: - -* uvicorn - for the server that loads and serves your application. -* orjson - Required if you want to use `ORJSONResponse`. - -You can install all of these with `pip install fastapi[all]`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/docs/uk/mkdocs.yml b/docs/uk/mkdocs.yml deleted file mode 100644 index 5e22570b1..000000000 --- a/docs/uk/mkdocs.yml +++ /dev/null @@ -1,160 +0,0 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/uk/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: uk -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js diff --git a/docs/uk/overrides/.gitignore b/docs/uk/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/zh/docs/advanced/index.md b/docs/zh/docs/advanced/index.md index d71838cd7..824f91f47 100644 --- a/docs/zh/docs/advanced/index.md +++ b/docs/zh/docs/advanced/index.md @@ -1,4 +1,4 @@ -# 高级用户指南 - 简介 +# 高级用户指南 ## 额外特性 diff --git a/docs/zh/docs/advanced/security/index.md b/docs/zh/docs/advanced/security/index.md new file mode 100644 index 000000000..fdc8075c7 --- /dev/null +++ b/docs/zh/docs/advanced/security/index.md @@ -0,0 +1,16 @@ +# 高级安全 + +## 附加特性 + +除 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性. + +!!! tip "小贴士" + 接下来的章节 **并不一定是 "高级的"**. + + 而且对于你的使用场景来说,解决方案很可能就在其中。 + +## 先阅读教程 + +接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/){.internal-link target=_blank}. + +它们都基于相同的概念,但支持一些额外的功能. diff --git a/docs/zh/docs/advanced/settings.md b/docs/zh/docs/advanced/settings.md new file mode 100644 index 000000000..597e99a77 --- /dev/null +++ b/docs/zh/docs/advanced/settings.md @@ -0,0 +1,433 @@ +# 设置和环境变量 + +在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。 + +这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。 + +因此,通常会将它们提供为由应用程序读取的环境变量。 + +## 环境变量 + +!!! tip + 如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。 + +环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。 + +您可以在 shell 中创建和使用环境变量,而无需使用 Python: + +=== "Linux、macOS、Windows Bash" + +
+ + ```console + // 您可以创建一个名为 MY_NAME 的环境变量 + $ export MY_NAME="Wade Wilson" + + // 然后您可以与其他程序一起使用它,例如 + $ echo "Hello $MY_NAME" + + Hello Wade Wilson + ``` + +
+ +=== "Windows PowerShell" + +
+ + ```console + // 创建一个名为 MY_NAME 的环境变量 + $ $Env:MY_NAME = "Wade Wilson" + + // 与其他程序一起使用它,例如 + $ echo "Hello $Env:MY_NAME" + + Hello Wade Wilson + ``` + +
+ +### 在 Python 中读取环境变量 + +您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。 + +例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +!!! tip + `os.getenv()` 的第二个参数是要返回的默认值。 + + 如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。 + +然后,您可以调用该 Python 程序: + +
+ +```console +// 这里我们还没有设置环境变量 +$ python main.py + +// 因为我们没有设置环境变量,所以我们得到默认值 + +Hello World from Python + +// 但是如果我们先创建一个环境变量 +$ export MY_NAME="Wade Wilson" + +// 然后再次调用程序 +$ python main.py + +// 现在它可以读取环境变量 + +Hello Wade Wilson from Python +``` + +
+ +由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。 + + + +您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。 + +要做到这一点,在程序本身之前的同一行创建它: + +
+ +```console +// 在此程序调用行中创建一个名为 MY_NAME 的环境变量 +$ MY_NAME="Wade Wilson" python main.py + +// 现在它可以读取环境变量 + +Hello Wade Wilson from Python + +// 之后环境变量不再存在 +$ python main.py + +Hello World from Python +``` + +
+ +!!! tip + 您可以在 Twelve-Factor App: Config 中阅读更多相关信息。 + +### 类型和验证 + +这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。 + +这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。 + +## Pydantic 的 `Settings` + +幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即Pydantic: Settings management。 + +### 创建 `Settings` 对象 + +从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。 + +与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。 + +您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。 + +```Python hl_lines="2 5-8 11" +{!../../../docs_src/settings/tutorial001.py!} +``` + +!!! tip + 如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。 + +然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。 + +然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。 + +### 使用 `settings` + +然后,您可以在应用程序中使用新的 `settings` 对象: + +```Python hl_lines="18-20" +{!../../../docs_src/settings/tutorial001.py!} +``` + +### 运行服务器 + +接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +!!! tip + 要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。 + +然后,`admin_email` 设置将为 `"deadpool@example.com"`。 + +`app_name` 将为 `"ChimichangApp"`。 + +而 `items_per_user` 将保持其默认值为 `50`。 + +## 在另一个模块中设置 + +您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。 + +例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容: + +```Python +{!../../../docs_src/settings/app01/config.py!} +``` + +然后在一个名为 `main.py` 的文件中使用它: + +```Python hl_lines="3 11-13" +{!../../../docs_src/settings/app01/main.py!} +``` +!!! tip + 您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 + +## 在依赖项中使用设置 + +在某些情况下,从依赖项中提供设置可能比在所有地方都使用全局对象 `settings` 更有用。 + +这在测试期间尤其有用,因为很容易用自定义设置覆盖依赖项。 + +### 配置文件 + +根据前面的示例,您的 `config.py` 文件可能如下所示: + +```Python hl_lines="10" +{!../../../docs_src/settings/app02/config.py!} +``` + +请注意,现在我们不创建默认实例 `settings = Settings()`。 + +### 主应用程序文件 + +现在我们创建一个依赖项,返回一个新的 `config.Settings()`。 + +=== "Python 3.9+" + + ```Python hl_lines="6 12-13" + {!> ../../../docs_src/settings/app02_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="6 12-13" + {!> ../../../docs_src/settings/app02_an/main.py!} + ``` + +=== "Python 3.6+ 非注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="5 11-12" + {!> ../../../docs_src/settings/app02/main.py!} + ``` + +!!! tip + 我们稍后会讨论 `@lru_cache()`。 + + 目前,您可以将 `get_settings()` 视为普通函数。 + +然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。 + +=== "Python 3.9+" + + ```Python hl_lines="17 19-21" + {!> ../../../docs_src/settings/app02_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="17 19-21" + {!> ../../../docs_src/settings/app02_an/main.py!} + ``` + +=== "Python 3.6+ 非注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="16 18-20" + {!> ../../../docs_src/settings/app02/main.py!} + ``` + +### 设置和测试 + +然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象: + +```Python hl_lines="9-10 13 21" +{!../../../docs_src/settings/app02/test_main.py!} +``` + +在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。 + +然后,我们可以测试它是否被使用。 + +## 从 `.env` 文件中读取设置 + +如果您有许多可能经常更改的设置,可能在不同的环境中,将它们放在一个文件中,然后从该文件中读取它们,就像它们是环境变量一样,可能非常有用。 + +这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。 + +!!! tip + 以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。 + + 但是,dotenv 文件实际上不一定要具有确切的文件名。 + +Pydantic 支持使用外部库从这些类型的文件中读取。您可以在Pydantic 设置: Dotenv (.env) 支持中阅读更多相关信息。 + +!!! tip + 要使其工作,您需要执行 `pip install python-dotenv`。 + +### `.env` 文件 + +您可以使用以下内容创建一个名为 `.env` 的文件: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### 从 `.env` 文件中读取设置 + +然后,您可以使用以下方式更新您的 `config.py`: + +```Python hl_lines="9-10" +{!../../../docs_src/settings/app03/config.py!} +``` + +在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。 + +!!! tip + `Config` 类仅用于 Pydantic 配置。您可以在Pydantic Model Config中阅读更多相关信息。 + +### 使用 `lru_cache` 仅创建一次 `Settings` + +从磁盘中读取文件通常是一项耗时的(慢)操作,因此您可能希望仅在首次读取后并重复使用相同的设置对象,而不是为每个请求都读取它。 + +但是,每次执行以下操作: + +```Python +Settings() +``` + +都会创建一个新的 `Settings` 对象,并且在创建时会再次读取 `.env` 文件。 + +如果依赖项函数只是这样的: + +```Python +def get_settings(): + return Settings() +``` + +我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️ + +但是,由于我们在顶部使用了 `@lru_cache()` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️ + +=== "Python 3.9+" + + ```Python hl_lines="1 11" + {!> ../../../docs_src/settings/app03_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="1 11" + {!> ../../../docs_src/settings/app03_an/main.py!} + ``` + +=== "Python 3.6+ 非注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="1 10" + {!> ../../../docs_src/settings/app03/main.py!} + ``` + +然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。 + +#### `lru_cache` 技术细节 + +`@lru_cache()` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。 + +因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。 + +例如,如果您有一个函数: +```Python +@lru_cache() +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +您的程序可以像这样执行: + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: 执行函数代码 + execute ->> code: 返回结果 + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: 返回存储的结果 + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: 执行函数代码 + execute ->> code: 返回结果 + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: 执行函数代码 + execute ->> code: 返回结果 + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: 返回存储的结果 + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: 返回存储的结果 + end +``` + +对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。 + +这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。 + +`@lru_cache()` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在Python 文档中了解有关 `@lru_cache()` 的更多信息。 + +## 小结 + +您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。 + +* 通过使用依赖项,您可以简化测试。 +* 您可以使用 `.env` 文件。 +* 使用 `@lru_cache()` 可以避免为每个请求重复读取 dotenv 文件,同时允许您在测试时进行覆盖。 diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md new file mode 100644 index 000000000..a723487fd --- /dev/null +++ b/docs/zh/docs/advanced/websockets.md @@ -0,0 +1,214 @@ +# WebSockets + +您可以在 **FastAPI** 中使用 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)。 + +## 安装 `WebSockets` + +首先,您需要安装 `WebSockets`: + +```console +$ pip install websockets + +---> 100% +``` + +## WebSockets 客户端 + +### 在生产环境中 + +在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。 + +要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。 + +或者,您可能有一个原生移动应用程序,直接使用原生代码与 WebSocket 后端通信。 + +或者,您可能有其他与 WebSocket 终端通信的方式。 + +--- + +但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。 + +当然,这并不是最优的做法,您不应该在生产环境中使用它。 + +在生产环境中,您应该选择上述任一选项。 + +但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: + +```Python hl_lines="2 6-38 41-43" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +## 创建 `websocket` + +在您的 **FastAPI** 应用程序中,创建一个 `websocket`: + +```Python hl_lines="1 46-47" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +!!! note "技术细节" + 您也可以使用 `from starlette.websockets import WebSocket`。 + + **FastAPI** 直接提供了相同的 `WebSocket`,只是为了方便开发人员。但它直接来自 Starlette。 + +## 等待消息并发送消息 + +在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 + +```Python hl_lines="48-52" +{!../../../docs_src/websockets/tutorial001.py!} +``` + +您可以接收和发送二进制、文本和 JSON 数据。 + +## 尝试一下 + +如果您的文件名为 `main.py`,请使用以下命令运行应用程序: + +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +在浏览器中打开 http://127.0.0.1:8000。 + +您将看到一个简单的页面,如下所示: + + + +您可以在输入框中输入消息并发送: + + + +您的 **FastAPI** 应用程序将回复: + + + +您可以发送(和接收)多条消息: + + + +所有这些消息都将使用同一个 WebSocket 连 + +接。 + +## 使用 `Depends` 和其他依赖项 + +在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同: + +=== "Python 3.10+" + + ```Python hl_lines="68-69 82" + {!> ../../../docs_src/websockets/tutorial002_an_py310.py!} + ``` + +=== "Python 3.9+" + + ```Python hl_lines="68-69 82" + {!> ../../../docs_src/websockets/tutorial002_an_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="69-70 83" + {!> ../../../docs_src/websockets/tutorial002_an.py!} + ``` + +=== "Python 3.10+ 非带注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="66-67 79" + {!> ../../../docs_src/websockets/tutorial002_py310.py!} + ``` + +=== "Python 3.6+ 非带注解版本" + + !!! tip + 如果可能,请尽量使用 `Annotated` 版本。 + + ```Python hl_lines="68-69 81" + {!> ../../../docs_src/websockets/tutorial002.py!} + ``` + +!!! info + 由于这是一个 WebSocket,抛出 `HTTPException` 并不是很合理,而是抛出 `WebSocketException`。 + + 您可以使用规范中定义的有效代码。 + +### 尝试带有依赖项的 WebSockets + +如果您的文件名为 `main.py`,请使用以下命令运行应用程序: + +```console +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +在浏览器中打开 http://127.0.0.1:8000。 + +在页面中,您可以设置: + +* "Item ID",用于路径。 +* "Token",作为查询参数。 + +!!! tip + 注意,查询参数 `token` 将由依赖项处理。 + +通过这样,您可以连接 WebSocket,然后发送和接收消息: + + + +## 处理断开连接和多个客户端 + +当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 + +=== "Python 3.9+" + + ```Python hl_lines="79-81" + {!> ../../../docs_src/websockets/tutorial003_py39.py!} + ``` + +=== "Python 3.6+" + + ```Python hl_lines="81-83" + {!> ../../../docs_src/websockets/tutorial003.py!} + ``` + +尝试以下操作: + +* 使用多个浏览器选项卡打开应用程序。 +* 从这些选项卡中发送消息。 +* 然后关闭其中一个选项卡。 + +这将引发 `WebSocketDisconnect` 异常,并且所有其他客户端都会收到类似以下的消息: + +``` +Client #1596980209979 left the chat +``` + +!!! tip + 上面的应用程序是一个最小和简单的示例,用于演示如何处理和向多个 WebSocket 连接广播消息。 + + 但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。 + + 如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。 + +## 更多信息 + +要了解更多选项,请查看 Starlette 的文档: + +* [WebSocket 类](https://www.starlette.io/websockets/) +* [基于类的 WebSocket 处理](https://www.starlette.io/endpoints/#websocketendpoint)。 diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md index 4db3ef10c..1de2a8d36 100644 --- a/docs/zh/docs/index.md +++ b/docs/zh/docs/index.md @@ -437,7 +437,6 @@ item: Item 用于 Pydantic: -* ujson - 更快的 JSON 「解析」。 * email_validator - 用于 email 校验。 用于 Starlette: diff --git a/docs/zh/docs/tutorial/dependencies/index.md b/docs/zh/docs/tutorial/dependencies/index.md index c717da0f6..7a133061d 100644 --- a/docs/zh/docs/tutorial/dependencies/index.md +++ b/docs/zh/docs/tutorial/dependencies/index.md @@ -1,4 +1,4 @@ -# 依赖项 - 第一步 +# 依赖项 FastAPI 提供了简单易用,但功能强大的**依赖注入**系统。 diff --git a/docs/zh/docs/tutorial/index.md b/docs/zh/docs/tutorial/index.md index 6093caeb6..6180d3de3 100644 --- a/docs/zh/docs/tutorial/index.md +++ b/docs/zh/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# 教程 - 用户指南 - 简介 +# 教程 - 用户指南 本教程将一步步向你展示如何使用 **FastAPI** 的绝大部分特性。 diff --git a/docs/zh/docs/tutorial/security/index.md b/docs/zh/docs/tutorial/security/index.md index 8f302a16c..0595f5f63 100644 --- a/docs/zh/docs/tutorial/security/index.md +++ b/docs/zh/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# 安全性简介 +# 安全性 有许多方法可以处理安全性、身份认证和授权等问题。 diff --git a/docs/zh/docs/tutorial/testing.md b/docs/zh/docs/tutorial/testing.md new file mode 100644 index 000000000..41f01f8d8 --- /dev/null +++ b/docs/zh/docs/tutorial/testing.md @@ -0,0 +1,212 @@ +# 测试 + +感谢 Starlette,测试**FastAPI** 应用轻松又愉快。 + +它基于 HTTPX, 而HTTPX又是基于Requests设计的,所以很相似且易懂。 + +有了它,你可以直接与**FastAPI**一起使用 pytest。 + +## 使用 `TestClient` + +!!! 信息 + 要使用 `TestClient`,先要安装 `httpx`. + + 例:`pip install httpx`. + +导入 `TestClient`. + +通过传入你的**FastAPI**应用创建一个 `TestClient` 。 + +创建名字以 `test_` 开头的函数(这是标准的 `pytest` 约定)。 + +像使用 `httpx` 那样使用 `TestClient` 对象。 + +为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。 + +```Python hl_lines="2 12 15-18" +{!../../../docs_src/app_testing/tutorial001.py!} +``` + +!!! 提示 + 注意测试函数是普通的 `def`,不是 `async def`。 + + 还有client的调用也是普通的调用,不是用 `await`。 + + 这让你可以直接使用 `pytest` 而不会遇到麻烦。 + +!!! note "技术细节" + 你也可以用 `from starlette.testclient import TestClient`。 + + **FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。但它直接来自Starlette。 + +!!! 提示 + 除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。 + +## 分离测试 + +在实际应用中,你可能会把你的测试放在另一个文件里。 + +您的**FastAPI**应用程序也可能由一些文件/模块组成等等。 + +### **FastAPI** app 文件 + +假设你有一个像 [更大的应用](./bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构: + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +在 `main.py` 文件中你有一个 **FastAPI** app: + + +```Python +{!../../../docs_src/app_testing/main.py!} +``` + +### 测试文件 + +然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件): + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象: + +```Python hl_lines="3" +{!../../../docs_src/app_testing/test_main.py!} +``` + +...然后测试代码和之前一样的。 + +## 测试:扩展示例 + +现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。 + +### 扩展后的 **FastAPI** app 文件 + +让我们继续之前的文件结构: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +假设现在包含**FastAPI** app的文件 `main.py` 有些其他**路径操作**。 + +有个 `GET` 操作会返回错误。 + +有个 `POST` 操作会返回一些错误。 + +所有*路径操作* 都需要一个`X-Token` 头。 + +=== "Python 3.10+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} + ``` + +=== "Python 3.9+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} + ``` + +=== "Python 3.6+" + + ```Python + {!> ../../../docs_src/app_testing/app_b_an/main.py!} + ``` + +=== "Python 3.10+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + {!> ../../../docs_src/app_testing/app_b_py310/main.py!} + ``` + +=== "Python 3.6+ non-Annotated" + + !!! tip + Prefer to use the `Annotated` version if possible. + + ```Python + {!> ../../../docs_src/app_testing/app_b/main.py!} + ``` + +### 扩展后的测试文件 + +然后您可以使用扩展后的测试更新`test_main.py`: + +```Python +{!> ../../../docs_src/app_testing/app_b/test_main.py!} +``` + +每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。 + +接着只需在测试中同样操作。 + +示例: + +* 传一个*路径* 或*查询* 参数,添加到URL上。 +* 传一个JSON体,传一个Python对象(例如一个`dict`)到参数 `json`。 +* 如果你需要发送 *Form Data* 而不是 JSON,使用 `data` 参数。 +* 要发送 *headers*,传 `dict` 给 `headers` 参数。 +* 对于 *cookies*,传 `dict` 给 `cookies` 参数。 + +关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 HTTPX 文档. + +!!! 信息 + 注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。 + + 如果你在测试中有一个Pydantic模型,并且你想在测试时发送它的数据给应用,你可以使用在[JSON Compatible Encoder](encoder.md){.internal-link target=_blank}介绍的`jsonable_encoder` 。 + +## 运行起来 + +之后,你只需要安装 `pytest`: + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +他会自动检测文件和测试,执行测试,然后向你报告结果。 + +执行测试: + +
+ +```console +$ pytest + +================ test session starts ================ +platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 +rootdir: /home/user/code/superawesome-cli/app +plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 +collected 6 items + +---> 100% + +test_main.py ...... [100%] + +================= 1 passed in 0.03s ================= +``` + +
diff --git a/docs/zh/mkdocs.yml b/docs/zh/mkdocs.yml index 522c83766..de18856f4 100644 --- a/docs/zh/mkdocs.yml +++ b/docs/zh/mkdocs.yml @@ -1,220 +1 @@ -site_name: FastAPI -site_description: FastAPI framework, high performance, easy to learn, fast to code, ready for production -site_url: https://fastapi.tiangolo.com/zh/ -theme: - name: material - custom_dir: overrides - palette: - - media: '(prefers-color-scheme: light)' - scheme: default - primary: teal - accent: amber - toggle: - icon: material/lightbulb - name: Switch to light mode - - media: '(prefers-color-scheme: dark)' - scheme: slate - primary: teal - accent: amber - toggle: - icon: material/lightbulb-outline - name: Switch to dark mode - features: - - search.suggest - - search.highlight - - content.tabs.link - icon: - repo: fontawesome/brands/github-alt - logo: https://fastapi.tiangolo.com/img/icon-white.svg - favicon: https://fastapi.tiangolo.com/img/favicon.png - language: zh -repo_name: tiangolo/fastapi -repo_url: https://github.com/tiangolo/fastapi -edit_uri: '' -plugins: -- search -- markdownextradata: - data: data -nav: -- FastAPI: index.md -- Languages: - - en: / - - az: /az/ - - cs: /cs/ - - de: /de/ - - em: /em/ - - es: /es/ - - fa: /fa/ - - fr: /fr/ - - he: /he/ - - hy: /hy/ - - id: /id/ - - it: /it/ - - ja: /ja/ - - ko: /ko/ - - lo: /lo/ - - nl: /nl/ - - pl: /pl/ - - pt: /pt/ - - ru: /ru/ - - sq: /sq/ - - sv: /sv/ - - ta: /ta/ - - tr: /tr/ - - uk: /uk/ - - zh: /zh/ -- features.md -- fastapi-people.md -- python-types.md -- 教程 - 用户指南: - - tutorial/index.md - - tutorial/first-steps.md - - tutorial/path-params.md - - tutorial/query-params.md - - tutorial/body.md - - tutorial/query-params-str-validations.md - - tutorial/path-params-numeric-validations.md - - tutorial/body-multiple-params.md - - tutorial/body-fields.md - - tutorial/middleware.md - - tutorial/body-nested-models.md - - tutorial/header-params.md - - tutorial/response-model.md - - tutorial/extra-models.md - - tutorial/response-status-code.md - - tutorial/schema-extra-example.md - - tutorial/extra-data-types.md - - tutorial/cookie-params.md - - tutorial/request-forms.md - - tutorial/request-files.md - - tutorial/request-forms-and-files.md - - tutorial/handling-errors.md - - tutorial/path-operation-configuration.md - - tutorial/encoder.md - - tutorial/body-updates.md - - 依赖项: - - tutorial/dependencies/index.md - - tutorial/dependencies/classes-as-dependencies.md - - tutorial/dependencies/sub-dependencies.md - - tutorial/dependencies/dependencies-in-path-operation-decorators.md - - tutorial/dependencies/global-dependencies.md - - 安全性: - - tutorial/security/index.md - - tutorial/security/first-steps.md - - tutorial/security/get-current-user.md - - tutorial/security/simple-oauth2.md - - tutorial/security/oauth2-jwt.md - - tutorial/cors.md - - tutorial/sql-databases.md - - tutorial/bigger-applications.md - - tutorial/metadata.md - - tutorial/static-files.md - - tutorial/debugging.md -- 高级用户指南: - - advanced/index.md - - advanced/path-operation-advanced-configuration.md - - advanced/additional-status-codes.md - - advanced/response-directly.md - - advanced/custom-response.md - - advanced/response-cookies.md - - advanced/response-change-status-code.md - - advanced/response-headers.md - - advanced/wsgi.md -- contributing.md -- help-fastapi.md -- benchmarks.md -markdown_extensions: -- toc: - permalink: true -- markdown.extensions.codehilite: - guess_lang: false -- mdx_include: - base_path: docs -- admonition -- codehilite -- extra -- pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format '' -- pymdownx.tabbed: - alternate_style: true -- attr_list -- md_in_html -extra: - analytics: - provider: google - property: G-YNEVN69SC3 - social: - - icon: fontawesome/brands/github-alt - link: https://github.com/tiangolo/fastapi - - icon: fontawesome/brands/discord - link: https://discord.gg/VQjSZaeJmf - - icon: fontawesome/brands/twitter - link: https://twitter.com/fastapi - - icon: fontawesome/brands/linkedin - link: https://www.linkedin.com/in/tiangolo - - icon: fontawesome/brands/dev - link: https://dev.to/tiangolo - - icon: fontawesome/brands/medium - link: https://medium.com/@tiangolo - - icon: fontawesome/solid/globe - link: https://tiangolo.com - alternate: - - link: / - name: en - English - - link: /az/ - name: az - - link: /cs/ - name: cs - - link: /de/ - name: de - - link: /em/ - name: 😉 - - link: /es/ - name: es - español - - link: /fa/ - name: fa - - link: /fr/ - name: fr - français - - link: /he/ - name: he - - link: /hy/ - name: hy - - link: /id/ - name: id - - link: /it/ - name: it - italiano - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - - link: /lo/ - name: lo - ພາສາລາວ - - link: /nl/ - name: nl - - link: /pl/ - name: pl - - link: /pt/ - name: pt - português - - link: /ru/ - name: ru - русский язык - - link: /sq/ - name: sq - shqip - - link: /sv/ - name: sv - svenska - - link: /ta/ - name: ta - தமிழ் - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 汉语 -extra_css: -- https://fastapi.tiangolo.com/css/termynal.css -- https://fastapi.tiangolo.com/css/custom.css -extra_javascript: -- https://fastapi.tiangolo.com/js/termynal.js -- https://fastapi.tiangolo.com/js/custom.js +INHERIT: ../en/mkdocs.yml diff --git a/docs/zh/overrides/.gitignore b/docs/zh/overrides/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/extending_openapi/tutorial001.py b/docs_src/extending_openapi/tutorial001.py index 561e95898..35e31c0e0 100644 --- a/docs_src/extending_openapi/tutorial001.py +++ b/docs_src/extending_openapi/tutorial001.py @@ -15,7 +15,8 @@ def custom_openapi(): openapi_schema = get_openapi( title="Custom title", version="2.5.0", - description="This is a very custom OpenAPI schema", + summary="This is a very custom OpenAPI schema", + description="Here's a longer description of the custom **OpenAPI** schema", routes=app.routes, ) openapi_schema["info"]["x-logo"] = { diff --git a/docs_src/metadata/tutorial001.py b/docs_src/metadata/tutorial001.py index 3fba9e7d1..76656e81b 100644 --- a/docs_src/metadata/tutorial001.py +++ b/docs_src/metadata/tutorial001.py @@ -18,6 +18,7 @@ You will be able to: app = FastAPI( title="ChimichangApp", description=description, + summary="Deadpool's favorite app. Nuff said.", version="0.0.1", terms_of_service="http://example.com/terms/", contact={ diff --git a/docs_src/metadata/tutorial001_1.py b/docs_src/metadata/tutorial001_1.py new file mode 100644 index 000000000..a8f5b9458 --- /dev/null +++ b/docs_src/metadata/tutorial001_1.py @@ -0,0 +1,38 @@ +from fastapi import FastAPI + +description = """ +ChimichangApp API helps you do awesome stuff. 🚀 + +## Items + +You can **read items**. + +## Users + +You will be able to: + +* **Create users** (_not implemented_). +* **Read users** (_not implemented_). +""" + +app = FastAPI( + title="ChimichangApp", + description=description, + summary="Deadpool's favorite app. Nuff said.", + version="0.0.1", + terms_of_service="http://example.com/terms/", + contact={ + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + }, + license_info={ + "name": "Apache 2.0", + "identifier": "MIT", + }, +) + + +@app.get("/items/") +async def read_items(): + return [{"name": "Katana"}] diff --git a/docs_src/openapi_webhooks/tutorial001.py b/docs_src/openapi_webhooks/tutorial001.py new file mode 100644 index 000000000..5016f5b00 --- /dev/null +++ b/docs_src/openapi_webhooks/tutorial001.py @@ -0,0 +1,25 @@ +from datetime import datetime + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Subscription(BaseModel): + username: str + montly_fee: float + start_date: datetime + + +@app.webhooks.post("new-subscription") +def new_subscription(body: Subscription): + """ + When a new user subscribes to your service we'll send you a POST request with this + data to the URL that you register for the event `new-subscription` in the dashboard. + """ + + +@app.get("/users/") +def read_users(): + return ["Rick", "Morty"] diff --git a/docs_src/schema_extra_example/tutorial001.py b/docs_src/schema_extra_example/tutorial001.py index a5ae28127..6ab96ff85 100644 --- a/docs_src/schema_extra_example/tutorial001.py +++ b/docs_src/schema_extra_example/tutorial001.py @@ -14,12 +14,14 @@ class Item(BaseModel): class Config: schema_extra = { - "example": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ] } diff --git a/docs_src/schema_extra_example/tutorial001_py310.py b/docs_src/schema_extra_example/tutorial001_py310.py index 77ceedd60..ec83f1112 100644 --- a/docs_src/schema_extra_example/tutorial001_py310.py +++ b/docs_src/schema_extra_example/tutorial001_py310.py @@ -12,12 +12,14 @@ class Item(BaseModel): class Config: schema_extra = { - "example": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ] } diff --git a/docs_src/schema_extra_example/tutorial002.py b/docs_src/schema_extra_example/tutorial002.py index 6de434f81..70f06567c 100644 --- a/docs_src/schema_extra_example/tutorial002.py +++ b/docs_src/schema_extra_example/tutorial002.py @@ -7,10 +7,10 @@ app = FastAPI() class Item(BaseModel): - name: str = Field(example="Foo") - description: Union[str, None] = Field(default=None, example="A very nice Item") - price: float = Field(example=35.4) - tax: Union[float, None] = Field(default=None, example=3.2) + name: str = Field(examples=["Foo"]) + description: Union[str, None] = Field(default=None, examples=["A very nice Item"]) + price: float = Field(examples=[35.4]) + tax: Union[float, None] = Field(default=None, examples=[3.2]) @app.put("/items/{item_id}") diff --git a/docs_src/schema_extra_example/tutorial002_py310.py b/docs_src/schema_extra_example/tutorial002_py310.py index e84928bb1..27d786867 100644 --- a/docs_src/schema_extra_example/tutorial002_py310.py +++ b/docs_src/schema_extra_example/tutorial002_py310.py @@ -5,10 +5,10 @@ app = FastAPI() class Item(BaseModel): - name: str = Field(example="Foo") - description: str | None = Field(default=None, example="A very nice Item") - price: float = Field(example=35.4) - tax: float | None = Field(default=None, example=3.2) + name: str = Field(examples=["Foo"]) + description: str | None = Field(default=None, examples=["A very nice Item"]) + price: float = Field(examples=[35.4]) + tax: float | None = Field(default=None, examples=[3.2]) @app.put("/items/{item_id}") diff --git a/docs_src/schema_extra_example/tutorial003.py b/docs_src/schema_extra_example/tutorial003.py index ce1736bba..385f3de8a 100644 --- a/docs_src/schema_extra_example/tutorial003.py +++ b/docs_src/schema_extra_example/tutorial003.py @@ -17,12 +17,14 @@ class Item(BaseModel): async def update_item( item_id: int, item: Item = Body( - example={ - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ], ), ): results = {"item_id": item_id, "item": item} diff --git a/docs_src/schema_extra_example/tutorial003_an.py b/docs_src/schema_extra_example/tutorial003_an.py index 1dec555a9..23675aba1 100644 --- a/docs_src/schema_extra_example/tutorial003_an.py +++ b/docs_src/schema_extra_example/tutorial003_an.py @@ -20,12 +20,14 @@ async def update_item( item: Annotated[ Item, Body( - example={ - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ], ), ], ): diff --git a/docs_src/schema_extra_example/tutorial003_an_py310.py b/docs_src/schema_extra_example/tutorial003_an_py310.py index 9edaddfb8..bbd2e171e 100644 --- a/docs_src/schema_extra_example/tutorial003_an_py310.py +++ b/docs_src/schema_extra_example/tutorial003_an_py310.py @@ -19,12 +19,14 @@ async def update_item( item: Annotated[ Item, Body( - example={ - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ], ), ], ): diff --git a/docs_src/schema_extra_example/tutorial003_an_py39.py b/docs_src/schema_extra_example/tutorial003_an_py39.py index fe08847d9..472808561 100644 --- a/docs_src/schema_extra_example/tutorial003_an_py39.py +++ b/docs_src/schema_extra_example/tutorial003_an_py39.py @@ -19,12 +19,14 @@ async def update_item( item: Annotated[ Item, Body( - example={ - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ], ), ], ): diff --git a/docs_src/schema_extra_example/tutorial003_py310.py b/docs_src/schema_extra_example/tutorial003_py310.py index 1e137101d..2d31619be 100644 --- a/docs_src/schema_extra_example/tutorial003_py310.py +++ b/docs_src/schema_extra_example/tutorial003_py310.py @@ -15,12 +15,14 @@ class Item(BaseModel): async def update_item( item_id: int, item: Item = Body( - example={ - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ], ), ): results = {"item_id": item_id, "item": item} diff --git a/docs_src/schema_extra_example/tutorial004.py b/docs_src/schema_extra_example/tutorial004.py index b67edf30c..75514a3e9 100644 --- a/docs_src/schema_extra_example/tutorial004.py +++ b/docs_src/schema_extra_example/tutorial004.py @@ -18,33 +18,22 @@ async def update_item( *, item_id: int, item: Item = Body( - examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, + { + "name": "Bar", + "price": "35.4", }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, + { + "name": "Baz", + "price": "thirty five point four", }, - }, + ], ), ): results = {"item_id": item_id, "item": item} diff --git a/docs_src/schema_extra_example/tutorial004_an.py b/docs_src/schema_extra_example/tutorial004_an.py index 82c9a92ac..e817302a2 100644 --- a/docs_src/schema_extra_example/tutorial004_an.py +++ b/docs_src/schema_extra_example/tutorial004_an.py @@ -21,33 +21,22 @@ async def update_item( item: Annotated[ Item, Body( - examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, + { + "name": "Bar", + "price": "35.4", }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, + { + "name": "Baz", + "price": "thirty five point four", }, - }, + ], ), ], ): diff --git a/docs_src/schema_extra_example/tutorial004_an_py310.py b/docs_src/schema_extra_example/tutorial004_an_py310.py index 01f1a486c..650da3187 100644 --- a/docs_src/schema_extra_example/tutorial004_an_py310.py +++ b/docs_src/schema_extra_example/tutorial004_an_py310.py @@ -20,33 +20,22 @@ async def update_item( item: Annotated[ Item, Body( - examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, + { + "name": "Bar", + "price": "35.4", }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, + { + "name": "Baz", + "price": "thirty five point four", }, - }, + ], ), ], ): diff --git a/docs_src/schema_extra_example/tutorial004_an_py39.py b/docs_src/schema_extra_example/tutorial004_an_py39.py index d50e8aa5f..dc5a8fe49 100644 --- a/docs_src/schema_extra_example/tutorial004_an_py39.py +++ b/docs_src/schema_extra_example/tutorial004_an_py39.py @@ -20,33 +20,22 @@ async def update_item( item: Annotated[ Item, Body( - examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, + { + "name": "Bar", + "price": "35.4", }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, + { + "name": "Baz", + "price": "thirty five point four", }, - }, + ], ), ], ): diff --git a/docs_src/schema_extra_example/tutorial004_py310.py b/docs_src/schema_extra_example/tutorial004_py310.py index 100a30860..05996ac2a 100644 --- a/docs_src/schema_extra_example/tutorial004_py310.py +++ b/docs_src/schema_extra_example/tutorial004_py310.py @@ -16,33 +16,22 @@ async def update_item( *, item_id: int, item: Item = Body( - examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, + { + "name": "Bar", + "price": "35.4", }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, + { + "name": "Baz", + "price": "thirty five point four", }, - }, + ], ), ): results = {"item_id": item_id, "item": item} diff --git a/docs_src/sql_databases/sql_app/tests/test_sql_app.py b/docs_src/sql_databases/sql_app/tests/test_sql_app.py index c60c3356f..5f55add0a 100644 --- a/docs_src/sql_databases/sql_app/tests/test_sql_app.py +++ b/docs_src/sql_databases/sql_app/tests/test_sql_app.py @@ -1,14 +1,17 @@ from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import StaticPool from ..database import Base from ..main import app, get_db -SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" +SQLALCHEMY_DATABASE_URL = "sqlite://" engine = create_engine( - SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} + SQLALCHEMY_DATABASE_URL, + connect_args={"check_same_thread": False}, + poolclass=StaticPool, ) TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/fastapi/_compat.py b/fastapi/_compat.py index 85a017fc0..a3b46830c 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -101,6 +101,9 @@ if PYDANTIC_V2: self._type_adapter: TypeAdapter[Any] = TypeAdapter( Annotated[self.field_info.annotation, self.field_info] ) + self._field_info_core_schema = TypeAdapter.model_field_schema( + name=self.name, field=self.field_info + ) def get_default(self) -> Any: if self.field_info.is_required(): @@ -180,7 +183,7 @@ if PYDANTIC_V2: model_name_map: ModelNameMap, ) -> Dict[str, Any]: # This expects that GenerateJsonSchema was already used to generate the definitions - json_schema = schema_generator.generate_inner(field._type_adapter.core_schema) + json_schema = schema_generator.generate_inner(field._field_info_core_schema) if "$ref" not in json_schema: # TODO remove when deprecating Pydantic v1 # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 @@ -199,7 +202,12 @@ if PYDANTIC_V2: model_name_map: ModelNameMap, ) -> Dict[str, Dict[str, Any]]: inputs = [ - (field, "validation", field._type_adapter.core_schema) for field in fields + ( + field, + "validation", + TypeAdapter.model_field_schema(name=field.name, field=field.field_info), + ) + for field in fields ] _, definitions = schema_generator.generate_definitions(inputs=inputs) # type: ignore[arg-type] return definitions # type: ignore[return-value] diff --git a/fastapi/applications.py b/fastapi/applications.py index 8d0cfa961..e32cfa03d 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -54,6 +54,7 @@ class FastAPI(Starlette): debug: bool = False, routes: Optional[List[BaseRoute]] = None, title: str = "FastAPI", + summary: Optional[str] = None, description: str = "", version: str = "0.1.0", openapi_url: Optional[str] = "/openapi.json", @@ -61,6 +62,7 @@ class FastAPI(Starlette): servers: Optional[List[Dict[str, Union[str, Any]]]] = None, dependencies: Optional[Sequence[Depends]] = None, default_response_class: Type[Response] = Default(JSONResponse), + redirect_slashes: bool = True, docs_url: Optional[str] = "/docs", redoc_url: Optional[str] = "/redoc", swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect", @@ -83,6 +85,7 @@ class FastAPI(Starlette): root_path_in_servers: bool = True, responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, callbacks: Optional[List[BaseRoute]] = None, + webhooks: Optional[routing.APIRouter] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, swagger_ui_parameters: Optional[Dict[str, Any]] = None, @@ -93,6 +96,7 @@ class FastAPI(Starlette): ) -> None: self.debug = debug self.title = title + self.summary = summary self.description = description self.version = version self.terms_of_service = terms_of_service @@ -108,7 +112,7 @@ class FastAPI(Starlette): self.swagger_ui_parameters = swagger_ui_parameters self.servers = servers or [] self.extra = extra - self.openapi_version = "3.0.2" + self.openapi_version = "3.1.0" self.openapi_schema: Optional[Dict[str, Any]] = None if self.openapi_url: assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'" @@ -121,11 +125,13 @@ class FastAPI(Starlette): "automatic. Check the docs at " "https://fastapi.tiangolo.com/advanced/sub-applications/" ) + self.webhooks = webhooks or routing.APIRouter() self.root_path = root_path or openapi_prefix self.state: State = State() self.dependency_overrides: Dict[Callable[..., Any], Callable[..., Any]] = {} self.router: routing.APIRouter = routing.APIRouter( routes=routes, + redirect_slashes=redirect_slashes, dependency_overrides_provider=self, on_startup=on_startup, on_shutdown=on_shutdown, @@ -212,11 +218,13 @@ class FastAPI(Starlette): title=self.title, version=self.version, openapi_version=self.openapi_version, + summary=self.summary, description=self.description, terms_of_service=self.terms_of_service, contact=self.contact, license_info=self.license_info, routes=self.routes, + webhooks=self.webhooks.routes, tags=self.openapi_tags, servers=self.servers, ) diff --git a/fastapi/encoders.py b/fastapi/encoders.py index af5d72ed0..b542749f2 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -202,7 +202,7 @@ def jsonable_encoder( ) encoded_dict[encoded_key] = encoded_value return encoded_dict - if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)): encoded_list = [] for item in obj: encoded_list.append( diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index bf335118f..81f67dcc5 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -17,8 +17,8 @@ def get_swagger_ui_html( *, openapi_url: str, title: str, - swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui-bundle.js", - swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@4/swagger-ui.css", + swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js", + swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css", swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png", oauth2_redirect_url: Optional[str] = None, init_oauth: Optional[Dict[str, Any]] = None, diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 7176e59dd..5d9d94ff5 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union from fastapi._compat import ( PYDANTIC_V2, @@ -11,7 +11,8 @@ from fastapi._compat import ( ) from fastapi.logger import logger from pydantic import AnyUrl, BaseModel, Field -from typing_extensions import Literal +from typing_extensions import Annotated, Literal +from typing_extensions import deprecated as typing_deprecated try: import email_validator @@ -70,6 +71,7 @@ class Contact(BaseModel): class License(BaseModel): name: str + identifier: Optional[str] = None url: Optional[AnyUrl] = None if PYDANTIC_V2: @@ -83,6 +85,7 @@ class License(BaseModel): class Info(BaseModel): title: str + summary: Optional[str] = None description: Optional[str] = None termsOfService: Optional[str] = None contact: Optional[Contact] = None @@ -99,7 +102,7 @@ class Info(BaseModel): class ServerVariable(BaseModel): - enum: Optional[List[str]] = None + enum: Annotated[Optional[List[str]], Field(min_length=1)] = None default: str description: Optional[str] = None @@ -165,9 +168,42 @@ class ExternalDocumentation(BaseModel): class Schema(BaseModel): + # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu + # Core Vocabulary + schema_: Optional[str] = Field(default=None, alias="$schema") + vocabulary: Optional[str] = Field(default=None, alias="$vocabulary") + id: Optional[str] = Field(default=None, alias="$id") + anchor: Optional[str] = Field(default=None, alias="$anchor") + dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor") ref: Optional[str] = Field(default=None, alias="$ref") - title: Optional[str] = None - multipleOf: Optional[float] = None + dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef") + defs: Optional[Dict[str, "Schema"]] = Field(default=None, alias="$defs") + comment: Optional[str] = Field(default=None, alias="$comment") + # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s + # A Vocabulary for Applying Subschemas + allOf: Optional[List["Schema"]] = None + anyOf: Optional[List["Schema"]] = None + oneOf: Optional[List["Schema"]] = None + not_: Optional["Schema"] = Field(default=None, alias="not") + if_: Optional["Schema"] = Field(default=None, alias="if") + then: Optional["Schema"] = None + else_: Optional["Schema"] = Field(default=None, alias="else") + dependentSchemas: Optional[Dict[str, "Schema"]] = None + prefixItems: Optional[List["Schema"]] = None + items: Optional[Union["Schema", List["Schema"]]] = None + contains: Optional["Schema"] = None + properties: Optional[Dict[str, "Schema"]] = None + patternProperties: Optional[Dict[str, "Schema"]] = None + additionalProperties: Optional["Schema"] = None + propertyNames: Optional["Schema"] = None + unevaluatedItems: Optional["Schema"] = None + unevaluatedProperties: Optional["Schema"] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural + # A Vocabulary for Structural Validation + type: Optional[str] = None + enum: Optional[List[Any]] = None + const: Optional[Any] = None + multipleOf: Optional[float] = Field(default=None, gt=0) maximum: Optional[float] = None exclusiveMaximum: Optional[float] = None minimum: Optional[float] = None @@ -178,29 +214,41 @@ class Schema(BaseModel): maxItems: Optional[int] = Field(default=None, ge=0) minItems: Optional[int] = Field(default=None, ge=0) uniqueItems: Optional[bool] = None + maxContains: Optional[int] = Field(default=None, ge=0) + minContains: Optional[int] = Field(default=None, ge=0) maxProperties: Optional[int] = Field(default=None, ge=0) minProperties: Optional[int] = Field(default=None, ge=0) required: Optional[List[str]] = None - enum: Optional[List[Any]] = None - type: Optional[str] = None - allOf: Optional[List["Schema"]] = None - oneOf: Optional[List["Schema"]] = None - anyOf: Optional[List["Schema"]] = None - not_: Optional["Schema"] = Field(default=None, alias="not") - items: Optional[Union["Schema", List["Schema"]]] = None - properties: Optional[Dict[str, "Schema"]] = None - additionalProperties: Optional[Union["Schema", Reference, bool]] = None - description: Optional[str] = None + dependentRequired: Optional[Dict[str, Set[str]]] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c + # Vocabularies for Semantic Content With "format" format: Optional[str] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten + # A Vocabulary for the Contents of String-Encoded Data + contentEncoding: Optional[str] = None + contentMediaType: Optional[str] = None + contentSchema: Optional["Schema"] = None + # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta + # A Vocabulary for Basic Meta-Data Annotations + title: Optional[str] = None + description: Optional[str] = None default: Optional[Any] = None - nullable: Optional[bool] = None - discriminator: Optional[Discriminator] = None + deprecated: Optional[bool] = None readOnly: Optional[bool] = None writeOnly: Optional[bool] = None + examples: Optional[List[Any]] = None + # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object + # Schema Object + discriminator: Optional[Discriminator] = None xml: Optional[XML] = None externalDocs: Optional[ExternalDocumentation] = None - example: Optional[Any] = None - deprecated: Optional[bool] = None + example: Annotated[ + Optional[Any], + typing_deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = None if PYDANTIC_V2: model_config = {"extra": "allow"} @@ -351,7 +399,7 @@ class Operation(BaseModel): parameters: Optional[List[Union[Parameter, Reference]]] = None requestBody: Optional[Union[RequestBody, Reference]] = None # Using Any for Specification Extensions - responses: Dict[str, Union[Response, Any]] + responses: Optional[Dict[str, Union[Response, Any]]] = None callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None deprecated: Optional[bool] = None security: Optional[List[Dict[str, List[str]]]] = None @@ -503,6 +551,7 @@ class Components(BaseModel): links: Optional[Dict[str, Union[Link, Reference]]] = None # Using Any for Specification Extensions callbacks: Optional[Dict[str, Union[Dict[str, PathItem], Reference, Any]]] = None + pathItems: Optional[Dict[str, Union[PathItem, Reference]]] = None if PYDANTIC_V2: model_config = {"extra": "allow"} @@ -530,9 +579,11 @@ class Tag(BaseModel): class OpenAPI(BaseModel): openapi: str info: Info + jsonSchemaDialect: Optional[str] = None servers: Optional[List[Server]] = None # Using Any for Specification Extensions - paths: Dict[str, Union[PathItem, Any]] + paths: Optional[Dict[str, Union[PathItem, Any]]] = None + webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None components: Optional[Components] = None security: Optional[List[Dict[str, List[str]]]] = None tags: Optional[List[Tag]] = None diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 550c6cb6c..3d292a819 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -110,9 +110,7 @@ def get_openapi_operation_parameters( } if field_info.description: parameter["description"] = field_info.description - if field_info.examples: - parameter["examples"] = jsonable_encoder(field_info.examples) - elif field_info.example != Undefined: + if field_info.example != Undefined: parameter["example"] = jsonable_encoder(field_info.example) if field_info.deprecated: parameter["deprecated"] = field_info.deprecated @@ -141,9 +139,7 @@ def get_openapi_operation_request_body( if required: request_body_oai["required"] = required request_media_content: Dict[str, Any] = {"schema": body_schema} - if field_info.examples: - request_media_content["examples"] = jsonable_encoder(field_info.examples) - elif field_info.example != Undefined: + if field_info.example != Undefined: request_media_content["example"] = jsonable_encoder(field_info.example) request_body_oai["content"] = {request_media_type: request_media_content} return request_body_oai @@ -409,9 +405,11 @@ def get_openapi( *, title: str, version: str, - openapi_version: str = "3.0.2", + openapi_version: str = "3.1.0", + summary: Optional[str] = None, description: Optional[str] = None, routes: Sequence[BaseRoute], + webhooks: Optional[Sequence[BaseRoute]] = None, tags: Optional[List[Dict[str, Any]]] = None, servers: Optional[List[Dict[str, Union[str, Any]]]] = None, terms_of_service: Optional[str] = None, @@ -419,6 +417,8 @@ def get_openapi( license_info: Optional[Dict[str, Union[str, Any]]] = None, ) -> Dict[str, Any]: info: Dict[str, Any] = {"title": title, "version": version} + if summary: + info["summary"] = summary if description: info["description"] = description if terms_of_service: @@ -432,8 +432,9 @@ def get_openapi( output["servers"] = servers components: Dict[str, Dict[str, Any]] = {} paths: Dict[str, Dict[str, Any]] = {} + webhook_paths: Dict[str, Dict[str, Any]] = {} operation_ids: Set[str] = set() - all_fields = get_fields_from_routes(routes) + all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or [])) model_name_map = get_compat_model_name_map(all_fields) schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) definitions = get_definitions( @@ -441,7 +442,7 @@ def get_openapi( schema_generator=schema_generator, model_name_map=model_name_map, ) - for route in routes: + for route in routes or []: if isinstance(route, routing.APIRoute): result = get_openapi_path( route=route, @@ -459,11 +460,31 @@ def get_openapi( ) if path_definitions: definitions.update(path_definitions) + for webhook in webhooks or []: + if isinstance(webhook, routing.APIRoute): + result = get_openapi_path( + route=webhook, + operation_ids=operation_ids, + schema_generator=schema_generator, + model_name_map=model_name_map, + ) + if result: + path, security_schemes, path_definitions = result + if path: + webhook_paths.setdefault(webhook.path_format, {}).update(path) + if security_schemes: + components.setdefault("securitySchemes", {}).update( + security_schemes + ) + if path_definitions: + definitions.update(path_definitions) if definitions: components["schemas"] = {k: definitions[k] for k in sorted(definitions)} if components: output["components"] = components output["paths"] = paths + if webhook_paths: + output["webhooks"] = webhook_paths if tags: output["tags"] = tags return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index c12e7c808..6ddce3468 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -1,7 +1,8 @@ -from typing import Any, Callable, Dict, Optional, Sequence +from typing import Any, Callable, List, Optional, Sequence from fastapi import params from fastapi._compat import Undefined +from typing_extensions import Annotated, deprecated def Path( # noqa: N802 @@ -18,8 +19,14 @@ def Path( # noqa: N802 max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -59,8 +66,14 @@ def Query( # noqa: N802 max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -101,8 +114,14 @@ def Header( # noqa: N802 max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -143,8 +162,14 @@ def Cookie( # noqa: N802 max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -186,8 +211,14 @@ def Body( # noqa: N802 max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, **extra: Any, ) -> Any: return params.Body( @@ -226,8 +257,14 @@ def Form( # noqa: N802 max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, **extra: Any, ) -> Any: return params.Form( @@ -265,8 +302,14 @@ def File( # noqa: N802 max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, **extra: Any, ) -> Any: return params.File( diff --git a/fastapi/params.py b/fastapi/params.py index e35f7b95d..c1bdaf1b4 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -1,7 +1,9 @@ +import warnings from enum import Enum -from typing import Any, Callable, Dict, Optional, Sequence, Type +from typing import Any, Callable, List, Optional, Sequence, Type from pydantic.fields import FieldInfo +from typing_extensions import Annotated, deprecated from ._compat import PYDANTIC_V2, Undefined @@ -32,13 +34,25 @@ class Param(FieldInfo): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ): self.deprecated = deprecated + if example is not Undefined: + warnings.warn( + "`example` has been depreacated, please use `examples` instead", + category=DeprecationWarning, + stacklevel=1, + ) self.example = example self.include_in_schema = include_in_schema kwargs = dict( @@ -54,6 +68,8 @@ class Param(FieldInfo): max_length=max_length, **extra, ) + if examples is not None: + kwargs["examples"] = examples if PYDANTIC_V2: kwargs["annotation"] = annotation kwargs["pattern"] = pattern or regex @@ -62,9 +78,6 @@ class Param(FieldInfo): kwargs["regex"] = pattern or regex super().__init__(**kwargs) - # TODO: pv2 decide how to handle OpenAPI examples vs JSON Schema examples - # and how to deprecate OpenAPI examples - self.examples = examples # type: ignore[assignment] def __repr__(self) -> str: return f"{self.__class__.__name__}({self.default})" @@ -89,8 +102,14 @@ class Path(Param): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -138,8 +157,14 @@ class Query(Param): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -186,8 +211,14 @@ class Header(Param): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -234,8 +265,14 @@ class Cookie(Param): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, @@ -281,12 +318,24 @@ class Body(FieldInfo): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, **extra: Any, ): self.embed = embed self.media_type = media_type + if example is not Undefined: + warnings.warn( + "`example` has been depreacated, please use `examples` instead", + category=DeprecationWarning, + stacklevel=1, + ) self.example = example kwargs = dict( default=default, @@ -301,6 +350,8 @@ class Body(FieldInfo): max_length=max_length, **extra, ) + if examples is not None: + kwargs["examples"] = examples if PYDANTIC_V2: kwargs["annotation"] = annotation kwargs["pattern"] = pattern or regex @@ -310,9 +361,6 @@ class Body(FieldInfo): super().__init__( **kwargs, ) - # TODO: pv2 decide how to handle OpenAPI examples vs JSON Schema examples - # and how to deprecate OpenAPI examples - self.examples = examples # type: ignore[assignment] def __repr__(self) -> str: return f"{self.__class__.__name__}({self.default})" @@ -336,8 +384,14 @@ class Form(Body): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, **extra: Any, ): super().__init__( @@ -380,8 +434,14 @@ class File(Form): max_length: Optional[int] = None, pattern: Optional[str] = None, regex: Optional[str] = None, - example: Any = Undefined, - examples: Optional[Dict[str, Any]] = None, + examples: Optional[List[Any]] = None, + example: Annotated[ + Optional[Any], + deprecated( + "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " + "although still supported. Use examples instead." + ), + ] = Undefined, **extra: Any, ): super().__init__( diff --git a/pyproject.toml b/pyproject.toml index 68c440bde..c421fefd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling"] +requires = ["hatchling >= 1.13.0"] build-backend = "hatchling.build" [project] @@ -43,12 +43,14 @@ classifiers = [ dependencies = [ "starlette>=0.27.0,<0.28.0", "pydantic>=1.7.4,!=1.8,!=1.8.1,<3.0.0", + "typing-extensions>=4.5.0", ] dynamic = ["version"] [project.urls] Homepage = "https://github.com/tiangolo/fastapi" Documentation = "https://fastapi.tiangolo.com/" +Repository = "https://github.com/tiangolo/fastapi" [project.optional-dependencies] all = [ diff --git a/requirements-docs.txt b/requirements-docs.txt index e9d0567ed..df60ca4df 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,8 +1,14 @@ -e . -mkdocs >=1.1.2,<2.0.0 -mkdocs-material >=8.1.4,<9.0.0 +mkdocs==1.4.3 +mkdocs-material==9.1.17 mdx-include >=1.4.1,<2.0.0 mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 typer-cli >=0.0.13,<0.0.14 typer[all] >=0.6.1,<0.8.0 pyyaml >=5.3.1,<7.0.0 +# For Material for MkDocs, Chinese search +jieba==0.42.1 +# For image processing by Material for MkDocs +pillow==9.5.0 +# For image processing by Material for MkDocs +cairosvg==2.7.0 diff --git a/requirements-tests.txt b/requirements-tests.txt index 915766f47..06ac8b551 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,13 +1,11 @@ -e . pytest >=7.1.3,<8.0.0 coverage[toml] >= 6.5.0,< 8.0 -dirty-equals >= 0.6.0 - -mypy ==1.3.0 -ruff ==0.0.272 +mypy ==1.4.0 +ruff ==0.0.275 black == 23.3.0 -httpx >=0.23.0,<0.24.0 -email_validator >=2.0.0,<3.0.0 +httpx >=0.23.0,<0.25.0 +email_validator >=1.1.1,<3.0.0 # TODO: once removing databases from tutorial, upgrade SQLAlchemy # probably when including SQLModel sqlalchemy >=1.3.18,<1.4.43 diff --git a/requirements.txt b/requirements.txt index cb9abb44a..7e746016a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -e .[all] -r requirements-tests.txt -r requirements-docs.txt -uvicorn[standard] >=0.12.0,<0.21.0 -pre-commit >=2.17.0,<3.0.0 +uvicorn[standard] >=0.12.0,<0.23.0 +pre-commit >=2.17.0,<4.0.0 diff --git a/scripts/docs.py b/scripts/docs.py index e0953b8ed..968dd9a3d 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -1,11 +1,15 @@ +import json +import logging import os import re import shutil import subprocess +from functools import lru_cache from http.server import HTTPServer, SimpleHTTPRequestHandler +from importlib import metadata from multiprocessing import Pool from pathlib import Path -from typing import Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Union import mkdocs.commands.build import mkdocs.commands.serve @@ -15,6 +19,8 @@ import typer import yaml from jinja2 import Template +logging.basicConfig(level=logging.INFO) + app = typer.Typer() mkdocs_name = "mkdocs.yml" @@ -26,19 +32,27 @@ missing_translation_snippet = """ docs_path = Path("docs") en_docs_path = Path("docs/en") en_config_path: Path = en_docs_path / mkdocs_name +site_path = Path("site").absolute() +build_site_path = Path("site_build").absolute() + +@lru_cache() +def is_mkdocs_insiders() -> bool: + version = metadata.version("mkdocs-material") + return "insiders" in version -def get_en_config() -> dict: + +def get_en_config() -> Dict[str, Any]: return mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8")) -def get_lang_paths(): +def get_lang_paths() -> List[Path]: return sorted(docs_path.iterdir()) -def lang_callback(lang: Optional[str]): +def lang_callback(lang: Optional[str]) -> Union[str, None]: if lang is None: - return + return None if not lang.isalpha() or len(lang) != 2: typer.echo("Use a 2 letter language code, like: es") raise typer.Abort() @@ -53,33 +67,12 @@ def complete_existing_lang(incomplete: str): yield lang_path.name -def get_base_lang_config(lang: str): - en_config = get_en_config() - fastapi_url_base = "https://fastapi.tiangolo.com/" - new_config = en_config.copy() - new_config["site_url"] = en_config["site_url"] + f"{lang}/" - new_config["theme"]["logo"] = fastapi_url_base + en_config["theme"]["logo"] - new_config["theme"]["favicon"] = fastapi_url_base + en_config["theme"]["favicon"] - new_config["theme"]["language"] = lang - new_config["nav"] = en_config["nav"][:2] - extra_css = [] - css: str - for css in en_config["extra_css"]: - if css.startswith("http"): - extra_css.append(css) - else: - extra_css.append(fastapi_url_base + css) - new_config["extra_css"] = extra_css - - extra_js = [] - js: str - for js in en_config["extra_javascript"]: - if js.startswith("http"): - extra_js.append(js) - else: - extra_js.append(fastapi_url_base + js) - new_config["extra_javascript"] = extra_js - return new_config +@app.callback() +def callback() -> None: + if is_mkdocs_insiders(): + os.environ["INSIDERS_FILE"] = "../en/mkdocs.insiders.yml" + # For MacOS with insiders and Cairo + os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = "/opt/homebrew/lib" @app.command() @@ -94,12 +87,8 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)): typer.echo(f"The language was already created: {lang}") raise typer.Abort() new_path.mkdir() - new_config = get_base_lang_config(lang) new_config_path: Path = Path(new_path) / mkdocs_name - new_config_path.write_text( - yaml.dump(new_config, sort_keys=False, width=200, allow_unicode=True), - encoding="utf-8", - ) + new_config_path.write_text("INHERIT: ../en/mkdocs.yml\n", encoding="utf-8") new_config_docs_path: Path = new_path / "docs" new_config_docs_path.mkdir() en_index_path: Path = en_docs_path / "docs" / "index.md" @@ -107,11 +96,8 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)): en_index_content = en_index_path.read_text(encoding="utf-8") new_index_content = f"{missing_translation_snippet}\n\n{en_index_content}" new_index_path.write_text(new_index_content, encoding="utf-8") - new_overrides_gitignore_path = new_path / "overrides" / ".gitignore" - new_overrides_gitignore_path.parent.mkdir(parents=True, exist_ok=True) - new_overrides_gitignore_path.write_text("") typer.secho(f"Successfully initialized: {new_path}", color=typer.colors.GREEN) - update_languages(lang=None) + update_languages() @app.command() @@ -119,89 +105,35 @@ def build_lang( lang: str = typer.Argument( ..., callback=lang_callback, autocompletion=complete_existing_lang ) -): +) -> None: """ - Build the docs for a language, filling missing pages with translation notifications. + Build the docs for a language. """ + insiders_env_file = os.environ.get("INSIDERS_FILE") + print(f"Insiders file {insiders_env_file}") + if is_mkdocs_insiders(): + print("Using insiders") lang_path: Path = Path("docs") / lang if not lang_path.is_dir(): typer.echo(f"The language translation doesn't seem to exist yet: {lang}") raise typer.Abort() typer.echo(f"Building docs for: {lang}") - build_dir_path = Path("docs_build") - build_dir_path.mkdir(exist_ok=True) - build_lang_path = build_dir_path / lang - en_lang_path = Path("docs/en") - site_path = Path("site").absolute() + build_site_dist_path = build_site_path / lang if lang == "en": dist_path = site_path + # Don't remove en dist_path as it might already contain other languages. + # When running build_all(), that function already removes site_path. + # All this is only relevant locally, on GitHub Actions all this is done through + # artifacts and multiple workflows, so it doesn't matter if directories are + # removed or not. else: - dist_path: Path = site_path / lang - shutil.rmtree(build_lang_path, ignore_errors=True) - shutil.copytree(lang_path, build_lang_path) - shutil.copytree(en_docs_path / "data", build_lang_path / "data") - overrides_src = en_docs_path / "overrides" - overrides_dest = build_lang_path / "overrides" - for path in overrides_src.iterdir(): - dest_path = overrides_dest / path.name - if not dest_path.exists(): - shutil.copy(path, dest_path) - en_config_path: Path = en_lang_path / mkdocs_name - en_config: dict = mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8")) - nav = en_config["nav"] - lang_config_path: Path = lang_path / mkdocs_name - lang_config: dict = mkdocs.utils.yaml_load( - lang_config_path.read_text(encoding="utf-8") - ) - lang_nav = lang_config["nav"] - # Exclude first 2 entries FastAPI and Languages, for custom handling - use_nav = nav[2:] - lang_use_nav = lang_nav[2:] - file_to_nav = get_file_to_nav_map(use_nav) - sections = get_sections(use_nav) - lang_file_to_nav = get_file_to_nav_map(lang_use_nav) - use_lang_file_to_nav = get_file_to_nav_map(lang_use_nav) - for file in file_to_nav: - file_path = Path(file) - lang_file_path: Path = build_lang_path / "docs" / file_path - en_file_path: Path = en_lang_path / "docs" / file_path - lang_file_path.parent.mkdir(parents=True, exist_ok=True) - if not lang_file_path.is_file(): - en_text = en_file_path.read_text(encoding="utf-8") - lang_text = get_text_with_translate_missing(en_text) - lang_file_path.write_text(lang_text, encoding="utf-8") - file_key = file_to_nav[file] - use_lang_file_to_nav[file] = file_key - if file_key: - composite_key = () - new_key = () - for key_part in file_key: - composite_key += (key_part,) - key_first_file = sections[composite_key] - if key_first_file in lang_file_to_nav: - new_key = lang_file_to_nav[key_first_file] - else: - new_key += (key_part,) - use_lang_file_to_nav[file] = new_key - key_to_section = {(): []} - for file, orig_file_key in file_to_nav.items(): - if file in use_lang_file_to_nav: - file_key = use_lang_file_to_nav[file] - else: - file_key = orig_file_key - section = get_key_section(key_to_section=key_to_section, key=file_key) - section.append(file) - new_nav = key_to_section[()] - export_lang_nav = [lang_nav[0], nav[1]] + new_nav - lang_config["nav"] = export_lang_nav - build_lang_config_path: Path = build_lang_path / mkdocs_name - build_lang_config_path.write_text( - yaml.dump(lang_config, sort_keys=False, width=200, allow_unicode=True), - encoding="utf-8", - ) + dist_path = site_path / lang + shutil.rmtree(dist_path, ignore_errors=True) current_dir = os.getcwd() - os.chdir(build_lang_path) - subprocess.run(["mkdocs", "build", "--site-dir", dist_path], check=True) + os.chdir(lang_path) + shutil.rmtree(build_site_dist_path, ignore_errors=True) + subprocess.run(["mkdocs", "build", "--site-dir", build_site_dist_path], check=True) + shutil.copytree(build_site_dist_path, dist_path, dirs_exist_ok=True) os.chdir(current_dir) typer.secho(f"Successfully built docs for: {lang}", color=typer.colors.GREEN) @@ -218,7 +150,7 @@ index_sponsors_template = """ """ -def generate_readme_content(): +def generate_readme_content() -> str: en_index = en_docs_path / "docs" / "index.md" content = en_index.read_text("utf-8") match_start = re.search(r"", content) @@ -238,7 +170,7 @@ def generate_readme_content(): @app.command() -def generate_readme(): +def generate_readme() -> None: """ Generate README.md content from main index.md """ @@ -249,7 +181,7 @@ def generate_readme(): @app.command() -def verify_readme(): +def verify_readme() -> None: """ Verify README.md content from main index.md """ @@ -266,23 +198,14 @@ def verify_readme(): @app.command() -def build_all(): +def build_all() -> None: """ Build mkdocs site for en, and then build each language inside, end result is located at directory ./site/ with each language inside. """ - site_path = Path("site").absolute() - update_languages(lang=None) - current_dir = os.getcwd() - os.chdir(en_docs_path) - typer.echo("Building docs for: en") - subprocess.run(["mkdocs", "build", "--site-dir", site_path], check=True) - os.chdir(current_dir) - langs = [] - for lang in get_lang_paths(): - if lang == en_docs_path or not lang.is_dir(): - continue - langs.append(lang.name) + update_languages() + shutil.rmtree(site_path, ignore_errors=True) + langs = [lang.name for lang in get_lang_paths() if lang.is_dir()] cpu_count = os.cpu_count() or 1 process_pool_size = cpu_count * 4 typer.echo(f"Using process pool size: {process_pool_size}") @@ -290,34 +213,16 @@ def build_all(): p.map(build_lang, langs) -def update_single_lang(lang: str): - lang_path = docs_path / lang - typer.echo(f"Updating {lang_path.name}") - update_config(lang_path.name) - - @app.command() -def update_languages( - lang: str = typer.Argument( - None, callback=lang_callback, autocompletion=complete_existing_lang - ) -): +def update_languages() -> None: """ Update the mkdocs.yml file Languages section including all the available languages. - - The LANG argument is a 2-letter language code. If it's not provided, update all the - mkdocs.yml files (for all the languages). """ - if lang is None: - for lang_path in get_lang_paths(): - if lang_path.is_dir(): - update_single_lang(lang_path.name) - else: - update_single_lang(lang) + update_config() @app.command() -def serve(): +def serve() -> None: """ A quick server to preview a built site with translations. @@ -343,7 +248,7 @@ def live( lang: str = typer.Argument( None, callback=lang_callback, autocompletion=complete_existing_lang ) -): +) -> None: """ Serve with livereload a docs site for a specific language. @@ -353,6 +258,8 @@ def live( Takes an optional LANG argument with the name of the language to serve, by default en. """ + # Enable line numbers during local development to make it easier to highlight + os.environ["LINENUMS"] = "true" if lang is None: lang = "en" lang_path: Path = docs_path / lang @@ -360,18 +267,8 @@ def live( mkdocs.commands.serve.serve(dev_addr="127.0.0.1:8008") -def update_config(lang: str): - lang_path: Path = docs_path / lang - config_path = lang_path / mkdocs_name - current_config: dict = mkdocs.utils.yaml_load( - config_path.read_text(encoding="utf-8") - ) - if lang == "en": - config = get_en_config() - else: - config = get_base_lang_config(lang) - config["nav"] = current_config["nav"] - config["theme"]["language"] = current_config["theme"]["language"] +def update_config() -> None: + config = get_en_config() languages = [{"en": "/"}] alternate: List[Dict[str, str]] = config["extra"].get("alternate", []) alternate_dict = {alt["link"]: alt["name"] for alt in alternate} @@ -391,61 +288,19 @@ def update_config(lang: str): new_alternate.append({"link": url, "name": use_name}) config["nav"][1] = {"Languages": languages} config["extra"]["alternate"] = new_alternate - config_path.write_text( + en_config_path.write_text( yaml.dump(config, sort_keys=False, width=200, allow_unicode=True), encoding="utf-8", ) -def get_key_section( - *, key_to_section: Dict[Tuple[str, ...], list], key: Tuple[str, ...] -) -> list: - if key in key_to_section: - return key_to_section[key] - super_key = key[:-1] - title = key[-1] - super_section = get_key_section(key_to_section=key_to_section, key=super_key) - new_section = [] - super_section.append({title: new_section}) - key_to_section[key] = new_section - return new_section - - -def get_text_with_translate_missing(text: str) -> str: - lines = text.splitlines() - lines.insert(1, missing_translation_snippet) - new_text = "\n".join(lines) - return new_text - - -def get_file_to_nav_map(nav: list) -> Dict[str, Tuple[str, ...]]: - file_to_nav = {} - for item in nav: - if type(item) is str: - file_to_nav[item] = () - elif type(item) is dict: - item_key = list(item.keys())[0] - sub_nav = item[item_key] - sub_file_to_nav = get_file_to_nav_map(sub_nav) - for k, v in sub_file_to_nav.items(): - file_to_nav[k] = (item_key,) + v - return file_to_nav - - -def get_sections(nav: list) -> Dict[Tuple[str, ...], str]: - sections = {} - for item in nav: - if type(item) is str: - continue - elif type(item) is dict: - item_key = list(item.keys())[0] - sub_nav = item[item_key] - sections[(item_key,)] = sub_nav[0] - sub_sections = get_sections(sub_nav) - for k, v in sub_sections.items(): - new_key = (item_key,) + k - sections[new_key] = v - return sections +@app.command() +def langs_json(): + langs = [] + for lang_path in get_lang_paths(): + if lang_path.is_dir(): + langs.append(lang_path.name) + print(json.dumps(langs)) if __name__ == "__main__": diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py new file mode 100644 index 000000000..008751f8a --- /dev/null +++ b/scripts/mkdocs_hooks.py @@ -0,0 +1,132 @@ +from functools import lru_cache +from pathlib import Path +from typing import Any, List, Union + +import material +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.structure.files import File, Files +from mkdocs.structure.nav import Link, Navigation, Section +from mkdocs.structure.pages import Page + + +@lru_cache() +def get_missing_translation_content(docs_dir: str) -> str: + docs_dir_path = Path(docs_dir) + missing_translation_path = docs_dir_path.parent.parent / "missing-translation.md" + return missing_translation_path.read_text(encoding="utf-8") + + +@lru_cache() +def get_mkdocs_material_langs() -> List[str]: + material_path = Path(material.__file__).parent + material_langs_path = material_path / "partials" / "languages" + langs = [file.stem for file in material_langs_path.glob("*.html")] + return langs + + +class EnFile(File): + pass + + +def on_config(config: MkDocsConfig, **kwargs: Any) -> MkDocsConfig: + available_langs = get_mkdocs_material_langs() + dir_path = Path(config.docs_dir) + lang = dir_path.parent.name + if lang in available_langs: + config.theme["language"] = lang + if not (config.site_url or "").endswith(f"{lang}/") and not lang == "en": + config.site_url = f"{config.site_url}{lang}/" + return config + + +def resolve_file(*, item: str, files: Files, config: MkDocsConfig) -> None: + item_path = Path(config.docs_dir) / item + if not item_path.is_file(): + en_src_dir = (Path(config.docs_dir) / "../../en/docs").resolve() + potential_path = en_src_dir / item + if potential_path.is_file(): + files.append( + EnFile( + path=item, + src_dir=str(en_src_dir), + dest_dir=config.site_dir, + use_directory_urls=config.use_directory_urls, + ) + ) + + +def resolve_files(*, items: List[Any], files: Files, config: MkDocsConfig) -> None: + for item in items: + if isinstance(item, str): + resolve_file(item=item, files=files, config=config) + elif isinstance(item, dict): + assert len(item) == 1 + values = list(item.values()) + if not values: + continue + if isinstance(values[0], str): + resolve_file(item=values[0], files=files, config=config) + elif isinstance(values[0], list): + resolve_files(items=values[0], files=files, config=config) + else: + raise ValueError(f"Unexpected value: {values}") + + +def on_files(files: Files, *, config: MkDocsConfig) -> Files: + resolve_files(items=config.nav or [], files=files, config=config) + if "logo" in config.theme: + resolve_file(item=config.theme["logo"], files=files, config=config) + if "favicon" in config.theme: + resolve_file(item=config.theme["favicon"], files=files, config=config) + resolve_files(items=config.extra_css, files=files, config=config) + resolve_files(items=config.extra_javascript, files=files, config=config) + return files + + +def generate_renamed_section_items( + items: List[Union[Page, Section, Link]], *, config: MkDocsConfig +) -> List[Union[Page, Section, Link]]: + new_items: List[Union[Page, Section, Link]] = [] + for item in items: + if isinstance(item, Section): + new_title = item.title + new_children = generate_renamed_section_items(item.children, config=config) + first_child = new_children[0] + if isinstance(first_child, Page): + if first_child.file.src_path.endswith("index.md"): + # Read the source so that the title is parsed and available + first_child.read_source(config=config) + new_title = first_child.title or new_title + # Creating a new section makes it render it collapsed by default + # no idea why, so, let's just modify the existing one + # new_section = Section(title=new_title, children=new_children) + item.title = new_title + item.children = new_children + new_items.append(item) + else: + new_items.append(item) + return new_items + + +def on_nav( + nav: Navigation, *, config: MkDocsConfig, files: Files, **kwargs: Any +) -> Navigation: + new_items = generate_renamed_section_items(nav.items, config=config) + return Navigation(items=new_items, pages=nav.pages) + + +def on_pre_page(page: Page, *, config: MkDocsConfig, files: Files) -> Page: + return page + + +def on_page_markdown( + markdown: str, *, page: Page, config: MkDocsConfig, files: Files +) -> str: + if isinstance(page.file, EnFile): + missing_translation_content = get_missing_translation_content(config.docs_dir) + header = "" + body = markdown + if markdown.startswith("#"): + header, _, body = markdown.partition("\n\n") + return f"{header}\n\n{missing_translation_content}\n\n{body}" + return markdown diff --git a/scripts/zip-docs.sh b/scripts/zip-docs.sh deleted file mode 100644 index 47c3b0977..000000000 --- a/scripts/zip-docs.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -x -set -e - -cd ./site - -if [ -f docs.zip ]; then - rm -rf docs.zip -fi -zip -r docs.zip ./ diff --git a/tests/test_additional_properties.py b/tests/test_additional_properties.py index 516a569e4..be14d10ed 100644 --- a/tests/test_additional_properties.py +++ b/tests/test_additional_properties.py @@ -29,7 +29,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/foo": { diff --git a/tests/test_additional_response_extra.py b/tests/test_additional_response_extra.py index d62638c8f..55be19bad 100644 --- a/tests/test_additional_response_extra.py +++ b/tests/test_additional_response_extra.py @@ -30,7 +30,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_additional_responses_bad.py b/tests/test_additional_responses_bad.py index d2eb4d7a1..36df07f46 100644 --- a/tests/test_additional_responses_bad.py +++ b/tests/test_additional_responses_bad.py @@ -11,7 +11,7 @@ async def a(): openapi_schema = { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a": { diff --git a/tests/test_additional_responses_custom_model_in_callback.py b/tests/test_additional_responses_custom_model_in_callback.py index 828565b24..2ad575455 100644 --- a/tests/test_additional_responses_custom_model_in_callback.py +++ b/tests/test_additional_responses_custom_model_in_callback.py @@ -33,7 +33,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_additional_responses_custom_validationerror.py b/tests/test_additional_responses_custom_validationerror.py index 052602768..9fec5c96d 100644 --- a/tests/test_additional_responses_custom_validationerror.py +++ b/tests/test_additional_responses_custom_validationerror.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a/{id}": { diff --git a/tests/test_additional_responses_default_validationerror.py b/tests/test_additional_responses_default_validationerror.py index 58de46ff6..153f04f57 100644 --- a/tests/test_additional_responses_default_validationerror.py +++ b/tests/test_additional_responses_default_validationerror.py @@ -16,7 +16,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a/{id}": { diff --git a/tests/test_additional_responses_response_class.py b/tests/test_additional_responses_response_class.py index 6746760f0..68753561c 100644 --- a/tests/test_additional_responses_response_class.py +++ b/tests/test_additional_responses_response_class.py @@ -42,7 +42,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a": { diff --git a/tests/test_additional_responses_router.py b/tests/test_additional_responses_router.py index 58d54b733..71cabc7c3 100644 --- a/tests/test_additional_responses_router.py +++ b/tests/test_additional_responses_router.py @@ -85,7 +85,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a": { diff --git a/tests/test_annotated.py b/tests/test_annotated.py index 2ef7894b4..541f84bca 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -145,7 +145,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/default": { diff --git a/tests/test_application.py b/tests/test_application.py index f68bb2171..ea7a80128 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -56,7 +56,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/api_route": { diff --git a/tests/test_custom_route_class.py b/tests/test_custom_route_class.py index d1b18ef1d..55374584b 100644 --- a/tests/test_custom_route_class.py +++ b/tests/test_custom_route_class.py @@ -75,7 +75,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a/": { diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py index 7d01cb8aa..0882cc41d 100644 --- a/tests/test_dependency_duplicates.py +++ b/tests/test_dependency_duplicates.py @@ -103,7 +103,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/with-duplicates": { diff --git a/tests/test_deprecated_openapi_prefix.py b/tests/test_deprecated_openapi_prefix.py index 688b9837f..ec7366d2a 100644 --- a/tests/test_deprecated_openapi_prefix.py +++ b/tests/test_deprecated_openapi_prefix.py @@ -22,7 +22,7 @@ def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/app": { diff --git a/tests/test_duplicate_models_openapi.py b/tests/test_duplicate_models_openapi.py index 116b2006a..83e86d231 100644 --- a/tests/test_duplicate_models_openapi.py +++ b/tests/test_duplicate_models_openapi.py @@ -36,7 +36,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py index bf05aa585..b64f8341b 100644 --- a/tests/test_enforce_once_required_parameter.py +++ b/tests/test_enforce_once_required_parameter.py @@ -57,7 +57,7 @@ expected_schema = { } }, "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.0.2", + "openapi": "3.1.0", "paths": { "/foo": { "get": { diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py index d2781e7b6..bd16fe925 100644 --- a/tests/test_extra_routes.py +++ b/tests/test_extra_routes.py @@ -100,7 +100,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py b/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py index 9e50f8321..48732dbf0 100644 --- a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py +++ b/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py @@ -42,7 +42,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/model/{name}": { diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py index 4c4d77943..656332a01 100644 --- a/tests/test_filter_pydantic_sub_model_pv2.py +++ b/tests/test_filter_pydantic_sub_model_pv2.py @@ -86,7 +86,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/model/{name}": { diff --git a/tests/test_generate_unique_id_function.py b/tests/test_generate_unique_id_function.py index 0b519f859..c5ef5182b 100644 --- a/tests/test_generate_unique_id_function.py +++ b/tests/test_generate_unique_id_function.py @@ -48,7 +48,7 @@ def test_top_level_generate_unique_id(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { @@ -249,7 +249,7 @@ def test_router_overrides_generate_unique_id(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { @@ -450,7 +450,7 @@ def test_router_include_overrides_generate_unique_id(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { @@ -661,7 +661,7 @@ def test_subrouter_top_level_include_overrides_generate_unique_id(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { @@ -928,7 +928,7 @@ def test_router_path_operation_overrides_generate_unique_id(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { @@ -1136,7 +1136,7 @@ def test_app_path_operation_overrides_generate_unique_id(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { @@ -1353,7 +1353,7 @@ def test_callback_override_generate_unique_id(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_get_request_body.py b/tests/test_get_request_body.py index 541147fa8..cc567b88f 100644 --- a/tests/test_get_request_body.py +++ b/tests/test_get_request_body.py @@ -29,7 +29,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/product": { diff --git a/tests/test_include_router_defaults_overrides.py b/tests/test_include_router_defaults_overrides.py index ced56c84d..33baa25e6 100644 --- a/tests/test_include_router_defaults_overrides.py +++ b/tests/test_include_router_defaults_overrides.py @@ -443,7 +443,7 @@ def test_openapi(): assert issubclass(w[-1].category, UserWarning) assert "Duplicate Operation ID" in str(w[-1].message) assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/override1": { diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py index ba4ebc7dd..e3d57bb42 100644 --- a/tests/test_infer_param_optionality.py +++ b/tests/test_infer_param_optionality.py @@ -109,7 +109,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 5fd63ac72..ff3033ecd 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -1,3 +1,4 @@ +from collections import deque from dataclasses import dataclass from datetime import datetime, timezone from decimal import Decimal @@ -299,3 +300,12 @@ def test_decimal_encoder_float(): def test_decimal_encoder_int(): data = {"value": Decimal(2)} assert jsonable_encoder(data) == {"value": 2} + + +def test_encode_deque_encodes_child_models(): + class Model(BaseModel): + test: str + + dq = deque([Model(test="test")]) + + assert jsonable_encoder(dq)[0]["test"] == "test" diff --git a/tests/test_modules_same_name_body/test_main.py b/tests/test_modules_same_name_body/test_main.py index 1ebcaee8c..cc165bdca 100644 --- a/tests/test_modules_same_name_body/test_main.py +++ b/tests/test_modules_same_name_body/test_main.py @@ -35,7 +35,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a/compute": { diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py index 391fbd207..aa989c612 100644 --- a/tests/test_multi_body_errors.py +++ b/tests/test_multi_body_errors.py @@ -138,7 +138,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py index 888c412c8..470a35808 100644 --- a/tests/test_multi_query_errors.py +++ b/tests/test_multi_query_errors.py @@ -67,7 +67,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py index 133ce3327..dc7147c71 100644 --- a/tests/test_openapi_query_parameter_extension.py +++ b/tests/test_openapi_query_parameter_extension.py @@ -43,7 +43,7 @@ def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_openapi_route_extensions.py b/tests/test_openapi_route_extensions.py index 943dc43f2..3a3099436 100644 --- a/tests/test_openapi_route_extensions.py +++ b/tests/test_openapi_route_extensions.py @@ -22,7 +22,7 @@ def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_openapi_servers.py b/tests/test_openapi_servers.py index bd7eb2049..8697c8438 100644 --- a/tests/test_openapi_servers.py +++ b/tests/test_openapi_servers.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "servers": [ {"url": "/", "description": "Default, relative server"}, diff --git a/tests/test_param_in_path_and_dependency.py b/tests/test_param_in_path_and_dependency.py index 0aef7ac7b..08eb0f40f 100644 --- a/tests/test_param_in_path_and_dependency.py +++ b/tests/test_param_in_path_and_dependency.py @@ -25,7 +25,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/{user_id}": { diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py index cb182a1cd..26201e9e2 100644 --- a/tests/test_param_include_in_schema.py +++ b/tests/test_param_include_in_schema.py @@ -33,8 +33,8 @@ async def hidden_query( return {"hidden_query": hidden_query} -openapi_shema = { - "openapi": "3.0.2", +openapi_schema = { + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/hidden_cookie": { @@ -162,7 +162,7 @@ def test_openapi_schema(): client = TestClient(app) response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == openapi_shema + assert response.json() == openapi_schema @pytest.mark.parametrize( diff --git a/tests/test_put_no_body.py b/tests/test_put_no_body.py index a02d1152c..8f4c82532 100644 --- a/tests/test_put_no_body.py +++ b/tests/test_put_no_body.py @@ -28,7 +28,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_repeated_dependency_schema.py b/tests/test_repeated_dependency_schema.py index ca0305184..d7d0dfa05 100644 --- a/tests/test_repeated_dependency_schema.py +++ b/tests/test_repeated_dependency_schema.py @@ -50,7 +50,7 @@ schema = { } }, "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.0.2", + "openapi": "3.1.0", "paths": { "/": { "get": { diff --git a/tests/test_repeated_parameter_alias.py b/tests/test_repeated_parameter_alias.py index c656a161d..fd72eaab2 100644 --- a/tests/test_repeated_parameter_alias.py +++ b/tests/test_repeated_parameter_alias.py @@ -58,7 +58,7 @@ def test_openapi_schema(): } }, "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.0.2", + "openapi": "3.1.0", "paths": { "/{repeated_alias}": { "get": { diff --git a/tests/test_reponse_set_reponse_code_empty.py b/tests/test_reponse_set_reponse_code_empty.py index 14770fed0..bf3aa758c 100644 --- a/tests/test_reponse_set_reponse_code_empty.py +++ b/tests/test_reponse_set_reponse_code_empty.py @@ -32,7 +32,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/{id}": { diff --git a/tests/test_request_body_parameters_media_type.py b/tests/test_request_body_parameters_media_type.py index fe8077c81..8c72fee54 100644 --- a/tests/test_request_body_parameters_media_type.py +++ b/tests/test_request_body_parameters_media_type.py @@ -40,7 +40,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/products": { diff --git a/tests/test_response_by_alias.py b/tests/test_response_by_alias.py index d05c08486..e162cd39b 100644 --- a/tests/test_response_by_alias.py +++ b/tests/test_response_by_alias.py @@ -150,7 +150,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/dict": { diff --git a/tests/test_response_class_no_mediatype.py b/tests/test_response_class_no_mediatype.py index 2d75c7535..706929ac3 100644 --- a/tests/test_response_class_no_mediatype.py +++ b/tests/test_response_class_no_mediatype.py @@ -42,7 +42,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a": { diff --git a/tests/test_response_code_no_body.py b/tests/test_response_code_no_body.py index 3851a325d..3ca8708f1 100644 --- a/tests/test_response_code_no_body.py +++ b/tests/test_response_code_no_body.py @@ -50,7 +50,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/a": { diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index 1ab93f6a2..85dd450eb 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -507,7 +507,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/no_response_model-no_annotation-return_model": { diff --git a/tests/test_response_model_sub_types.py b/tests/test_response_model_sub_types.py index e462006ff..660bcee1b 100644 --- a/tests/test_response_model_sub_types.py +++ b/tests/test_response_model_sub_types.py @@ -50,7 +50,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/valid1": { diff --git a/tests/test_router_redirect_slashes.py b/tests/test_router_redirect_slashes.py new file mode 100644 index 000000000..086665c04 --- /dev/null +++ b/tests/test_router_redirect_slashes.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient + + +def test_redirect_slashes_enabled(): + app = FastAPI() + router = APIRouter() + + @router.get("/hello/") + def hello_page() -> str: + return "Hello, World!" + + app.include_router(router) + + client = TestClient(app) + + response = client.get("/hello/", follow_redirects=False) + assert response.status_code == 200 + + response = client.get("/hello", follow_redirects=False) + assert response.status_code == 307 + + +def test_redirect_slashes_disabled(): + app = FastAPI(redirect_slashes=False) + router = APIRouter() + + @router.get("/hello/") + def hello_page() -> str: + return "Hello, World!" + + app.include_router(router) + + client = TestClient(app) + + response = client.get("/hello/", follow_redirects=False) + assert response.status_code == 200 + + response = client.get("/hello", follow_redirects=False) + assert response.status_code == 404 diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index eb9fd474c..a1505afe2 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -1,248 +1,228 @@ from typing import Union +import pytest from dirty_equals import IsDict from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict -app = FastAPI() +def create_app(): + app = FastAPI() -class Item(BaseModel): - data: str + class Item(BaseModel): + data: str - if PYDANTIC_V2: - model_config = ConfigDict( - json_schema_extra={"example": {"data": "Data in schema_extra"}} - ) - else: - - class Config: - schema_extra = {"example": {"data": "Data in schema_extra"}} - - -@app.post("/schema_extra/") -def schema_extra(item: Item): - return item + if PYDANTIC_V2: + model_config = ConfigDict( + json_schema_extra={"example": {"data": "Data in schema_extra"}} + ) + else: + class Config: + schema_extra = {"example": {"data": "Data in schema_extra"}} -@app.post("/example/") -def example(item: Item = Body(example={"data": "Data in Body example"})): - return item + @app.post("/schema_extra/") + def schema_extra(item: Item): + return item + with pytest.warns(DeprecationWarning): -@app.post("/examples/") -def examples( - item: Item = Body( - examples={ - "example1": { - "summary": "example1 summary", - "value": {"data": "Data in Body examples, example1"}, - }, - "example2": {"value": {"data": "Data in Body examples, example2"}}, - }, - ) -): - return item - - -@app.post("/example_examples/") -def example_examples( - item: Item = Body( - example={"data": "Overriden example"}, - examples={ - "example1": {"value": {"data": "examples example_examples 1"}}, - "example2": {"value": {"data": "examples example_examples 2"}}, - }, - ) -): - return item - - -# TODO: enable these tests once/if Form(embed=False) is supported -# TODO: In that case, define if File() should support example/examples too -# @app.post("/form_example") -# def form_example(firstname: str = Form(example="John")): -# return firstname - - -# @app.post("/form_examples") -# def form_examples( -# lastname: str = Form( -# ..., -# examples={ -# "example1": {"summary": "last name summary", "value": "Doe"}, -# "example2": {"value": "Doesn't"}, -# }, -# ), -# ): -# return lastname - - -# @app.post("/form_example_examples") -# def form_example_examples( -# lastname: str = Form( -# ..., -# example="Doe overriden", -# examples={ -# "example1": {"summary": "last name summary", "value": "Doe"}, -# "example2": {"value": "Doesn't"}, -# }, -# ), -# ): -# return lastname - - -@app.get("/path_example/{item_id}") -def path_example( - item_id: str = Path( - example="item_1", - ), -): - return item_id - - -@app.get("/path_examples/{item_id}") -def path_examples( - item_id: str = Path( - examples={ - "example1": {"summary": "item ID summary", "value": "item_1"}, - "example2": {"value": "item_2"}, - }, - ), -): - return item_id - - -@app.get("/path_example_examples/{item_id}") -def path_example_examples( - item_id: str = Path( - example="item_overriden", - examples={ - "example1": {"summary": "item ID summary", "value": "item_1"}, - "example2": {"value": "item_2"}, - }, - ), -): - return item_id - - -@app.get("/query_example/") -def query_example( - data: Union[str, None] = Query( - default=None, - example="query1", - ), -): - return data - - -@app.get("/query_examples/") -def query_examples( - data: Union[str, None] = Query( - default=None, - examples={ - "example1": {"summary": "Query example 1", "value": "query1"}, - "example2": {"value": "query2"}, - }, - ), -): - return data - - -@app.get("/query_example_examples/") -def query_example_examples( - data: Union[str, None] = Query( - default=None, - example="query_overriden", - examples={ - "example1": {"summary": "Query example 1", "value": "query1"}, - "example2": {"value": "query2"}, - }, - ), -): - return data - - -@app.get("/header_example/") -def header_example( - data: Union[str, None] = Header( - default=None, - example="header1", - ), -): - return data - - -@app.get("/header_examples/") -def header_examples( - data: Union[str, None] = Header( - default=None, - examples={ - "example1": {"summary": "header example 1", "value": "header1"}, - "example2": {"value": "header2"}, - }, - ), -): - return data - - -@app.get("/header_example_examples/") -def header_example_examples( - data: Union[str, None] = Header( - default=None, - example="header_overriden", - examples={ - "example1": {"summary": "Query example 1", "value": "header1"}, - "example2": {"value": "header2"}, - }, - ), -): - return data - - -@app.get("/cookie_example/") -def cookie_example( - data: Union[str, None] = Cookie( - default=None, - example="cookie1", - ), -): - return data - - -@app.get("/cookie_examples/") -def cookie_examples( - data: Union[str, None] = Cookie( - default=None, - examples={ - "example1": {"summary": "cookie example 1", "value": "cookie1"}, - "example2": {"value": "cookie2"}, - }, - ), -): - return data - - -@app.get("/cookie_example_examples/") -def cookie_example_examples( - data: Union[str, None] = Cookie( - default=None, - example="cookie_overriden", - examples={ - "example1": {"summary": "Query example 1", "value": "cookie1"}, - "example2": {"value": "cookie2"}, - }, - ), -): - return data + @app.post("/example/") + def example(item: Item = Body(example={"data": "Data in Body example"})): + return item - -client = TestClient(app) + @app.post("/examples/") + def examples( + item: Item = Body( + examples=[ + {"data": "Data in Body examples, example1"}, + {"data": "Data in Body examples, example2"}, + ], + ) + ): + return item + + with pytest.warns(DeprecationWarning): + + @app.post("/example_examples/") + def example_examples( + item: Item = Body( + example={"data": "Overridden example"}, + examples=[ + {"data": "examples example_examples 1"}, + {"data": "examples example_examples 2"}, + ], + ) + ): + return item + + # TODO: enable these tests once/if Form(embed=False) is supported + # TODO: In that case, define if File() should support example/examples too + # @app.post("/form_example") + # def form_example(firstname: str = Form(example="John")): + # return firstname + + # @app.post("/form_examples") + # def form_examples( + # lastname: str = Form( + # ..., + # examples={ + # "example1": {"summary": "last name summary", "value": "Doe"}, + # "example2": {"value": "Doesn't"}, + # }, + # ), + # ): + # return lastname + + # @app.post("/form_example_examples") + # def form_example_examples( + # lastname: str = Form( + # ..., + # example="Doe overridden", + # examples={ + # "example1": {"summary": "last name summary", "value": "Doe"}, + # "example2": {"value": "Doesn't"}, + # }, + # ), + # ): + # return lastname + + with pytest.warns(DeprecationWarning): + + @app.get("/path_example/{item_id}") + def path_example( + item_id: str = Path( + example="item_1", + ), + ): + return item_id + + @app.get("/path_examples/{item_id}") + def path_examples( + item_id: str = Path( + examples=["item_1", "item_2"], + ), + ): + return item_id + + with pytest.warns(DeprecationWarning): + + @app.get("/path_example_examples/{item_id}") + def path_example_examples( + item_id: str = Path( + example="item_overridden", + examples=["item_1", "item_2"], + ), + ): + return item_id + + with pytest.warns(DeprecationWarning): + + @app.get("/query_example/") + def query_example( + data: Union[str, None] = Query( + default=None, + example="query1", + ), + ): + return data + + @app.get("/query_examples/") + def query_examples( + data: Union[str, None] = Query( + default=None, + examples=["query1", "query2"], + ), + ): + return data + + with pytest.warns(DeprecationWarning): + + @app.get("/query_example_examples/") + def query_example_examples( + data: Union[str, None] = Query( + default=None, + example="query_overridden", + examples=["query1", "query2"], + ), + ): + return data + + with pytest.warns(DeprecationWarning): + + @app.get("/header_example/") + def header_example( + data: Union[str, None] = Header( + default=None, + example="header1", + ), + ): + return data + + @app.get("/header_examples/") + def header_examples( + data: Union[str, None] = Header( + default=None, + examples=[ + "header1", + "header2", + ], + ), + ): + return data + + with pytest.warns(DeprecationWarning): + + @app.get("/header_example_examples/") + def header_example_examples( + data: Union[str, None] = Header( + default=None, + example="header_overridden", + examples=["header1", "header2"], + ), + ): + return data + + with pytest.warns(DeprecationWarning): + + @app.get("/cookie_example/") + def cookie_example( + data: Union[str, None] = Cookie( + default=None, + example="cookie1", + ), + ): + return data + + @app.get("/cookie_examples/") + def cookie_examples( + data: Union[str, None] = Cookie( + default=None, + examples=["cookie1", "cookie2"], + ), + ): + return data + + with pytest.warns(DeprecationWarning): + + @app.get("/cookie_example_examples/") + def cookie_example_examples( + data: Union[str, None] = Cookie( + default=None, + example="cookie_overridden", + examples=["cookie1", "cookie2"], + ), + ): + return data + + return app def test_call_api(): + app = create_app() + client = TestClient(app) response = client.post("/schema_extra/", json={"data": "Foo"}) assert response.status_code == 200, response.text response = client.post("/example/", json={"data": "Foo"}) @@ -285,10 +265,12 @@ def test_openapi_schema(): * Body(example={}) overrides schema_extra in pydantic model * Body(examples{}) overrides Body(example={}) and schema_extra in pydantic model """ + app = create_app() + client = TestClient(app) response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/schema_extra/": { @@ -359,20 +341,28 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "example1": { - "summary": "example1 summary", - "value": { - "data": "Data in Body examples, example1" - }, - }, - "example2": { - "value": { - "data": "Data in Body examples, example2" - } - }, - }, + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "Data in Body examples, example1"}, + {"data": "Data in Body examples, example2"}, + ], + } + ) + | IsDict( + # TODO: remove this when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + {"data": "Data in Body examples, example1"}, + {"data": "Data in Body examples, example2"}, + ], + } + ) } }, "required": True, @@ -402,15 +392,29 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "example1": { - "value": {"data": "examples example_examples 1"} - }, - "example2": { - "value": {"data": "examples example_examples 2"} + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "examples example_examples 1"}, + {"data": "examples example_examples 2"}, + ], + } + ) + | IsDict( + # TODO: remove this when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + {"data": "examples example_examples 1"}, + {"data": "examples example_examples 2"}, + ], }, - }, + ), + "example": {"data": "Overridden example"}, } }, "required": True, @@ -471,13 +475,10 @@ def test_openapi_schema(): "parameters": [ { "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "examples": { - "example1": { - "summary": "item ID summary", - "value": "item_1", - }, - "example2": {"value": "item_2"}, + "schema": { + "title": "Item Id", + "type": "string", + "examples": ["item_1", "item_2"], }, "name": "item_id", "in": "path", @@ -508,14 +509,12 @@ def test_openapi_schema(): "parameters": [ { "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "examples": { - "example1": { - "summary": "item ID summary", - "value": "item_1", - }, - "example2": {"value": "item_2"}, + "schema": { + "title": "Item Id", + "type": "string", + "examples": ["item_1", "item_2"], }, + "example": "item_overridden", "name": "item_id", "in": "path", } @@ -589,19 +588,17 @@ def test_openapi_schema(): { "anyOf": [{"type": "string"}, {"type": "null"}], "title": "Data", + "examples": ["query1", "query2"], } ) | IsDict( # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} + { + "type": "string", + "title": "Data", + "examples": ["query1", "query2"], + } ), - "examples": { - "example1": { - "summary": "Query example 1", - "value": "query1", - }, - "example2": {"value": "query2"}, - }, "name": "data", "in": "query", } @@ -635,19 +632,18 @@ def test_openapi_schema(): { "anyOf": [{"type": "string"}, {"type": "null"}], "title": "Data", + "examples": ["query1", "query2"], } ) | IsDict( # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} + { + "type": "string", + "title": "Data", + "examples": ["query1", "query2"], + } ), - "examples": { - "example1": { - "summary": "Query example 1", - "value": "query1", - }, - "example2": {"value": "query2"}, - }, + "example": "query_overridden", "name": "data", "in": "query", } @@ -721,19 +717,17 @@ def test_openapi_schema(): { "anyOf": [{"type": "string"}, {"type": "null"}], "title": "Data", + "examples": ["header1", "header2"], } ) | IsDict( # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} + { + "type": "string", + "title": "Data", + "examples": ["header1", "header2"], + } ), - "examples": { - "example1": { - "summary": "header example 1", - "value": "header1", - }, - "example2": {"value": "header2"}, - }, "name": "data", "in": "header", } @@ -767,19 +761,18 @@ def test_openapi_schema(): { "anyOf": [{"type": "string"}, {"type": "null"}], "title": "Data", + "examples": ["header1", "header2"], } ) | IsDict( # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} + { + "title": "Data", + "type": "string", + "examples": ["header1", "header2"], + } ), - "examples": { - "example1": { - "summary": "Query example 1", - "value": "header1", - }, - "example2": {"value": "header2"}, - }, + "example": "header_overridden", "name": "data", "in": "header", } @@ -853,19 +846,17 @@ def test_openapi_schema(): { "anyOf": [{"type": "string"}, {"type": "null"}], "title": "Data", + "examples": ["cookie1", "cookie2"], } ) | IsDict( # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} + { + "title": "Data", + "type": "string", + "examples": ["cookie1", "cookie2"], + } ), - "examples": { - "example1": { - "summary": "cookie example 1", - "value": "cookie1", - }, - "example2": {"value": "cookie2"}, - }, "name": "data", "in": "cookie", } @@ -899,19 +890,18 @@ def test_openapi_schema(): { "anyOf": [{"type": "string"}, {"type": "null"}], "title": "Data", + "examples": ["cookie1", "cookie2"], } ) | IsDict( # TODO: Remove this when deprecating Pydantic v1 - {"title": "Data", "type": "string"} + { + "title": "Data", + "type": "string", + "examples": ["cookie1", "cookie2"], + } ), - "examples": { - "example1": { - "summary": "Query example 1", - "value": "cookie1", - }, - "example2": {"value": "cookie2"}, - }, + "example": "cookie_overridden", "name": "data", "in": "cookie", } diff --git a/tests/test_security_api_key_cookie.py b/tests/test_security_api_key_cookie.py index b1c648c55..4ddb8e2ee 100644 --- a/tests/test_security_api_key_cookie.py +++ b/tests/test_security_api_key_cookie.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_cookie_description.py b/tests/test_security_api_key_cookie_description.py index ac8b4abf8..d99d616e0 100644 --- a/tests/test_security_api_key_cookie_description.py +++ b/tests/test_security_api_key_cookie_description.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_cookie_optional.py b/tests/test_security_api_key_cookie_optional.py index b8c440c9d..cb5590168 100644 --- a/tests/test_security_api_key_cookie_optional.py +++ b/tests/test_security_api_key_cookie_optional.py @@ -48,7 +48,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_header.py b/tests/test_security_api_key_header.py index 96ad80b54..1ff883703 100644 --- a/tests/test_security_api_key_header.py +++ b/tests/test_security_api_key_header.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_header_description.py b/tests/test_security_api_key_header_description.py index 382f53dd7..27f9d0f29 100644 --- a/tests/test_security_api_key_header_description.py +++ b/tests/test_security_api_key_header_description.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_header_optional.py b/tests/test_security_api_key_header_optional.py index adfb20ba0..6f9682a64 100644 --- a/tests/test_security_api_key_header_optional.py +++ b/tests/test_security_api_key_header_optional.py @@ -47,7 +47,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_query.py b/tests/test_security_api_key_query.py index da98eafd6..dc7a0a621 100644 --- a/tests/test_security_api_key_query.py +++ b/tests/test_security_api_key_query.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_query_description.py b/tests/test_security_api_key_query_description.py index 3c08afc5f..35dc7743a 100644 --- a/tests/test_security_api_key_query_description.py +++ b/tests/test_security_api_key_query_description.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_api_key_query_optional.py b/tests/test_security_api_key_query_optional.py index 99a26cfd0..4cc134bd4 100644 --- a/tests/test_security_api_key_query_optional.py +++ b/tests/test_security_api_key_query_optional.py @@ -47,7 +47,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_base.py b/tests/test_security_http_base.py index d3a754203..51928bafd 100644 --- a/tests/test_security_http_base.py +++ b/tests/test_security_http_base.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_base_description.py b/tests/test_security_http_base_description.py index 3d7d15016..bc79f3242 100644 --- a/tests/test_security_http_base_description.py +++ b/tests/test_security_http_base_description.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_base_optional.py b/tests/test_security_http_base_optional.py index 180c9110e..dd4d76843 100644 --- a/tests/test_security_http_base_optional.py +++ b/tests/test_security_http_base_optional.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py index 7e7fcac32..9b6cb6c45 100644 --- a/tests/test_security_http_basic_optional.py +++ b/tests/test_security_http_basic_optional.py @@ -54,7 +54,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_basic_realm.py b/tests/test_security_http_basic_realm.py index 470afd662..9fc33971a 100644 --- a/tests/test_security_http_basic_realm.py +++ b/tests/test_security_http_basic_realm.py @@ -52,7 +52,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_basic_realm_description.py b/tests/test_security_http_basic_realm_description.py index 44289007b..02122442e 100644 --- a/tests/test_security_http_basic_realm_description.py +++ b/tests/test_security_http_basic_realm_description.py @@ -52,7 +52,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_bearer.py b/tests/test_security_http_bearer.py index f24869fc3..5b9e2d691 100644 --- a/tests/test_security_http_bearer.py +++ b/tests/test_security_http_bearer.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_bearer_description.py b/tests/test_security_http_bearer_description.py index 6d5ad0b8e..2f11c3a14 100644 --- a/tests/test_security_http_bearer_description.py +++ b/tests/test_security_http_bearer_description.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_bearer_optional.py b/tests/test_security_http_bearer_optional.py index b596ac730..943da2ee2 100644 --- a/tests/test_security_http_bearer_optional.py +++ b/tests/test_security_http_bearer_optional.py @@ -43,7 +43,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_digest.py b/tests/test_security_http_digest.py index 2a25efe02..133d35763 100644 --- a/tests/test_security_http_digest.py +++ b/tests/test_security_http_digest.py @@ -39,7 +39,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_digest_description.py b/tests/test_security_http_digest_description.py index 721f7cfde..4e31a0c00 100644 --- a/tests/test_security_http_digest_description.py +++ b/tests/test_security_http_digest_description.py @@ -39,7 +39,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_http_digest_optional.py b/tests/test_security_http_digest_optional.py index d4c3597bc..1e6eb8bd7 100644 --- a/tests/test_security_http_digest_optional.py +++ b/tests/test_security_http_digest_optional.py @@ -45,7 +45,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py index 04760abd7..e98f80ebf 100644 --- a/tests/test_security_oauth2.py +++ b/tests/test_security_oauth2.py @@ -196,7 +196,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/login": { diff --git a/tests/test_security_oauth2_authorization_code_bearer.py b/tests/test_security_oauth2_authorization_code_bearer.py index 6df81528d..f2097b149 100644 --- a/tests/test_security_oauth2_authorization_code_bearer.py +++ b/tests/test_security_oauth2_authorization_code_bearer.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_security_oauth2_authorization_code_bearer_description.py b/tests/test_security_oauth2_authorization_code_bearer_description.py index c119abde4..5386fbbd9 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_description.py +++ b/tests/test_security_oauth2_authorization_code_bearer_description.py @@ -44,7 +44,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index 1f5a7017a..d06c01bba 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -200,7 +200,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/login": { diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index c795757de..9287e4366 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -201,7 +201,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/login": { diff --git a/tests/test_security_oauth2_password_bearer_optional.py b/tests/test_security_oauth2_password_bearer_optional.py index e5dcbb553..4c9362c3e 100644 --- a/tests/test_security_oauth2_password_bearer_optional.py +++ b/tests/test_security_oauth2_password_bearer_optional.py @@ -41,7 +41,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_security_oauth2_password_bearer_optional_description.py b/tests/test_security_oauth2_password_bearer_optional_description.py index 9ff48e715..6e6ea846c 100644 --- a/tests/test_security_oauth2_password_bearer_optional_description.py +++ b/tests/test_security_oauth2_password_bearer_optional_description.py @@ -45,7 +45,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_security_openid_connect.py b/tests/test_security_openid_connect.py index 206de6574..1e322e640 100644 --- a/tests/test_security_openid_connect.py +++ b/tests/test_security_openid_connect.py @@ -47,7 +47,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_openid_connect_description.py b/tests/test_security_openid_connect_description.py index 5884de793..44cf57f86 100644 --- a/tests/test_security_openid_connect_description.py +++ b/tests/test_security_openid_connect_description.py @@ -49,7 +49,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_security_openid_connect_optional.py b/tests/test_security_openid_connect_optional.py index 8ac719118..e817434b0 100644 --- a/tests/test_security_openid_connect_optional.py +++ b/tests/test_security_openid_connect_optional.py @@ -53,7 +53,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_starlette_exception.py b/tests/test_starlette_exception.py index 96f835b93..229fe8016 100644 --- a/tests/test_starlette_exception.py +++ b/tests/test_starlette_exception.py @@ -80,7 +80,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/http-no-body-statuscode-exception": { diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py index db7f4d40c..ed7f4efe8 100644 --- a/tests/test_sub_callbacks.py +++ b/tests/test_sub_callbacks.py @@ -88,7 +88,7 @@ def test_openapi_schema(): with client: response = client.get("/openapi.json") assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/invoices/": { diff --git a/tests/test_tuples.py b/tests/test_tuples.py index 69df6e9bb..ca33d2580 100644 --- a/tests/test_tuples.py +++ b/tests/test_tuples.py @@ -87,7 +87,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/model-with-tuple/": { diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py index 3d6267023..3afeaff84 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py @@ -21,7 +21,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index 2073df97f..588a3160a 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -28,7 +28,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py index 77568d9d4..bd34d2938 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py @@ -21,7 +21,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index 70273cd3a..55b556d8e 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -28,7 +28,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py index 86f55b083..25d6df3e9 100644 --- a/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py +++ b/tests/test_tutorial/test_async_sql_databases/test_tutorial001.py @@ -26,7 +26,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/notes/": { diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py index 7533a1b68..a070f850f 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py @@ -15,7 +15,7 @@ def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/app": { diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py index 930ab3bf5..ce791e215 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py @@ -15,7 +15,7 @@ def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/app": { diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py index 5048d8849..ec17b4179 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py @@ -16,7 +16,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "servers": [ {"url": "/api/v1"}, diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py index b46ae1047..2f8eb4699 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py @@ -16,7 +16,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "servers": [ { diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py index e341717d5..526e265a6 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main.py +++ b/tests/test_tutorial/test_bigger_applications/test_main.py @@ -396,7 +396,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_bigger_applications/test_main_an.py b/tests/test_tutorial/test_bigger_applications/test_main_an.py index c884f0508..c0b77d4a7 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main_an.py +++ b/tests/test_tutorial/test_bigger_applications/test_main_an.py @@ -396,7 +396,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py b/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py index 134b8957f..948331b5d 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py +++ b/tests/test_tutorial/test_bigger_applications/test_main_an_py39.py @@ -423,7 +423,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index 690e97c24..2476b773f 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -395,7 +395,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_body/test_tutorial001_py310.py b/tests/test_tutorial/test_body/test_tutorial001_py310.py index ac4b9bdbe..b64d86005 100644 --- a/tests/test_tutorial/test_body/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body/test_tutorial001_py310.py @@ -411,7 +411,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py index 9a234280d..1ff2d9576 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py @@ -80,7 +80,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_an.py b/tests/test_tutorial/test_body_fields/test_tutorial001_an.py index 6f3e49397..907d6842a 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_an.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_an.py @@ -80,7 +80,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py index 41b237421..431d2d181 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py310.py @@ -86,7 +86,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py index 7c2d5e4d8..8cef6c154 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_an_py39.py @@ -86,7 +86,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py index 285924f75..b48cd9ec2 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001_py310.py @@ -86,7 +86,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py index 5d755b7b4..e5dc13b26 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py @@ -72,7 +72,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py index 377895d59..51e8e3a4e 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an.py @@ -72,7 +72,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py index 05f4cefa8..8ac1f7261 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py310.py @@ -79,7 +79,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py index 4b9b15886..7ada42c52 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_an_py39.py @@ -79,7 +79,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py index c249f5ec6..0a832eaf6 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001_py310.py @@ -79,7 +79,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py index 4f17bad50..2046579a9 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -145,7 +145,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py index 7ee17ef90..1282483e0 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an.py @@ -145,7 +145,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py index 0138332f6..577c079d0 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py310.py @@ -151,7 +151,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py index 6dae31d6a..0ec04151c 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_an_py39.py @@ -151,7 +151,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py index 01edbee84..9caf5fe6c 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003_py310.py @@ -151,7 +151,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py index 12ac3cd83..f4a76be44 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py @@ -53,7 +53,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/index-weights/": { diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py index 8af75b202..8ab9bcac8 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009_py39.py @@ -58,7 +58,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/index-weights/": { diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index cc56f16ca..b02f7c81c 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -40,7 +40,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py index 56283184f..4af2652a7 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py310.py @@ -45,7 +45,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py index ac5e2b5b5..832f45388 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001_py39.py @@ -45,7 +45,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py index dad6f307b..25d6dda35 100644 --- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py @@ -47,7 +47,7 @@ def test_default_openapi(): assert response.status_code == 200, response.text response = client.get("/openapi.json") assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py index 8a14da516..7d0e669ab 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py index 56a620282..2505876c8 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_an.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py index 310386dd2..108f78b9c 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py310.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py index db3336041..8126a1052 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_an_py39.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py index 9b3fcac3a..6711fa581 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001_py310.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index da2ca8d62..fc8362467 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py index f681f5a9d..91e5c501e 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_custom_response/test_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial004.py index ef0ba3446..de60574f5 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial004.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial004.py @@ -27,7 +27,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_custom_response/test_tutorial005.py b/tests/test_tutorial/test_custom_response/test_tutorial005.py index e4b5c1546..889bf3e92 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial005.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial005.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py index 9f1b07bee..2d0a2cd3f 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/typer": { diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py index cf204cbc0..1739fd457 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/fastapi": { diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py index f196899ac..51aa1833d 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006c.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/pydantic": { diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index a325103bc..9f1200f37 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -51,7 +51,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index 9bc85e999..7d88e2861 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -21,8 +21,9 @@ def test_get_item(): def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.0.2", + data = response.json() + assert data == { + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/next": { diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index 3bc061663..597757e09 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -56,7 +56,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/authors/{author_id}/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001.py index 372722c0a..d1324a641 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001.py @@ -27,7 +27,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an.py index aa138e43f..79c2a1e10 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_an.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_an.py @@ -27,7 +27,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py index 98bc28181..7db55a1c5 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py310.py @@ -35,7 +35,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py index bfe4647e8..68c2dedb1 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_an_py39.py @@ -35,7 +35,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py index 3ac4d7e23..381eecb63 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_py310.py @@ -35,7 +35,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py index 66ee87d76..5c5d34cfc 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004.py @@ -65,7 +65,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an.py index 847c7bb18..c5c1a1fb8 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_an.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_an.py @@ -65,7 +65,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py index a3be44ef6..6fd093ddb 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py310.py @@ -73,7 +73,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py index 948c8d322..fbbe84cc9 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_an_py39.py @@ -73,7 +73,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py index be85fc162..845b098e7 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004_py310.py @@ -73,7 +73,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py index 1949b2a92..704e389a5 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py @@ -78,7 +78,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006_an.py b/tests/test_tutorial/test_dependencies/test_tutorial006_an.py index 387f3b7ba..5034fceba 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006_an.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006_an.py @@ -78,7 +78,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py index 772ec884d..3fc22dd3c 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006_an_py39.py @@ -90,7 +90,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py index 447bd932c..753e62e43 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py @@ -145,7 +145,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012_an.py b/tests/test_tutorial/test_dependencies/test_tutorial012_an.py index 6946fe93c..4157d4612 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012_an.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012_an.py @@ -145,7 +145,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py b/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py index 40a90198f..9e46758cb 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012_an_py39.py @@ -161,7 +161,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py index 52f9beed5..a5bb299ac 100644 --- a/tests/test_tutorial/test_events/test_tutorial001.py +++ b/tests/test_tutorial/test_events/test_tutorial001.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py index 882d41aa5..81cbf4ab6 100644 --- a/tests/test_tutorial/test_events/test_tutorial002.py +++ b/tests/test_tutorial/test_events/test_tutorial002.py @@ -17,7 +17,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_events/test_tutorial003.py b/tests/test_tutorial/test_events/test_tutorial003.py index b2820b63c..0ad1a1f8b 100644 --- a/tests/test_tutorial/test_events/test_tutorial003.py +++ b/tests/test_tutorial/test_events/test_tutorial003.py @@ -22,7 +22,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/predict": { diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py index 6e71bb2de..a85a31350 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py @@ -15,11 +15,12 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": { "title": "Custom title", + "summary": "This is a very custom OpenAPI schema", + "description": "Here's a longer description of the custom **OpenAPI** schema", "version": "2.5.0", - "description": "This is a very custom OpenAPI schema", "x-logo": { "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" }, diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py index ca5a3e3e2..7710446ce 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py index 032d16230..9951b3b51 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py index 06ee96758..7c482b8cb 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py310.py @@ -40,7 +40,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py index 4679947fd..87473867b 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_an_py39.py @@ -40,7 +40,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py index a1a74eb61..0b71d9177 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001_py310.py @@ -40,7 +40,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py index f08bf4c50..21192b7db 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py @@ -28,7 +28,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py index 407c71787..c17ddbbe1 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003_py310.py @@ -38,7 +38,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py index 47790ba8f..71f6a8c70 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py @@ -18,7 +18,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py index a98700172..5475b92e1 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004_py39.py @@ -27,7 +27,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py index 7c094b253..b0861c37f 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/keyword-weights/": { diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py index b40386450..7278e93c3 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005_py39.py @@ -24,7 +24,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/keyword-weights/": { diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001.py index ea37aec53..6cc9fc228 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py index 8b22eab9e..1cd9678a1 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial003.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py @@ -32,7 +32,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py index 99a1053ca..8809c135b 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py @@ -22,7 +22,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py index 091c74f4d..efd86ebde 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py @@ -22,7 +22,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items-header/{item_id}": { diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py index 1639cb1d8..4763f68f3 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/unicorns/{name}": { diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py index f3b1e9c14..217159a59 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py @@ -38,7 +38,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py index e19047154..494c317ca 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py @@ -49,7 +49,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py index de178a720..cc2b496a8 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py @@ -52,7 +52,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py index 3e5957130..746fc0502 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001.py @@ -25,7 +25,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_an.py b/tests/test_tutorial/test_header_params/test_tutorial001_an.py index f0a13e14a..a715228aa 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001_an.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001_an.py @@ -25,7 +25,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py index 9e6691ac6..caf85bc6c 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001_an_py310.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py index 4e3fd14a8..57e0a296a 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001_py310.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001_py310.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py index f3db3205e..78bac838c 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002.py @@ -36,7 +36,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_an.py b/tests/test_tutorial/test_header_params/test_tutorial002_an.py index 5da3d83ae..ffda8158f 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002_an.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002_an.py @@ -36,7 +36,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py b/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py index 77dd5e30a..6f332f3ba 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002_an_py310.py @@ -44,7 +44,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py b/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py index ba997bee3..8202bc671 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002_an_py39.py @@ -47,7 +47,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial002_py310.py b/tests/test_tutorial/test_header_params/test_tutorial002_py310.py index b9f233e00..c113ed23e 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002_py310.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002_py310.py @@ -47,7 +47,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py index 1eb22120e..268df7a3e 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003.py @@ -26,7 +26,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_an.py b/tests/test_tutorial/test_header_params/test_tutorial003_an.py index 725fb6dd2..742ed41f4 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003_an.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003_an.py @@ -26,7 +26,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py b/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py index ab3ec5db9..fdac4a416 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003_an_py310.py @@ -34,7 +34,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py b/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py index 48d24ece9..c50543cc8 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003_an_py39.py @@ -34,7 +34,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_header_params/test_tutorial003_py310.py b/tests/test_tutorial/test_header_params/test_tutorial003_py310.py index c7af2b717..3afb355e9 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003_py310.py @@ -34,7 +34,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_metadata/test_tutorial001.py b/tests/test_tutorial/test_metadata/test_tutorial001.py index f1ddc3259..04e8ff82b 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001.py @@ -15,9 +15,10 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": { "title": "ChimichangApp", + "summary": "Deadpool's favorite app. Nuff said.", "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", "termsOfService": "http://example.com/terms/", "contact": { diff --git a/tests/test_tutorial/test_metadata/test_tutorial001_1.py b/tests/test_tutorial/test_metadata/test_tutorial001_1.py new file mode 100644 index 000000000..3efb1c432 --- /dev/null +++ b/tests/test_tutorial/test_metadata/test_tutorial001_1.py @@ -0,0 +1,49 @@ +from fastapi.testclient import TestClient + +from docs_src.metadata.tutorial001_1 import app + +client = TestClient(app) + + +def test_items(): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.json() == [{"name": "Katana"}] + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "openapi": "3.1.0", + "info": { + "title": "ChimichangApp", + "summary": "Deadpool's favorite app. Nuff said.", + "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", + "termsOfService": "http://example.com/terms/", + "contact": { + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + }, + "license": { + "name": "Apache 2.0", + "identifier": "MIT", + }, + "version": "0.0.1", + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + } diff --git a/tests/test_tutorial/test_metadata/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial004.py index f7f47a558..507220371 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial004.py +++ b/tests/test_tutorial/test_metadata/test_tutorial004.py @@ -16,7 +16,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py index 3e3c31c5b..73af420ae 100644 --- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/invoices/": { diff --git a/docs/cs/overrides/.gitignore b/tests/test_tutorial/test_openapi_webhooks/__init__.py similarity index 100% rename from docs/cs/overrides/.gitignore rename to tests/test_tutorial/test_openapi_webhooks/__init__.py diff --git a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py new file mode 100644 index 000000000..9111fdb2f --- /dev/null +++ b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py @@ -0,0 +1,117 @@ +from fastapi.testclient import TestClient + +from docs_src.openapi_webhooks.tutorial001 import app + +client = TestClient(app) + + +def test_get(): + response = client.get("/users/") + assert response.status_code == 200, response.text + assert response.json() == ["Rick", "Morty"] + + +def test_dummy_webhook(): + # Just for coverage + app.webhooks.routes[0].endpoint({}) + + +def test_openapi_schema(): + 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": { + "summary": "Read Users", + "operationId": "read_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + } + }, + "webhooks": { + "new-subscription": { + "post": { + "summary": "New Subscription", + "description": "When a new user subscribes to your service we'll send you a POST request with this\ndata to the URL that you register for the event `new-subscription` in the dashboard.", + "operationId": "new_subscriptionnew_subscription_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Subscription"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Subscription": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "montly_fee": {"type": "number", "title": "Montly Fee"}, + "start_date": { + "type": "string", + "format": "date-time", + "title": "Start Date", + }, + }, + "type": "object", + "required": ["username", "montly_fee", "start_date"], + "title": "Subscription", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py index c1cdbee24..95542398e 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py index fdaddd018..d1388c367 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py index 782c64a84..313bb2a04 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": {}, } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index 2d81ffe1f..dd123f48d 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -22,7 +22,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py index 52379c01e..07e2d7d20 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py @@ -14,7 +14,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py index deb6b0910..f92c59015 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py @@ -22,7 +22,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py index 1a6d6a1bd..6d59aa453 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py @@ -71,7 +71,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py index 76e44b5e5..58dec5769 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py @@ -21,7 +21,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py index c2b07f0a0..e7e9a982e 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -22,7 +22,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py index c5fa697d0..ebfeb809c 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py @@ -31,7 +31,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py index 1633882c6..8e79afe96 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py @@ -31,7 +31,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py index e90771f24..91180d109 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py @@ -24,7 +24,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py index ab0455bf5..acbeaca76 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial004.py +++ b/tests/test_tutorial/test_path_params/test_tutorial004.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/{file_path}": { diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py index b2c3dcf69..90fa6adaf 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params/test_tutorial005.py @@ -59,7 +59,7 @@ def test_openapi_schema(): assert response.status_code == 200, response.text data = response.json() assert data == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/models/{model_name}": { diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py index 04f5c0451..921586357 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params/test_tutorial005.py @@ -46,7 +46,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py index e479e6eb4..e07803d6c 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006.py @@ -80,7 +80,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py index 02b70f06d..6c4c0b4dc 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006_py310.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006_py310.py @@ -85,7 +85,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index 949ff857d..287c2e8f8 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -68,7 +68,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { @@ -108,6 +108,7 @@ def test_openapi_schema(client: TestClient): {"type": "null"}, ], "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", } ) | IsDict( diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py index 666064455..21fc10cd8 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an.py @@ -70,7 +70,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { @@ -110,6 +110,7 @@ def test_openapi_schema(client: TestClient): {"type": "null"}, ], "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", } ) | IsDict( diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py index 3b5134274..d22b1ce20 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py310.py @@ -75,7 +75,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { @@ -115,6 +115,7 @@ def test_openapi_schema(client: TestClient): {"type": "null"}, ], "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", } ) | IsDict( diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py index 92e334f7f..3e7d5d3ad 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_an_py39.py @@ -75,7 +75,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { @@ -115,6 +115,7 @@ def test_openapi_schema(client: TestClient): {"type": "null"}, ], "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", } ) | IsDict( diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py index 4a99ffad8..1c3a09d39 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010_py310.py @@ -75,7 +75,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { @@ -115,6 +115,7 @@ def test_openapi_schema(client: TestClient): {"type": "null"}, ], "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", } ) | IsDict( diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index 39eccf8c8..5ba39b05d 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -24,7 +24,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py index 12fac738f..3942ea77a 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an.py @@ -24,7 +24,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py index afbf07b81..f2ec38c95 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py310.py @@ -34,7 +34,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py index f92c99410..cd7b15679 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_an_py39.py @@ -34,7 +34,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py index f0a02215d..bdc729516 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py310.py @@ -34,7 +34,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py index 2006d3706..26ac56b2f 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011_py39.py @@ -34,7 +34,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index 7bc020540..1436db384 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py index be5557f6a..270763f1d 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py index d9512e193..548391683 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_an_py39.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py index b2a2c8d1d..e7d745154 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012_py39.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index 4a0b9e8b5..1ba1fdf61 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py index 71e4638ae..343261748 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an.py @@ -23,7 +23,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py index 4e90db358..537d6325b 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013_an_py39.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index 7686c07b3..7bce7590c 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -21,7 +21,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py index e739044a8..2182e87b7 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an.py @@ -21,7 +21,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py index 73f0ba78b..344004d01 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py310.py @@ -31,7 +31,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py index e2c149992..5d4f6df3d 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_an_py39.py @@ -31,7 +31,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py index 07f30b739..dad49fb12 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014_py310.py @@ -31,7 +31,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index 396873711..91cc2b636 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -114,7 +114,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index 01913979a..42f75442a 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -44,7 +44,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py index 02075476f..f63eb339c 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_an.py @@ -44,7 +44,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py index 31fe3ae0f..94b6ac67e 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py310.py @@ -56,7 +56,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py index d201848be..fcb39f8f1 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_an_py39.py @@ -56,7 +56,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py index faded40b7..a700752a3 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02_py310.py @@ -56,7 +56,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index 4af659a11..f02170814 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py index 91dbc60b9..acfb749ce 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03_an.py @@ -31,7 +31,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py index 7c4ad326c..36e5faac1 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03_an_py39.py @@ -39,7 +39,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an.py b/tests/test_tutorial/test_request_files/test_tutorial001_an.py index 5859a2bce..3021eb3c3 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_an.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_an.py @@ -103,7 +103,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py index 52ba0850f..04f3a4693 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_an_py39.py @@ -113,7 +113,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index c625ccac8..ed9680b62 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -114,7 +114,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial002_an.py b/tests/test_tutorial/test_request_files/test_tutorial002_an.py index 2d884c593..ea8c1216c 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002_an.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002_an.py @@ -114,7 +114,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial002_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial002_an_py39.py index 49a6dee51..6d5877836 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002_an_py39.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002_an_py39.py @@ -133,7 +133,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial002_py39.py b/tests/test_tutorial/test_request_files/test_tutorial002_py39.py index 1ed552ee0..2d0445421 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002_py39.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002_py39.py @@ -144,7 +144,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py index e2d69184b..85cd03a59 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003.py @@ -54,7 +54,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial003_an.py b/tests/test_tutorial/test_request_files/test_tutorial003_an.py index f199d4d2f..0327a2db6 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003_an.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003_an.py @@ -54,7 +54,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial003_an_py39.py b/tests/test_tutorial/test_request_files/test_tutorial003_an_py39.py index 51fa83470..3aa68c621 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003_an_py39.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003_an_py39.py @@ -82,7 +82,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_files/test_tutorial003_py39.py b/tests/test_tutorial/test_request_files/test_tutorial003_py39.py index 32b028909..238bb39cd 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003_py39.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003_py39.py @@ -82,7 +82,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001.py b/tests/test_tutorial/test_request_forms/test_tutorial001.py index 64956abcb..805daeb10 100644 --- a/tests/test_tutorial/test_request_forms/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms/test_tutorial001.py @@ -162,7 +162,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/login/": { diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001_an.py b/tests/test_tutorial/test_request_forms/test_tutorial001_an.py index bce08886f..c43a0b695 100644 --- a/tests/test_tutorial/test_request_forms/test_tutorial001_an.py +++ b/tests/test_tutorial/test_request_forms/test_tutorial001_an.py @@ -162,7 +162,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/login/": { diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_forms/test_tutorial001_an_py39.py index 2f5647442..078b812aa 100644 --- a/tests/test_tutorial/test_request_forms/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_request_forms/test_tutorial001_an_py39.py @@ -170,7 +170,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/login/": { diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py index 89f535937..cac58639f 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py @@ -236,7 +236,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an.py index 7388ab7b7..009568048 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an.py @@ -236,7 +236,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py index 86e160450..3d007e90b 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001_an_py39.py @@ -244,7 +244,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/files/": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py index a2c8bee12..20221399b 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003.py @@ -28,7 +28,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/user/": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py index dba7a5f02..e8f0658f4 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_01.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py @@ -28,7 +28,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/user/": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py index bd7e3dc4f..a69f8cc8d 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py @@ -37,7 +37,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/user/": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_02.py b/tests/test_tutorial/test_response_model/test_tutorial003_02.py index 6ccb054b8..eabd20345 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_02.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_02.py @@ -21,7 +21,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/portal": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_03.py b/tests/test_tutorial/test_response_model/test_tutorial003_03.py index ba4c0f275..970ff5845 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_03.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_03.py @@ -15,7 +15,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/teleport": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05.py b/tests/test_tutorial/test_response_model/test_tutorial003_05.py index d7c232e75..c7a39cc74 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_05.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py @@ -21,7 +21,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/portal": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py index a89f1dad8..f80d62572 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05_py310.py @@ -31,7 +31,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/portal": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py b/tests/test_tutorial/test_response_model/test_tutorial003_py310.py index dc8dcc0bd..64dcd6cbd 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_py310.py @@ -37,7 +37,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/user/": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py index 6d86e509d..8beb847d1 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004.py @@ -37,7 +37,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py index 11c077223..28eb88c34 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004_py310.py @@ -45,7 +45,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py b/tests/test_tutorial/test_response_model/test_tutorial004_py39.py index aef5fbe6b..9e1a21f8d 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004_py39.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004_py39.py @@ -45,7 +45,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py index 53eb5c32d..06e5d0fd1 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005.py @@ -26,7 +26,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}/name": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py index c07bd6100..0f1566243 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005_py310.py @@ -36,7 +36,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}/name": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py index 328928aff..6e6152b9f 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006.py @@ -26,7 +26,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}/name": { diff --git a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py index 756d5ede0..9a980ab5b 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006_py310.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006_py310.py @@ -36,7 +36,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}/name": { diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py index 6c94421b2..eac0d1e29 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py @@ -24,7 +24,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { @@ -42,31 +42,46 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": {"name": "Bar", "price": "35.4"}, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) } }, "required": True, diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py index 61b142e54..a9cecd098 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an.py @@ -24,7 +24,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { @@ -42,31 +42,46 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": {"name": "Bar", "price": "35.4"}, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) } }, "required": True, diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py index 613d43845..b6a735599 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py310.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { @@ -51,31 +51,46 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": {"name": "Bar", "price": "35.4"}, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) } }, "required": True, diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py index 3554ed997..2493194a0 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_an_py39.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { @@ -51,31 +51,46 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": {"name": "Bar", "price": "35.4"}, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) } }, "required": True, diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py index 861d6e421..15f54bd5a 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004_py310.py @@ -33,7 +33,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/{item_id}": { @@ -51,31 +51,46 @@ def test_openapi_schema(client: TestClient): "requestBody": { "content": { "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": {"name": "Bar", "price": "35.4"}, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, + "schema": IsDict( + { + "$ref": "#/components/schemas/Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], + } + ) } }, "required": True, diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py index a7f55b78b..417bed8f7 100644 --- a/tests/test_tutorial/test_security/test_tutorial001.py +++ b/tests/test_tutorial/test_security/test_tutorial001.py @@ -29,7 +29,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_security/test_tutorial001_an.py b/tests/test_tutorial/test_security/test_tutorial001_an.py index fc48703aa..59460da7f 100644 --- a/tests/test_tutorial/test_security/test_tutorial001_an.py +++ b/tests/test_tutorial/test_security/test_tutorial001_an.py @@ -29,7 +29,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_security/test_tutorial001_an_py39.py b/tests/test_tutorial/test_security/test_tutorial001_an_py39.py index 345e0be0f..d8e712773 100644 --- a/tests/test_tutorial/test_security/test_tutorial001_an_py39.py +++ b/tests/test_tutorial/test_security/test_tutorial001_an_py39.py @@ -40,7 +40,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py index 06b58d21e..18d4680f6 100644 --- a/tests/test_tutorial/test_security/test_tutorial003.py +++ b/tests/test_tutorial/test_security/test_tutorial003.py @@ -71,7 +71,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial003_an.py b/tests/test_tutorial/test_security/test_tutorial003_an.py index d4eba15e8..a8f64d0c6 100644 --- a/tests/test_tutorial/test_security/test_tutorial003_an.py +++ b/tests/test_tutorial/test_security/test_tutorial003_an.py @@ -71,7 +71,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial003_an_py310.py b/tests/test_tutorial/test_security/test_tutorial003_an_py310.py index 3ed146e80..7cbbcee2f 100644 --- a/tests/test_tutorial/test_security/test_tutorial003_an_py310.py +++ b/tests/test_tutorial/test_security/test_tutorial003_an_py310.py @@ -87,7 +87,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial003_an_py39.py b/tests/test_tutorial/test_security/test_tutorial003_an_py39.py index 6baac46f1..7b21fbcc9 100644 --- a/tests/test_tutorial/test_security/test_tutorial003_an_py39.py +++ b/tests/test_tutorial/test_security/test_tutorial003_an_py39.py @@ -87,7 +87,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial003_py310.py b/tests/test_tutorial/test_security/test_tutorial003_py310.py index 77e058a3f..512504534 100644 --- a/tests/test_tutorial/test_security/test_tutorial003_py310.py +++ b/tests/test_tutorial/test_security/test_tutorial003_py310.py @@ -87,7 +87,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py index e50c86ad1..22ae76f42 100644 --- a/tests/test_tutorial/test_security/test_tutorial005.py +++ b/tests/test_tutorial/test_security/test_tutorial005.py @@ -180,7 +180,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial005_an.py b/tests/test_tutorial/test_security/test_tutorial005_an.py index 585a63c74..07239cc89 100644 --- a/tests/test_tutorial/test_security/test_tutorial005_an.py +++ b/tests/test_tutorial/test_security/test_tutorial005_an.py @@ -180,7 +180,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial005_an_py310.py b/tests/test_tutorial/test_security/test_tutorial005_an_py310.py index 82a5ba554..1ab836639 100644 --- a/tests/test_tutorial/test_security/test_tutorial005_an_py310.py +++ b/tests/test_tutorial/test_security/test_tutorial005_an_py310.py @@ -208,7 +208,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial005_an_py39.py b/tests/test_tutorial/test_security/test_tutorial005_an_py39.py index 702baa696..6aabbe04a 100644 --- a/tests/test_tutorial/test_security/test_tutorial005_an_py39.py +++ b/tests/test_tutorial/test_security/test_tutorial005_an_py39.py @@ -208,7 +208,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial005_py310.py b/tests/test_tutorial/test_security/test_tutorial005_py310.py index 45905ae8b..c21884df8 100644 --- a/tests/test_tutorial/test_security/test_tutorial005_py310.py +++ b/tests/test_tutorial/test_security/test_tutorial005_py310.py @@ -208,7 +208,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial005_py39.py b/tests/test_tutorial/test_security/test_tutorial005_py39.py index d764d3022..170c5d60b 100644 --- a/tests/test_tutorial/test_security/test_tutorial005_py39.py +++ b/tests/test_tutorial/test_security/test_tutorial005_py39.py @@ -208,7 +208,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/token": { diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py index 73cbdc538..dc459b6fd 100644 --- a/tests/test_tutorial/test_security/test_tutorial006.py +++ b/tests/test_tutorial/test_security/test_tutorial006.py @@ -42,7 +42,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_tutorial/test_security/test_tutorial006_an.py b/tests/test_tutorial/test_security/test_tutorial006_an.py index 5f970ed01..52ddd938f 100644 --- a/tests/test_tutorial/test_security/test_tutorial006_an.py +++ b/tests/test_tutorial/test_security/test_tutorial006_an.py @@ -42,7 +42,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_tutorial/test_security/test_tutorial006_an_py39.py b/tests/test_tutorial/test_security/test_tutorial006_an_py39.py index 7d7a851ac..52b22e573 100644 --- a/tests/test_tutorial/test_security/test_tutorial006_an_py39.py +++ b/tests/test_tutorial/test_security/test_tutorial006_an_py39.py @@ -54,7 +54,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/me": { diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases.py b/tests/test_tutorial/test_sql_databases/test_sql_databases.py index b4248f9f8..03e747433 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases.py @@ -106,7 +106,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py index 45262ce71..a503ef2a6 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware.py @@ -108,7 +108,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py index 679af00de..d54cc6552 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py310.py @@ -120,7 +120,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py index 2310ecc00..4e43995e6 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_middleware_py39.py @@ -120,7 +120,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py index 4127b8df1..b89b8b031 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_py310.py @@ -119,7 +119,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py b/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py index 46478b81e..13351bc81 100644 --- a/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py +++ b/tests/test_tutorial/test_sql_databases/test_sql_databases_py39.py @@ -119,7 +119,7 @@ def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py b/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py index 3e50475bc..4350567d1 100644 --- a/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py +++ b/tests/test_tutorial/test_sql_databases_peewee/test_sql_databases_peewee.py @@ -107,7 +107,7 @@ def test_openapi_schema(client): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/users/": { diff --git a/tests/test_tutorial/test_sub_applications/test_tutorial001.py b/tests/test_tutorial/test_sub_applications/test_tutorial001.py index 00e9aec57..0790d207b 100644 --- a/tests/test_tutorial/test_sub_applications/test_tutorial001.py +++ b/tests/test_tutorial/test_sub_applications/test_tutorial001.py @@ -5,7 +5,7 @@ from docs_src.sub_applications.tutorial001 import app client = TestClient(app) openapi_schema_main = { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/app": { @@ -23,7 +23,7 @@ openapi_schema_main = { }, } openapi_schema_sub = { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/sub": { diff --git a/tests/test_tutorial/test_testing/test_main.py b/tests/test_tutorial/test_testing/test_main.py index 937ce75e4..fe3498081 100644 --- a/tests/test_tutorial/test_testing/test_main.py +++ b/tests/test_tutorial/test_testing/test_main.py @@ -9,7 +9,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_tutorial/test_testing/test_tutorial001.py b/tests/test_tutorial/test_testing/test_tutorial001.py index f3db70af2..471e896c9 100644 --- a/tests/test_tutorial/test_testing/test_tutorial001.py +++ b/tests/test_tutorial/test_testing/test_tutorial001.py @@ -9,7 +9,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/": { diff --git a/tests/test_union_body.py b/tests/test_union_body.py index a9a11ee8a..c15acacd1 100644 --- a/tests/test_union_body.py +++ b/tests/test_union_body.py @@ -40,7 +40,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py index a02fc35f9..ef75d459e 100644 --- a/tests/test_union_inherited_body.py +++ b/tests/test_union_inherited_body.py @@ -40,7 +40,7 @@ def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": {"title": "FastAPI", "version": "0.1.0"}, "paths": { "/items/": { diff --git a/tests/test_webhooks_security.py b/tests/test_webhooks_security.py new file mode 100644 index 000000000..a1c7b18fb --- /dev/null +++ b/tests/test_webhooks_security.py @@ -0,0 +1,126 @@ +from datetime import datetime + +from fastapi import FastAPI, Security +from fastapi.security import HTTPBearer +from fastapi.testclient import TestClient +from pydantic import BaseModel +from typing_extensions import Annotated + +app = FastAPI() + +bearer_scheme = HTTPBearer() + + +class Subscription(BaseModel): + username: str + montly_fee: float + start_date: datetime + + +@app.webhooks.post("new-subscription") +def new_subscription( + body: Subscription, token: Annotated[str, Security(bearer_scheme)] +): + """ + When a new user subscribes to your service we'll send you a POST request with this + data to the URL that you register for the event `new-subscription` in the dashboard. + """ + + +client = TestClient(app) + + +def test_dummy_webhook(): + # Just for coverage + new_subscription(body={}, token="Bearer 123") + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + # insert_assert(response.json()) + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": {}, + "webhooks": { + "new-subscription": { + "post": { + "summary": "New Subscription", + "description": "When a new user subscribes to your service we'll send you a POST request with this\ndata to the URL that you register for the event `new-subscription` in the dashboard.", + "operationId": "new_subscriptionnew_subscription_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Subscription"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "security": [{"HTTPBearer": []}], + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": {"$ref": "#/components/schemas/ValidationError"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Subscription": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "montly_fee": {"type": "number", "title": "Montly Fee"}, + "start_date": { + "type": "string", + "format": "date-time", + "title": "Start Date", + }, + }, + "type": "object", + "required": ["username", "montly_fee", "start_date"], + "title": "Subscription", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + }, + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}}, + }, + }